상세 컨텐츠

본문 제목

Object Oriented Programming 원칙 (2) - 캡슐화(encapsulation)

개발/python-객체지향프로그래밍(OOP)

by Matthew0633 2022. 5. 18. 23:09

본문

캡슐화 Encapsulation

In object-oriented programming (OOP), encapsulation refers to the bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components.[1] Encapsulation is used to hide the values or state of a structured data object inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods.

Wiki 에 정의된 캡슐화의 정의의 일부이다.

캡슐화란 객체(object)의 속성과 행위(methods)를 하나로 묶고, 구현된 일부를 내부에 감추어 은닉하는 것을 의미한다고 적혀있다. 이 때, 은닉이란 외부에서 class 내 속성 값에 대한 무분별한 접근과 수정을 제한한다는 의미이다.

이러한 캡슐화가 필요한 이유는 무엇일까? 우리가 TV 의 사용법만 알면 TV의 기술을 뜯어보지 않고, 간단히 시청하고 사용할 수 있는 것처럼 우리가 정의하는 데이터모델에 대해서도 사용자에게 이러한 인터페이스를 제공하는 것을 목표로 하기 때문이다. 구현한 내용들을 잘 모아놓고, 사용자가 내부를 건드려 사용에 문제가 발생하지 않도록 단단한 테두리 내에 잘 감추어 두어야, 사용자가 문제없이 간편하게 사용할 수 있다.

만약 사용자에게 수정이 가능한 기능을 제공한다면, 제한된 범위에서 문제가 발생하지 않는 인터페이스를 제공해야한다. 이것이 유효성 검사이며, 이를 구현하기 위해서는 파이썬을 포함한 프로그래밍 언어에서는 property, getter, setter 등을 활용할 수 있다.

아래에는 캡슐화에 필요한 파이썬 문법, 접근지정자와 property 에 대해 정리하였다.

access modifier (접근지정자)

파이썬에서는 class 변수나 instance 변수를 정의할 때, 기본적으로 public 한 성질을 가지고 있고, protected, private 에 대한 강제성이 없다. 이와 관련된 python 공식문서의 일부분이다.

(9.6. Private variables) “Private” instance variables that cannot be accessed except from inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member). It should be considered an implementation detail and subject to change without notice.

강제성이 없다는 것은 private과 protected 에 대해 다른 언어와 같은 기능적 제한이 없다는 것이다. 이는 자유도를 높이고자 하는 파이썬만의 철학에서 비롯된다.

그러나, 개발자들은 일종의 naming convention 지키며 파이썬 코드들에서 access modifier 을 명시하고 있다고 적혀있다. 이는 아래와 같다.

  • name : public
  • _name : protected
  • __name : private

파이썬 내에서는 private 을 위해, 해당 개념과 관련된 제한된 방식으로서 name mangling 을 지원한다고 말한다.

(9.6. Private variables) Since there is a valid use-case for class-private members (namely to avoid name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called name mangling. Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, as long as it occurs within the definition of a class.

naming을 통해 protected, private 변수로 표시했을 경우, class 밖에서의 직접적인 접근 또는 값 수정을 장려하지 않는다는 뜻으로 받아들일 수 있다.

재밌는 것은 private 으로 표시할 때는 해당 변수에 name mangling 이 일어난다. 변수명이 임의로 바뀌게 되어 class 밖에서 해당 변수의 이름으로는 접근 또는 값 수정이 불가하게 된다. 이는 아래서 확인할 수 있다. a.__y 로 접근했을 때는 변수가 없다고 나온다. 그러나, 파이썬의 철학답게 이것이 완전히 불가능한 건 아니다. a._SampleA__y 와 같이 변경된 변수이름으로 접근하면, 접근과 값수정이 가능하다.

class SampleA:
    def __init__(self):
        self.x = 0
        self.__y = 0
        self._z = 0
 
a = SampleA()
a.x = 1

print(f'x : {a.x}')
# print('Ex2 > y : {}'.format(a.__y)) # 예외발생

print(f'z : {a._z}')
print('dir(a)) # _SampleA__y

# a._SampleA__y = 2 # 수정 가능
# print('Ex2 > y : {}'.format(a._SampleA__y))

그러나 위와 같은 접근 또는 값 수정은 private 과 맞지 않기에, 해당 변수값에 대한 getter 와 setter 메소드를 따로 정의하는 것이 일반적이다.

# Ex3
# 메소드 활용 Getter, Setter 작성

class SampleB:
    def __init__(self):
        self.x = 0
        self.__y = 0
 
    def get_y(self):
        return self.__y
 
    def set_y(self, value):
        self.__y = value

b = SampleB()

b.x = 1
b.set_y(2)


print(f'x : {b.x}')
print(f'y : {b.get_y()}')

# 변수 접근 후 수정 부분에서 일관성, 가독성 하락
print(dir(b)) # _SampleB__y

그런데, private 변수가 매우 많다면 getter, setter 메소드가 매우 많아지게 되어 코드가 비효율적으로 될 것이다. 이를 피하기 위해 property 를 활용하여 간결하게 private 변수를 위한 메소드를 정의할 수 있다.

Property 를 사용했을 때의 사용 장점을 정리하면,

  1. 파이써닉한 코드의 사용
  2. private 변수에 대한 제약 설정 가능
  3. Getter, Setter 사용의 장점 지속 (코드 일관성)
    • 캡슐화-유효성 검사 기능 추가 용이
    • 대체 표현(속성 노출, 내부의 표현 숨기기 가능)
    • Getter, Setter 작동에 대해 설계된 여러 라이브러리(오픈소스) 상호 운용성 증가

private 변수를 다룰 때, decorator + property 를 통해 getter, decorator + setter 을 통해 setter, decorator + deleter 을 통해 deleter 를 정의할 수 있다.

# Property 활용 Getter, Setter 작성

class SampleA:
    def __init__(self):
        self.x = 0
        self.__y = 0 # private

    @property
    def y(self):
        # print('Called get method.')
        return self.__y
    
    @y.setter
    def y(self, value):
        # print('Called set method.')
        self.__y = value

    @y.deleter
    def y(self):
        print('Called del method.')
        del self.__y

a = SampleA()

a.y = 2 # setter 동작
print(f'y : {a.y}') # getter 동작

# deleter 동작
del a.y
print(dir(a)) # a.__y 삭제됨

setter 을 통해 제약조건까지 설정하게 되면 private 변수의 올바른 값 수정을 권장할 수 있고, 캡슐화를 강화하게 된다.

# Property 활용 제약 조건 추가

class SampleB:
    def __init__(self):
        self.x = 0
        self.__y = 0 # private

    @property
    def y(self):
        # print('Called get method.')
        return self.__y
    
    @y.setter
    def y(self, value):
        # print('Called set method.')
        if value < 0:
            raise ValueError('0 보다 큰 값을 입력하세요.')
        self.__y = value

    @y.deleter
    def y(self):
        print('Called del method.')
        del self.__y

b = SampleB()

b.x = 1
b.y = 10
# b.y = -5 # 예외발생

print(f'x : {b.x}')
print(f'y : {b.y}')

<Reference>

https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)

관련글 더보기

댓글 영역