Today
-
Yesterday
-
Total
-
  • 파이썬 프러퍼티 사용하여 일관되게 속성에 접근하라
    Programmer/Programming 2018. 1. 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.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()))

    매번 주의를 기울여, 어떤 것은 직접 접근 'a.x'로 다른 것은 액세스 함수 'a.set_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. 그 동안의 실무 경험에서 깨달은 한가지 교훈. "때때로 백번의 설명보다 한번의 예제가 낫다." [본문으로]

    댓글

Designed by Tistory.