Skip to content

Python에서의 Encapsulation 관련 구현.

Data Hiding 관점

Encapsulation을 엄격히 적용하려면,

  • data에 직접 접근이 아닌
  • getter나 setter와 같은 method들을 통해서만 이루어져야 한다.

문제는 python에서는 이를 구현하기 쉽지 않다는 점이다. 이를 위해 Python에서 지원하는 기법은 Name mangling이다.

다음 코드를 살펴보자.

class Student:
    def __init__(self, name, age):
        self.name = name
        self.__age = age #Naming mangling
        self._job = "test" # 관례적으로 접근하지 말것을 _로 표시.

    def set_name(self,age):
        self.name = name

    def set_age(self,age):
        self.__age = age

    def set_job(self,job):
        self._job = job # 관례상 _가 앞에 있으면 접근하지 말라는 애기임.

    def display(self):
        print(f"{self.name}'s age is {self.__age}")
        print(f"{self.name}'s job is {self._job}")

if __name__ == "__main__":
    s = Student("김 아무개", 21)
    s.name = "박 아무개"
    s._job = "학생" # 관례이므로 강제력이 Name mangling보다 약하다. (그냥 됨)
    s.display()
    print(dir(s))
    # print(s.__dict__) #attribute들을 출력해줌.
  • __age처럼 attribute의 이름에 __를 붙이면, python은 이 property에 data hiding을 위한 처리로 실제 이름을 _Student__age로 변경하여 관리한다.
  • class내에서는 self.__age를 사용하여 접근하는 구현이 가능하나, 외부에서 접근하려면 __age만으로는 되지 않는다.
  • 위 코드에서 s.__age = 32는 에러가 뜨진 않지만, 새로운 __age라는 property가 추가되고 거기에 32가 value가 된다.
  • 만약 s.__age = 32 대신 print(s.__age)같이 property의 value에 접근하는 경우에는 AttributeError가 발생한다. s._Student_age는 있지만, s.__age는 없기 때문이다.

외부에서의 직접적인 접근을 막기 위해 도입 된 것이지만...
어떤 의미로는 유명무실하기도 하다.
다 같은 개발자들이니, 고치고자 마음을 먹으면 쉽게 처리할 수 있다.

이 방식의 한계는 명확하다. Python에서는 각 object 별로 __dict__를 통해 property들이 관리가 되기 때문이다.

__dict__는 해당 object의 attribute들을 dictionary형태로 가지고 있으며 이를 통해 attribute들을 관리한다.
관리를 한다는 애기는 __dict__를 통한 직접 접근이 가능하다는 애기이다. 즉 dictionary 사용을 통해 attribute들의 직접변경이 가능하다.
때문에 Python에서는 엄격한 Encapsulation을 구현할 수 없다.

property설정.

property를 이용하여 encapsulation의 장점을 살리면서 직접 접근하는 것처럼 간단한 코딩도 가능하다.

다음 예제에서 property를 사용한 부분을 참고하라.

class Student:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age #Naming mangling

    def get_name(self):
        return self.__name
    def set_name(self,name):
        self.__name = name    
    name = property(get_name, set_name)# property 이용
    # name = property() # 위의 라인과 동일한 효과의 3개 라인임.
    # name.getter(get_name)
    # name.setter(set_name)

    @property
    def age(self):
        return f"[{self.__age}]"

    @age.setter
    def age(self,age):
        if age < 0:
            self.age = 0
        else:
            self.__age = age


    def display(self):
        print(f"{self.__name}'s age is {self.__age}")

if __name__ == "__main__":
    s = Student("김 아무개", 21)
    s.name = "박 아무개" 
    s.age = -10
    print(s.age)
    print("------------")
    print(s.display())
    print("------------")
    print(s.__dict__) # property들은 보이지 않음.
    print("------------")
    print(dir(s)) # property확인 가능.
  • s.age = -10으로 직접 접근하는 것처럼 보이지만, 실제로는 set_age(self, age)메서드를 통해 접근된다.
  • 재사용이 빈번한 class에서는 권장되는 방식임.
  • nameproperty object 를 생성하여 구현.
  • age는 Decorator @property@XXX.setter를 사용하여 구현됨.

References