본문 바로가기
programming/python

[PY] 캡슐화 (encapsulation) - @property

by AteN 2022. 11. 27.

캡슐화 (encapsulation)

캡슐화는 클래스 필드에 직접 접근하지 못하게 하는 보호 수단이며, 클래스 내부의 모든 데이터는 프라이빗으로 취급 되어야 한다는 가정을 기반으로 한다. 

완전히 캡슐화된 클래스에서는 가능한 적은 수의 메서드만 퍼블릭하게 노출되어야 한다 객체의 상태를 읽거나 쓰는 모든 접근은 세터 도는 케터 메서드를 통해 제공되고 이를 적절한 사용을 보호해야 한다. 

 

예를 들어 Java 에서 다음과 같이 구현된다. 

public class UserAccount {
	private String username;
    
    public String getUsername() {
    	return username;
    }
    
    public void setUsername(String newUsername) {
    	this.username = newUsername;
    }
}

getUsername() 메서드와 setUsernmae() 메서드는 각각 username에 대한 케터와 세터이다. 

게터와 세터 뒤로 클래스 멤버에 관한 접근을 숨김으로써 (접근자(accessor)와 변경자( mutator)) 내부 클래스에 대한 올바른 접근을 보장한다.

또한 클래스 퍼블릭 API 안에 확장 포인트를 만들었다. 이는 잠재적으로 API 의 하위 호환성을 파괴하지 않고도 필요할 때 추가적으로 작동할 수 있다. 

 

파이썬은 프로퍼티 메커니즘을 이용해 접근자와 변경자에 완전히 다른 방식으로 접근한다. 프로퍼티를 이용하면 클래스의 퍼블릭 멤버를 자유롭게 노출할 수 있으며, 필요하다면 언제든 케터와 세터 메서드로 간단히 변환할 수 있다. 

이과정에서 클래스 API의 하위 호환성을 파괴하지 않을 수도 있다. 

 

class UserAccount:
	def __init__(self, username, password):
    	self._username = username
        self._password = password
        
    def get_username(self):
    	return self._username
    
    def set_username(self, username):
    	self._username = username
        
    def get_password(self):
    	return self._password
        
    def set_username(self, password):
    	self._password = password

이처럼 get_, set_ 메서드로 가득찬 코드를 본다면, 이는 c++ 또는 Java 와 같은 언어 이디엄이라고 생각해봐야 한다 

만약 능숙한 파이선 프로그래머라면 다음과 같이 코드를 작성할 것이다 

 

class UserAccount:
 	def __init__(self, username, password):
    	self._username = username
        self._password = password
    
    @property
    def password(self):
    	return self._password
        
    @property.setter
    def password(self, value):
    	self._password = value

프로퍼티는 내장 디스크립터 타입을 제공하며, 이를 이용하면 속성을 일련의 메서드와 연결할 수 있다. property() 함수는 fget, fset, fdel, doc이라는 4개의 인수를 받는다. 마지막 인수는 doc은 속성과 연결된 독스트링 함수를 정의하며 마치 메스드인 것처럼 취급된다.

 

class Rectangle:
	def __init__(self, x1, y1, x2, y2):
    	self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2
        
    def _width_get(self):
    	return self.x2 - self.x1
    def _width_set(self, value):
    	self.x2 = self.x1 + value
        
    def _height_get(self):
    	return self.y2 - self.y1
    def _height_set(self, value):
    	self.y2 = self.y1 + value
    
    width = property(
    	_width_get, _width_set,
        doc = 'rectangle width measured from left'
    )
    
    width = property(
    	_height_get, _height_set,
        doc = 'rectangle width measured from top'
    )
    
    def __repr__(self):
    	return "{}({}, {}, {}, {})".format(
        	self.__class__.__name__,
            self.x1, self.y1, self.x2, self.y2
        )

다음은 Rectangle 클래스로 두 모서리 위치(좌표)를 저장하는 속성에 직접 접근하거나 width/height 프로퍼티를 이용해 제어된다. 

프로퍼티를 이용하면 디스크립터를 쉽게 작성할 수 있지만, 클래스에 대한  상속을 이용할 때는 주의해야 한다. 생성된 속성은 현재 클래스의 메서드를 이용해 사용될 때 만들어지며 파생된 클래스에서 오버라이드된 메서드는 사용하지 않는다. 

예를 들어 부모 클래스의 width 프로퍼티의 fget메서드의 구현은 오버라이드하지 못한다

 

작동을 변경하고 싶다면 부모 클래스의 구현에 의존하는 대신, 파생된 클래스의 모든 프로퍼티 메서드를 다시 작성하는 것이 좋다. 

 

이런 이유로 프로퍼티를 생성하는 가장 좋은 구문은 property를 데커레이터를 사용하는 것이다 

이를 이용하면 내부의 메서드 시그니터의 수를 줄이고 코드의 가독성과 유지보수성을 높일 수 있다

class Rectangle:
	def __init__(self, x1, y1, x2, y2):
    	self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2
        
   	@property
    def width(self):
    	return self.x2 - self.x1
    
    @property.setter
    def width(self, value):
    	self.x2 = self.x1 + value
    
    @property
    def height(self):
    	return self.y2 - self.y1
        
    @property.setter  
    def height(self, value):
    	self.y2 = self.y1 + value

파이썬 프로퍼티 메커니즘의 장점은 클래스에 점진적으로 도입할 수 있다는 것이다. 처음에는 클래스 인스턴스의 퍼블릭 속성만 노출시키고 필요한 시점에 이들을 프로퍼티로 전환할 수 있다. 코드의 다른 부분들은 클래스 API에서 알려지거나 변경되지 않는다. 프로퍼티들은 일반적인 인스턴스 속성처럼 접근할 수 있다. 

 

'programming > python' 카테고리의 다른 글

[PY] line_profiler (@profile)  (0) 2023.03.04
[PY] Python 메모리 이모저모 - 1  (0) 2022.12.17
[PY] 객체지향 - 상속 (inheritance)  (0) 2022.11.24
[PY] 파이썬의 global과 nonlocal  (0) 2022.11.23
[PY] 파이썬 진수 변환  (0) 2022.11.22

댓글