Today
-
Yesterday
-
Total
-
  • 파이썬 프러퍼티 사용하여 일관되게 속성에 접근하라
    Practice/Python, Perl 2018.01.17 11:56

    파이썬의 프러퍼티(property)를 설명한 책과 글은 많음으로 필요성만 살짝 언급하고 지나가겠다.

    파이썬에서는 공개 애트리뷰트(public attribute)을 직접 접근(access)하는 것이 관례이다.

    class A:
        def __init__(self):
            self.x = 0
    
    a = A()
    a.x = 1
    print('{}'.format(a.x))

    그런데, 비공개(private) 애트리뷰트[각주:1]가 필요한 경우가 있다.
    이들을 외부에서 접근하기 위해서는 엑세스 함수-즉 get_xxx(), set_xxx()와 같은 것-을 사용해야 한다.
    접근 형식이 관례와 달아서 표현의 일관성이 깨지는 것이 문제이다.[각주:2]
    이럴 때, 프러퍼티를 사용하면 동일한 표현 형식으로 접근이 가능하다.

    class A:
        def __init__(self):
            self.x = 0
            self.__y = 0
    
        @property
        def y(self):
            return self.__y
    
        @y.setter
        def y(self, value):
            self.__y = value
    
    
    a = A()
    # attribute access
    a.x = 1
    print('{}'.format(a.x))
    # property access
    a.y = 2
    print('{}'.format(a.y))

    아래 코드처럼 엑세스 함수를 사용하면, 즉, a.x와 a.set_y()를 섞어쓰면, 더럽지 아니한가?

    class A:
        def __init__(self):
            self.x = 0
            self.__y = 0
    
        def get_y(self):
            return self.__y
    
        def set_y(self, value):
            self.__y = value
    
    
    a = A()
    # attribute access
    a.x = 1
    print('{}'.format(a.x))
    # getter/setter access
    a.set_y(2)
    print('{}'.format(a.get_y()))

    매우 드물기는 하지만, 엑세스 함수의 오버라이딩(overriding)이 필요할 때가 있다.
    그러한 경우, 아래 예제 코드를 참고하여 작성하도록 한다.[각주:3]

    class A:
        def __init__(self):
            pass
    
        @property
        def x(self):
            return self.__x
    
        @x.setter
        def x(self, value):
            self.__x = value
    
    
    class B(A):
        def __init__(self):
            super().__init__()
    
        @A.x.setter
        def x(self, value):
            if value < 0:
                raise ValueError('x below 0 is not possible')
            super(B, B).x.__set__(self, value)
    
    
    class C(B):
        def __init__(self):
            super().__init__()
    
        @B.x.setter
        def x(self, value):
            if value > 100:
                raise ValueError('x above 100 is not possible')
            super(C, C).x.__set__(self, value)

    데코레이션 패턴(decoration pattern)이 상속보다 더 유용할 수 있다.
    예를 들면, getter에 캐싱 기능을 추가하는 경우가 그러하다.
    데코레이션 패턴으로 구현하는 것은 본 글의 취지와 맞지 않아서 생략한다.



    참고 링크:


    1. 관례적으로 '__'로 시작하면 비공개 애트리뷰트이다. [본문으로]
    2. 코드가 더러워진다. [본문으로]
    3. 그 동안의 실무 경험에서 깨달은 한가지 교훈. "때때로 백번의 설명보다 한번의 예제가 낫다." [본문으로]

    댓글 0

Designed by Tistory.