프로그래밍/python

파이썬 클래스 getter, setter와 네임맹글링(name mangling)

콘파냐 2022. 4. 13. 00:01
반응형

getter와 setter의 사용 의미

  1. getter/setter 클래스의 속성값에 직접 접근 막기 위함에 목적이 있습니다.
  2. 직접 접근을 막고 정해둔 메소드로만 접근하게 함으로서 메소드의 특성도 얻게되므로 값의 조작이나 값의 검증하거나 제한할 수 있습니다.

파이썬의 getter/setter의 사용법

파이썬에서 getter와 setter는 property 장식자를 사용하여 만들 수 있습니다.
앞으로 제시되는 예제는 3단계를 거쳐 문제점이 수정되어 완성되갑니다.

1단계 - 순수한 getter/setter

# getset.py
class GetSetTest():
    def __init__(self):
        self.count = 1

    def get_count(self): # getter
        return self.count

    def set_count(self, value): # setter
        self.count = value

if __name__ == "__main__":
    obj = GetSetTest()
    print(obj.get_count())
    obj.set_count(33)
    print(obj.get_count())

# 결과
$ python3 getset.py
1
33

get_count메소드는 getter의 역할을, set_count 메소드는 setter의 역할을 합니다. 이 코드는 단순하지만 제대로 동작합니다. 그런데 앞에서 코드를 완성해나간다고 했으며 우리는 1단계에 있으므로 이 코드는 개선될 부분이 있습니다. 어떤 부분을 개선해야하는지 정리해보겠습니다.

  • gettersetter 메소드를 사용할 때 메소드 형태로 사용하기 때문에 가독성이 떨어집니다. 코드 맥락상 count라는 속성에 접근하는 것이 가독성이 좋을 것 같습니다. 그렇다면 getter/setter를 메소드 없애고 count에 바로 접근하면 다음과 같은 이유로 바람직 하지 않습니다.
    a. count에 직접 접근을 하면 count에 어떤 값이라도 넣을 수 있습니다.

만약 GetSetTest 클래스를 설계할 때 count의 값이 0보다 크고 10보다 작도록 제한을 둔다고 한다면 count에 직접 접근하는 방식으로는 이런 제한을 둘 방법이 없습니다.

2단계 - count이름을 사용하는 getter, setter

파이썬에서는 함수,메소드의 기능을 개선, 추가해주는 장식자라는 문법이 있습니다. 파이썬의 제공하는 property장식자를 사용하면 count라는 통일된 이름의 gettersetter를 만들 수 있습니다.

# getset.py
class GetSetTest():
    def __init__(self):
        self.mycount = 9 

    @property # property 데코레이터로 count의 getter를 만든다.
    def count(self):
        return self.mycount

    @count.setter # count getter로부터 setter를 만든다.
    def count(self, value):
        if value > 0 and value <10:
            self.mycount = value
        else:
            print("0 < count < 10 의 범위내에 있어야합니다. 입력은 실패합니다.")

if __name__ == "__main__":
    obj = GetSetTest()
    print(obj.count) # 메소드가 아닌 속성으로 다루는 것처럼 보인다.
    obj.count = 33 # 메소드가 아닌 속성에 대입하는 것처럼 보인다.
    print(obj.count)

# python3 getset.py   
9
0 < count < 10 의 범위내에 있어야합니다. 입력은 실패합니다.
9 

gettersetter 메소드를 만드는 방법은 위 코드의 주석을 참고해 주세요. 좀 더 깊은 내용이 있지만 이 정도로도 충분합니다.
클래스 외부에서는 count라는 속성에 접근하여 직접 값을 읽고 = 연산자를 이용한 대입이 가능한 것처럼 보입니다.
또한 count의 범위 제한(0 < count < 10)을 둘 수 있고 위 결과처럼 벗어난 범위값에 대해 처리하는 코드도 작성하였습니다.
이렇게 코드의 가독성과 추가 제한이 가능하게 되었습니다.

그런데 위 코드는 좀 이상한 부분이 있습니다. 원래 사용하던 속성인 count가 아닌 mycount를 사용한다는 것이죠.
이렇게 한 이유가 있습니다. 한번 위 코드에서 mycountcount로 바꾼 후 실행해보세요. 그렇다면 다음과 같은 오류가 발생합니다.

