Post

[Py: Class] 6.정보은닉(Information Hiding)과 __dict__


속성 감춤의 필요성

  • 프로그래머의 실수로 인한 오류는 종종 실행 시점에서는 잘 동작하는 것처럼 보이지만, 실제로는 예상치 못한 결과를 초래할 수 있다. 이러한 문제는 실제 서비스에서 발생할 경우 큰 피해를 줄 수 있다.

  • 예를 들어, 다음과 같이 주민등록상의 만 나이를 직접 조정하는 경우가 문제가 될 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      class person:
          def __init__(self, n, a):
              self.name = n
              self.age = a
          def __str__(self):
              return '{0}: {1}'.format(self.name, self.age)
        
      def main():
          p = person('James', 22)
          print(p)
          p.age -= 1 # (이슈) 나이를 감소시킴
          print(p)
        
      main()
      # James: 22
      # James: 21
    


  • 이러한 코드의 문제점은 객체의 속성에 직접 접근하게 되면, 해당 속성의 값이 어떻게 변경될지 예측하기 어렵다는 것입니다. 만약 나이를 조정하는 메소드가 제공되었고, 그 메소드 내에서 입력값의 유효성을 검사하고 오류를 반환하는 로직이 포함되어 있었다면, 위와 같은 실수는 발생하지 않았을 것입니다.
  • 따라서, 객체의 속성에 직접 접근하는 것은 데이터의 무결성을 해칠 수 있으므로, 속성을 감추어 외부에서의 접근을 제한하고, 속성을 조작하는 메소드를 통해 안전하게 데이터를 관리하는 것이 바람직하다.



직접 접근 & 간접 접근

(1) 직접 접근

  • 클래스 외부에서 [클래스객체, 클래스이름].변수이름과 같은 방법으로 변수에 직접적으로 접근하는 방식을 의미한다.
  • 그리고, 이러한 직접 접근을 막는 것을 정보 은닉이라고한다.


(2) 간접 접근

  • 메소드를 거쳐서 변수에 간접적으로 접근시키는 방식을 의미한다.
  • 해당 접근 방식으로 정보 은닉을 수행할 수 있다.
  • 즉, 메소드를 통해서만 접근할 수 있게 함으로서 안전성을 높이는 것이다.
    (기능의 관점에서는 아무런 이득이 없지만, 안전성 측면에서는 좋다.)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
      class person:
          def __init__(self, n, a):
              self.name = n
              self.age = a
        
          def add_age(self, a):
              if(a < 0):
                  print('나이 정보 오류')
              else:
                  self.age += a
        
          def __str__(self):
              return '{0}: {1}'.format(self.name, self.age)
        
      def main():
          p = person('James', 22)
          p.add_age(1) # 메소드를 통해서 변수에 간접 접근
          print(p)
        
      main()
    



직접 접근의 원천 봉쇄

  • 만약 p.age와 같은 직접접근을 100% 차단하고 싶으면 다음과 같이
    class의 변수 속성 이름앞에 __를 붙여주면 된다.

    • self.ageself.__age
    • self.nameself.__name


  • 만약, __가 붙어있는 변수에 직접 접근을 시도하려고하면 다음과 같이 AttributeError를 발생시키면서 class 객체에 색인 변수가 없다며 착한 거짓말을 선 보인다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      class person:
          def __init__(self, n, a):
              self.__name = n
              self.__age = a
          def add_age(self, a):
              if(a < 0):
                  print('나이 정보 오류')
              else:
                  self.__age += a
          def __str__(self):
              return '{0}: {1}'.format(self.__name, self.__age)
        
      def main():
              p = person('James', 22)
          print(p.name) # James
          print(p.__age) # AttributeError: 'person' object has no attribute '__age'
        
      main()
    


  • 하지만, _를 2개씩이나 붙여주는 것은 스페셜 메소드와 비슷하기에 code상에서는 시각적으로 좋지 않아서 프로그래머들 사이에서는 암묵적으로 _를 하나만 붙여주어 절대 직접 접근하지말라고 경고를 보내기도 한다.



__dict__ 스페셜 메소드

  • class객체 내에는 해당 객체의 변수 정보를 담고 있는 딕셔너리가 객체 당 한개 씩 존재한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      class personperson:
          def __init__(self, n, a):
              self.name = n
              self.age = a
        
      def main():
          x = personperson('James', 22)
          print(x.__dict__)
        
      main() # {'name': 'James', 'age': 22}
    


  • 만약, 객체에 변수가 추가되거나 기존 변수의 값이 변경되면 __dict__에도 정보의 변경사항이 반영된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      class personperson:
          def __init__(self, n, a):
              self.name = n
              self.age = a
        
      def main():
          x = personperson('James', 22)
          print(x.__dict__)
          x.gender = 'M' # gender라는 변수를 객체에 추가
          x.name = 'Mic' # name라는 변수의 값을 변경
          print(x.__dict__)
        
      main() # {'name': 'Mic', 'age': 22, 'gender': 'M'}
    


  • 또한, __dict__를 이용해서 객체 내 변수의 값도 수정 가능하다.
    즉, 객체 내에 있는 변수의 값은 사실 __dict__를 통해서 관리됨을 알 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      class personperson:
          def __init__(self, n, a):
              self.name = n
              self.age = a
        
      def main():
          x = personperson('James', 22)
          x.__dict__['name'] = 'doradora'
          x.__dict__['city'] = 'seoul'
          print(x.__dict__)
         
      main() # {'name': 'doradora', 'age': 22, 'city': 'seoul'}
    


  • class객체에서의 __dict__를 정리하면 다음과 같다.
    • class객체의 변수 정보를 딕셔너리 형태로 담고 있다.
    • class객체의 추가 및 변동 사항 또한 __dict__에 반영된다.
    • class객체의 변수는 __dict__를 이용해서 추가 및 변경가능하다.
    • 즉, 객체 내에 있는 변수의 관리자는 바로 __dict__이다.



직접 접근의 봉쇄가 가능했던 이유

  • __를 사용했을 때 변수의 이름으로 직접 접근이 불가능했던 이유는 __dict__를 통해서 확인할 수 있다.


  • 다음의 예제에서 __가 붙은 변수를 가지고 있는 class객체의 딕셔너리를 확인해보면 다음과 같이 __dict__에 등록된 속성의 이름이 다음과 같은 패턴으로 수정되었음을 알 수 있다.

    __AttrName_ClassName_AttrName
    __name_sample__name
    __age_sample__age
  • 즉, 변수 이름에 언더바를 두 개 붙이면 파이썬은 접두에 [_클래스 객체 이름]을 붙이는 패턴으로 변수 이름을 바꾸어 버리기 때문에 기존에 생성한 변수의 이름을 통해서 객체 외부에서 접근이 불가능 했던 것이다.


  • 따라서, 변수 이름에 언더바를 두 개 붙이더라도 바뀐 이름으로 접근하다면 그 접근은 막지 못한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      class sample:
          def __init__(self, x, y):
              self.__x = x
              self.__y = y
        
      def main():
          s = sample(10, 50)
          print(s.__dict__) #{'_sample__x': 10, '_sample__y': 50}
        
          print(s._sample__x) # 10
          print(s._sample__y) # 50
            
      main()
    



References

  • 윤성우, 『윤성우의 열혈 파이썬 중급편』, ORANGE MEDIA(2021)
This post is licensed under CC BY 4.0 by the author.