ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] Tip - 공개 속성보다는 비공개 속성을 사용
    언어/파이썬 & 장고 2016. 10. 29. 16:01

    파이썬에는 클래스 속성의 가시성(visibility)이 공개(public)와 비공개(private) 두 유형밖에 없습니다.

    class MyObject:
        def __init__(self):
            self.public_field = 5
            self.__private_field=10
    
        def get_private_field(self):
            return self.__private_field
    
    foo = MyObject()
    assert foo.public_field == 5

    assert foo.__private_field == 10 # 에러


    클래스 메서드도 같은 class 블록에 선언되어 있으므로 비공개 속성에 접근할 수 있습니다.

    class MyObject:
        def __init__(self):
            self.__private_field = 10
    
        @classmethod
        def get_private_field_of_instance(cls, instance):
            return instance.__private_field
    
    
    bar = MyObject()
    assert bar.get_private_field_of_instance(bar) == 10


    비공개 필드라는 용어에서 예상할 수 있듯이 서브클래스에는 부모 클래스의 비공개 필드에 접근할 수 없습니다.

    class MyObject:
        def __init__(self):
            self.__private_field = 10
    
    
    class Child(MyObject):
        def get_private_field_of_instance(self):
            return self.__private_field
    
    
    bar = Child()
    bar.get_private_field_of_instance()
     
    # 에러 
    AttributeError: 'Child' object has no attribute '_Child__private_field'


    비공개 속성의 동작은 간단하게 속성 이름을 변환하는 방식으로 구현됩니다. 파이썬 컴파일러는 Child.get_private_field 같은 메서드에서 비공개 속성에 접근하는 코드를 발견하면 __private_field를 _Child_private_field에 접근하는 코드로 변환시킵니다. 위 예제에서는 __private_field가 MyObject.__init__에만 정의되어 있으므로 비공개 속성의 실제이름은 _MyObject_private_field가 됩니다. 자식 클래스에서 부모의 비공개 속성에 접근하는 동작은 단순히 변환된 속성 이름이 일치하지 않아서 실패하게 되는 것입니다.

    이 체계를 이해하면 접근 권한을 확인하지 않고서도 서브클래스나 외부 클래스에서 어떤 클래스의 비공개 속성이든 쉽게 접근할 수 있습니다.

    assert bar._MyObject__private_field == 10

    print(bar.__dict__)
    # 결과
    # {'_MyObject__private_field': 10}

    객체의 속성 딕셔너리를 들여다보면 실제로 비공개 속성이 변환 후의 이름으로 저장되어 있음을 알 수 있습니다.


    파이썬을 처음 접하는 많은 프로그래머가 서브클래스나 외부에서 접근하면 안 되는 내부 API를 비공개 필드로 나타냅니다.

    class MyClass:
        def __init__(self, value):
            self.__value = value
    
        def get_value(self):
            return str(self.__value)
    
    foo = MyClass(5)
    assert foo.get_value() == '5'


    이러한 접근 방식은 잘못되었습니다. 누군가는 클래스에 새 동작을 추가하거나 기존 메서드의 결함을 해결하려고 서브클래스를 만들기 마련입니다. 비공개 속성을 선택하면 서브클래스의 오버라이드와 확장을 다루기 어렵고 불안정하게 만들 뿐입니다. 나중에 만들 서브클래스에서 꼭 필요하면 여전히 비공개 필드에 접근할 수 있습니다.

    class MyClass:
        def __init__(self, value):
            self.__value = value
    
        def get_value(self):
            return str(self.__value)
    
    
    class MyIntegerSublass(MyClass):
        def get_value(self):
            return int(self._MyClass__value)
    
    
    foo = MyIntegerSublass(5)
    assert foo.get_value() == 5


    하지만 나중에 클래스의 계층이 변경되면 MyIntegerSublass같은 클래스는 비공개 참조가 더는 유효하지 않게 되어 제대로 동작하지 않습니다. MyIntegerSublass클래스의 직계 부모인 MyClass에 MyBaseClass라는 또 다른 부모 클래스를 추가했다고 가정합니다.

    class MyBaseClass:
        def __init__(self, value):
            self.__value = value
            
    
    class MyClass(MyBaseClass):
        def get_value(self):
            return str(self.__value)
    
    
    class MyIntegerSublass(MyClass):
        def get_value(self):
            return int(self._MyClass__value)


    이제 __value속성을 MyClass 클래스가 아닌 MyBaseClass에서 할당합니다. 그러면 MyIntegerSublass에 있는 비공개 변수 참조인 self._MyClass__value가 동작하지 않습니다.

    일반적으로 보호 속성을 사용해서 서브클래스가 더 많은 일을 할 수 있게 하는 편이 낫습니다. 각각의 보호 필드를 문서화해서 서브클래스에서 내부 API 중 어느 것을 쓸 수 있고 어느 것을 그대로 둬야 하는지 설명해야합니다. 이렇게 하면 자신이 작성한 코드를 미래에 안전하게 확장하는 지침이 되는 것처럼 다른 프로그래머에게도 조언이 됩니다.

    class MyClass:
        def __init__(self, value):
            """
            사용자가 객체에 전달한 값을 저장
            문자열로 강제할 수 있는 값이어야 하며 객체에 할당하고 나면 불변으로 취급
            """
            self._value = value


    비공개 속성을 사용할지 진지하게 고민할 시점은 서브클래스와 이름이 충돌할 염려가 있을 때뿐입니다. 주로 공개 API의 일부일 때 문제가 됩니다. 서브클래스는 직접 제어할 수 없으니 문제를 해결하기 위해 리펙토링을 할 수 밖에 없습니다. 이런 상황이 일어날 위험을 줄이려면 부모 클래스에서 비공개 속성을 사용해서 자식 클래스와 속성 이름이 겹치지 않게 하면 됩니다.

    요약

    파이썬 컴파일러는 비공개 속성을 엄격하게 강요하지 않음

    서브클래스가 내부 API와 속성에 접근하지 못하게 막기보다는 처음부터 내부 API와 속성으로 더 많은 일을 할 수 있게 설계해야함

    비공개 속성에 대한 접근을 강제로 제어하지 말고 보호 필드를 문서화해서 서브클래스에 필요한 지침을 제공

    직접 제어할 수 없는 서브클래스와 이름이 충돌하지 않게 할 때만 비공개 속성을 사용하는 방안을 고려



    댓글