Traceback (most recent call last):
  File "getset.py", line 17, in <module>
    obj = GetSetTest()
  File "getset.py", line 3, in __init__
    self.count = 9 
  File "getset.py", line 12, in count
    self.count = value
  File "getset.py", line 12, in count
    self.count = value
  File "getset.py", line 12, in count
    self.count = value
  [Previous line repeated 494 more times]
RecursionError: maximum recursion depth exceeded

이 오류는 count라는 속성이름이 이미 setter/getter에서 이미 사용하였기 때문에 발생한 것입니다.
객체 초기화 시 실행된 코드 self.count = 9는 원래 목적은 count 속성을 생성하는 것이지만 실제로는 setter인 count를 참조하게 됩니다.
그리고 더 심각한 건 setter 안에 또 settercount에 값을 넣는 다는 것이죠.
이렇게 Recursive 호출이 일어나 스택오버플로가 발생하는 것입니다.

3단계 - __(double underbar)를 사용한 네임 맹글링

__는 더블언더바 또는 더던이라고도 불립니다.
속성이름 앞에 더던을 붙이면 파이썬은 알아서 네임을 맹글링 합니다. 맹글링은 원래이름의 형체를 알 수없게 변형한다는 의미인데 규칙이 있습니다.
__count_클래스이름__count 로 변경됩니다.
파이썬 컨벤션으로 getter/setter 를 사용할 때 대상 속성에 더던을 붙입니다.

class GetSetTest():
    def __init__(self):
        self.__count = 9 # 더던

    @property
    def count(self):
        return self.__count 

    @count.setter
    def count(self, value):
        if value > 0 and value <10:
            self.__count = value
        else:
            print("0 < count < 10 의 범위내에 있어야합니다. 입력은 실패합니다.")

if __name__ == "__main__":
    obj = GetSetTest()
    print(obj.count)
    obj.count = 33
    print(obj.count)
    obj.count = 7
    # print(obj.__count) # error 
    # print(obj._GetSetTest__count) # 7

이렇게 더던을 붙여서 Recursive 에러를 해결하며 외부에서 쉽게 접근하지 못하도록 _네임 맹글링_이 됩니다.
C++과 같은 언어처럼 private와 같은 접근 지정자가 있다면 해결되겠지만 파이썬의 태생상 클래스를 엄격하게 통제하지는 않습니다.
그래도 이렇게 네임 맹글링을 통해서 혹시 실수로 더던 속성에 접근하는 것을 방지하는 것입니다.

추가 4단계, 더 자연스러운 객체 초기화

객체 생성시 값을 지정하여 초기화하는 방법이 필요하다면 앞서 코드처럼 초기화 함수 내에 self.__count = 초기화 값 처럼 사용하는 것은 자연스럽지 않습니다. 왜냐면 setter함수의 제한사항이 초기화 시에도 필요하다면 이런 제한사항이 전혀 검사되지 않기 때문이죠.
이런 상황이라면 초기화 시에도 setter함수를 활용하여 초기화 하는 것이 좋습니다.
다음은 이런 사항을 고려해 개선한 코드입니다.

class GetSetTest():
    def __init__(self, value):
        self.count = value  # setter로 초기화

    @property
    def count(self):
        return self.__count 

    @count.setter
    def count(self, value):
        if value > 0 and value <10:
            self.__count = value
        else:
            print("0 < count < 10 의 범위내에 있어야합니다. 입력은 실패합니다.")
            self.__count=1

if __name__ == "__main__":
    obj = GetSetTest(11)
    print(obj.count)
    obj.count = 33
    print(obj.count)
    obj.count = 7
    print(obj.count)

이 코드는 초기화 시에 setter를 사용했습니다. 그리고 객체 초기화를 11로 하여 제한사항을 어겨봤습니다.

  • 결과이제 객체 초기화 시에도 setter가 적용되어 제한 사항이 제대로 동작합니다. 이 경우 __count를 초기화 함수가 아닌 setter 내에서 런타임에 동적으로 생성합니다. 이런 활용은 동적 타이핑 언어인 파이썬이나 자바스크립트에서 가능하다는 것을 기억해주시면 됩니다.
  • $ python3 getset.py 0 < count < 10 의 범위내에 있어야합니다. 입력은 실패합니다. 1 0 < count < 10 의 범위내에 있어야합니다. 입력은 실패합니다. 1 7
반응형