ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Python: 객체 지향 프로그래밍을 위한 4가지 기본개념 (상속, 추상화, 캡슐화, 다형성)2
    Python 2020. 8. 16. 16:09

    이번 포스팅은 전 포스팅에서 못 다룬 캡슐화 다형성에 대해서 공부해봅시다.

     

    상속과 추상화에 관한 내용은 아래에 있습니다. 

     

    Python: 객체 지향 프로그래밍을 위한 4가지 기본개념 (상속, 추상화, 캡슐화, 다형성)1

    객체 지향 프로그램을 다루기 위해서는 알아야 할 4가지 기본개념에 대해 알아봅시다. 크게 4가지로 나뉩니다. 상속 추상화 캡슐화 다형성 하나씩 차례로 알아봅시다. < 상속 > 재산상속할때 그 �

    seungjuitmemo.tistory.com

     

    <캡슐화>

     

     

    캡슐화란 인스턴스를 생성했을 때 일부 구현 내용에 대한 

     

    외부로부터의 직접적인 엑세스를 차단하는것입니다. 

     

    쉽게 말해서 캡슐처럼 객체 내부를 숨겨 외부로부터의 엑세스를

     

    차단하는 것입니다. 

     

     

     

    객체 내부를 숨기는 법은 크게 두가지가 있습니다.

     

     

    첫번째는 언더바 두개를 이용하는 겁니다.

     

    변수앞에 __(언더바 두개)를 쓰면 외부로부터 접근을 막을 수 있습니다. 

     

     

     

    예를 들어보겠습니다. 

     

    이름, 나이, 주민등록번호를 갖는 Citizen 클래스가 있습니다. 

     

    class Citizen:
    
        def __init__(self, name, age, id):
            self.name = name
            self.set_age(age)
            self.__id = id
            
            
        def authenticate(self, id):
            return self.__id == id
            
            
        def get_age(self):
            return self.__age
            
            
        def set_age(self, age):
            if age > 0:
                self.__age = age
            else:
                print("나이가 0보다 작습니다. 나이를 0으로 설정하겠습니다.")
                self.__age = 0

     

    주민등록번호와 나이는 개인정보이므로 __id, __age라고 선언해주었습니다. 

     

    인스턴스 생성후, 인스턴스 자체로 __age에 접근해서 10을 넣어줘 보겠습니다. 

     

    person = Citizen("김판다", 25, "123456-1234567")
    print(person.get_age())
    person.__age = 10
    print(person.get_age())

     

    25
    25

     

    하지만 출력 결과는 예상과 다르게 10이 아닌 25네요?

     

    __때문에 그렇습니다. 만약 age의 값을 원하는대로 바꾸어 주고 싶다면

     

    getter와 setter 메소드를 이용해서 값 설정을 해주어야합니다. 

     

    person = Citizen("김판다", 25, "123456-1234567")
    print(person.get_age())
    person.__age = 10
    print(person.get_age())
    person.set_age(10)
    print(person.get_age())

     

    25
    25
    10

     

     

     

     

     

    또한 이러한 방법 말고도 name mangling을 이용해서 접근할 수도 있는데요 

     

    클래스명과 변수명을 이용하여 _Citizen__age으로 접근할 수 있습니다. 

     

    person = Citizen("김판다", 25, "123456-1234567")
    print(person.get_age())
    person.__age = 10
    print(person.get_age())
    person.set_age(10)
    print(person.get_age())
    person._Citizen__age = 20
    print(person.get_age())

     

    25
    25
    10
    20

     

     

     

     

     

    두번째 방법은 사실 방법이라기보다 약속입니다. 

     

     

    첫번째 방법은 언더바 두개를 이용해서 접근하지 못하게 했지만 

     

    name mangling으로 접근을 할 수 있었습니다. 완벽하게 접근을 막는 방법은 아니였습니다.

     

    사실 파이썬은 캡슐화를 지원하는 언어가 아니기 때문입니다.

     

     

    그래서 개발자끼리 약속을 했습니다.

     

    변수 앞에 _가 있다면 그 변수는 함부로 건드리지 말라는 뜻으로 

     

    만약 건드리게 되면 클래스 자체가 이상하게 반응할 수도 있기 때문입니다. 

     

    그래서 변수에 값을 지정하고 싶다면 getter와 setter메소드를 이용하는 것이 가장 이상적입니다. 

     

     

     

     

     

    마지막으로 property 데코레이터를 이용하여 getter와 setter를 만든 후 age 값을 설정해보겠습니다. 

     

    class Citizen:
    
        def __init__(self, name, age, id):
            self.name = name
            self.age = age
            self._id = id
            
            
        def authenticate(self, id):
            return self._id == id
    
    
        @property
        def age(self):
            print("나이를 리턴합니다")
            return self._age
    
    
        @age.setter
        def age(self, age):
            print("나이를 지정합니다.")
            if age > 0:
                self._age = age
            else:
                print("나이가 0보다 작습니다. 나이를 0으로 설정하겠습니다.")
                self._age = 0
    

     

    person = Citizen("김판다", 25, "123456-1234567")
    print(person.age)
    person.age = 10
    print(person.age)

     

    property를 이용하면 age는 메소드임에도 불구하고 

     

    person 인스턴스에 변수처럼 값을 지정할 수 있습니다.

     

    전자의 age메소드는 getter로

     

    후자의 age메소드는 setter로 작동합니다.  

     

    나이를 지정합니다
    나이를 리턴합니다
    25
    나이를 지정합니다
    나이를 리턴합니다
    10

     

     

     

     

     

    < 다형성 > 

     

     

    다형성이란 여러가지 형태를 갖는 성질을 의미합니다.

     

    하나의 객체가 여러개의 타입을 가르킬 수 있다는 겁니다.

     

     

     

     

     

    바로 예시를 이용해서 알아봅시다.

     

    그림판(Paint) 클래스를 만들어 그림판에 도형들을 추가하고

     

    총 넓이와 총 둘레의 길이를 구해보겠습니다.  

     

    총 5개의 클래스가 있습니다.

     

    • Shape 추상 클래스: 넓이와 둘레길이의 합을 구하는 메소드를 갖는다.
    • Rectangular 클래스: Shape를 상속받는다.
    • Circle 클래스: Shape를 상속받는다.
    • Cylinder 클래스: 넓이를 구하는 메소드만 가지고 있다.
    • Paint 클래스: 그림판에 도형을 추가한다. 도형들의 총 넓이와 총 둘레의길이를 구하는 메소드를 갖는다. 

     

    from abc import ABC, abstractmethod
    
    class Shape(ABC):  # 도형 추상 클래스
    
        @abstractclassmethod
        def get_area(self) -> float:
            pass
    
        @abstractclassmethod
        def get_perimeter(self) -> float:
            pass
    
    
    class Rectangular(Shape):  # 직사각형 
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def get_area(self):
            return self.x * self.y
    
        def get_perimeter(self):
            return 2 * (self.x + self.y)
    
        def __str__(self):
            return "가로:{}, 세로: {}인 직사각형".format(self.x,self.y)
    
    
    class Circle(Shape):  # 원 
    
        def __init__(self, radius):
            self.radius = radius
    
        def get_area(self):
            return pi * (self.radius ** 2)
    
        def get_perimeter(self):
            return 2 * pi * self.radius
    
        def __str__(self):
            return "반지름이 {}인 원".format(self.radius)
    
    
    class Cylinder:  # 원기둥
        def __init__(self, radius):
            self.radius = radius
    
        def get_area(self):
            return pi * (self.radius**2)

     

    class Paint():  # 그림판 클래스
    
        def __init__(self):
            self.shapes = []
    
        def add_shape(self, shape):  # 도형들을 그림판위에 추가합니다. 
            if isinstance(shape, Shape):
                self.shapes.append(shape)
            else:
                print("필요한 메소드가 없습니다.")
    
        def get_total_area(self):  # 그림판위의 도형들의 총 넓이를 구합니다.
            return sum([shape.get_area() for shape in self.shapes])
    
        def get_total_perimeter(self):  # 그림판위의 도형들의 총 둘레의 길이를 구합니다.
            return sum([shape.get_perimeter() for shape in self.shapes])
    
        def __str__(self):
            total = ""
            for shape in self.shapes:
                total += str(shape) +"\n"
    
            return total

     

    다형성은 여기서 나옵니다. 

     

    Paint 클래스안 add_shape 메소드의 파라미터 shape은 어떤 도형이든 다 될 수 있습니다. 

     

    shape는 원이 될수도, 사각형이 될수도, 원기둥이 될 수도 있습니다. 

     

    하지만 만약 Cylinder 인스턴스가 들어온다면 

     

    get_total_perimeter를 구하는 과정에서 Cylinder 인스턴스는 perimeter 메소드가 없기 때문에

     

    에러가 나게 됩니다. 

     

     

    에러를 사전에 방지하고자 add_shape 메소드 내부에서 isinstance 함수를 이용해

    (isinstance(A, B): A가 B클래스의 인스턴스이면 True, 아니면 False 출력 )

     

    파라미터로 들어온 shape가 Shape의 인스턴스냐를 확인한 후 

     

    맞다면 그림판에 추가, 아니라면 그림판에 추가하지 못하게 설정해놨습니다. 

     

    여기서 또 하나 중요한 것은 Shape라는 추상클래스를 만들어서

     

    다형성을 구분했다는 것입니다. 

     

    이렇게 추상클래스와 isinstance메소드를 이용하면 다형성을 이용하기 쉬워집니다. 

    (참고로 추상 메소드는 type hinting을 해주는게 좋습니다)

     

     

     

    잘 출력되는지 확인해봅시다. 

    c1 = Circle(5)  # 원 생성
    r1 = Rectangular(2, 10)  # 사각형 생성
    cy1 = Cylinder(3)  # 원기둥 생성
    
    print(c1.get_area())  # 각 도형들의 넓이를 구합니다.
    print(r1.get_area())
    print(cy1.get_area())
    
    
    paint = Paint() # paint 인스턴스 생성
    
    paint.add_shape(c1)  # 그림판에 도형들을 추가합니다. 
    paint.add_shape(r1)
    paint.add_shape(cy1)
    
    print(paint) # 넓이와 둘레의 길이를 구합니다. 

     

    78.53981633974483
    20
    28.274333882308138
    필요한 메소드가 없습니다.
    반지름이 5인 원
    가로:2, 세로: 10인 직사각형

     

    그림판 리스트 목록에 원기둥이 없네요

     

    Cylinder 인스턴스는 필요한 메소드가 없음으로

     

    잘 걸러진것을 알 수 있습니다. 

     

     

     

     

     

    이외에도 클래스 다형성이외에 함수다형성, 메소드 다형성도 있습니다. 

     

     

    1. 옵셔널 파라미터

     

     

    파라미터에 미리 값을 지정해놓으면 모든 파라미터를 넣지 않아도

     

    미리 정해 놓은 값이 들어갑니다. 

     

    단, 값은 뒤에서부터 순차적으로 정할 수 있습니다. 

     

    def new_print(first = None, second = None, third, fourth):

     

    이런것들이 불가능합니다. 

     

    def new_print(first, second = None, third = None, fourth = None):
        print(first, second, third, fourth )
    
    new_print("abcde", 1234)
    

     

    abcde 1234 None None

     

     

     

     

     

     

    2. 파라미터 이름 명시

     

     

    파라미터 이름만 명시해 준다면, 순서가  달라도 상관없습니다. 

     

    def new_print(first, second = None, third = None, fourth = None):
        print(first, second, third, fourth )
    
    new_print(second = "abcde", first = 1234)

     

    1234 abcde None None

     

     

     

     

     

    3. 개수가 정해지지 않은 파라미터 

     

     

    *y 라고 하면 y부터 시작해서 끝까지 튜플형태로 저장됩니다. 

     

    def new_print2(x, *y):
        print(x, sum(y))
    
    new_print2("abcde", 3, 4, 5, 1, 2, 3)
    

     

    abcde 18

     

    (3, 4, 5, 1, 2, 3)이 한 튜플로 만들어져서 총 합을 구해보면 18이 맞네요 ㅎㅎ

    반응형

    댓글

Designed by Tistory.