해쟈스토리

Property 본문

파이썬/클린코드

Property

해쟈 2024. 6. 14. 16:07
728x90
반응형

객체지향 프로그래밍 언어에서 프로퍼티(property)는 객체의 속성을 제어할 때 유용하게 사용되는 기능이며 데이터 캡슐화에 도움이 된다. 그리고 종종 데이터의 정확성이 객체를 생성할 수 있는지 여부를 결정한다. 그러니까 데이터가 특정 값을 가질 경우에만 존재할 수 있고, 잘못된 값을 가진 경우에는 존재할 수 없는 경우가 있다..

이것이 유효성 검사 메서드를 만드는 이유이다.(setter작업) 

파이썬에서는 property를 사용하여 setter와 getter 메서드를 더 간결하게 캡슐화할 수 있다.

일반적으로 데이터를 읽어주는 메서드를 getter(게터), 데이터를 변경해주는 메서드를 setter(세터)라고 한다.

 

위도와 경도는 특정 범위에서만 의미가 있다. 해당 범위를 벗어나는 좌표는 존재할 수 없다. 좌표를 나타내는 객체를 생성할 수 있지만 어떤 값을 사용할 때는 항상 허용 가능한 범위내에 있는지 확인해야 한다. 이런 경우 프로퍼티를 사용할 수 있다.

class Coordinate:
	def __init__(self, lat:float, long: float) -> None:
    	self._latitude = self._longtitude = None
        self.latitude = lat
        self.longtitude = long
        
    @property
    def latitude(self) -> float:
    	return self._latitude
        
    @lititude.setter
    def latitude(self, lat_value: float) -> None
    	if lat_value not in range(-90, 90+1):
        	raise ValueError(f"유효하지 않는 위도 값: {lat_value}")
        self._latitude = lat_value
        
    @property
    def longtitude(self) -> float:
    	return self._longtitude
        
    @longtitude.setter
    def longtitude(self, long_value: float) -> None
    	if long_value not in range(-90, 90+1):
        	raise ValueError(f"유효하지 않는 경도 값: {long_value}")
        self._longtitude = long_value

 

이렇게 함으로써 private 변수에 저장된 값을 반환하는 별도의 속성을 만들었다.

@latitude.setter 데코레이터로 선언된 유효성 검사 로직이 자동으로 호출되며 명령문의 오른쪽에 있는 값이 파라미터로 전달된다.

 

데코레이터를 사용하지 않고 setter와 getter 메소드를 작성하면 어떻게 될까?

class Coordinate:
	def __init__(self, lat:float, long: float) -> None:
    	self._latitude = self._longtitude = None
        self.latitude = lat
        self.longtitude = long
        
    def get_latitude(self) -> float:
    	return self._latitude
        
    def set_latitude(self, lat_value: float) -> None
    	if lat_value not in range(-90, 90+1):
        	raise ValueError(f"유효하지 않는 위도 값: {lat_value}")
        self._latitude = lat_value
        
    def get_longtitude(self) -> float:
    	return self._longtitude
        
    def set_longtitude(self, long_value: float) -> None
    	if long_value not in range(-90, 90+1):
        	raise ValueError(f"유효하지 않는 경도 값: {long_value}")
        self._longtitude = long_value

 

위와 같이 작성되었을 때 아래와 같이 사용할 수 있다.

>>> t = Coordinate(11.12, 33.33)
>>> t.set_latitude(34.3)
>>> t.get_latitude()
34.3

 

근데 이런 방식은 처음 인스턴스객체를 만들때 유효성 검사가 동작하지 않는다. 11.12, 33.33을 넣었지만 만약 1111.111를 넣어도 범위에서 벗어나지만 set_latitude가 동작하지 않는다. 

그리고 이 경우 기존에 `<인스턴스 객체>.속성 = 값` 으로 이미 이 클래스를 어딘가외부부에서 사용하고 있는 코드가 있을 경우 사용법이 .set_latitude() 메소드 사용으로 바뀌어서 유지보수했을때 다른 외부코드도 변경이 일어나야하는 단점이 존재한다. 

그래서 사용법 변경이 일어나지 않는 데코레이터를 사용하는 것이 좋다.

 

객체의 모든 속성에 대해 get_, set_ 메서드를 작성할 필요는 없다. 속성값을 가져오거나 수정할 때 특별한 로직이 필요한 경우에만 프로퍼티를 사용하자.

 

프로퍼티는 명령-쿼리 분리 원칙(command and query separation - CC08)을 따르기 위한 좋은 방법이다. 명령-쿼리 분리 원칙은 객체의 메서드가 무언가의 상태를 변경하는 커맨드이거나 무언가의 값을 변환하는 쿼리이거나 둘 중에 하나만 수행해야지 둘 다 동시에 수행하면 안 된다는 것이다.

예를 들어 set_email이라는 메서드가 있는데 세팅하고 세팅된 이메일을 반환해서 setter와 getter 역할을 둘다 한다고 치자if self.set_email("test@test.com")처럼 사용했다면 이 코드는 무엇을 의미하는 걸까? "test@test.com"으로 이메일을 설정하려는 걸까? 이미 이메일이 해당값으로 설정되어있는지 확인하려는 걸까?

아니면 동시에 이메일 값을 설정하고 상태가 유효한지 체크하려는 걸까? 동작이 여러개면 이렇듯 헷갈리게 된다.

 

@property 데코레이터는 무언가에 응답하기 위한 쿼리이고, @<property_name>.setter는 무언가를 하기 위한 커맨드이다.

무언가를 할당하고 유효성 검사를 하고 싶으면 두개 이상의 문장으로 나누어야 한다.

 


참조 : 유지보수가 쉬운 파이썬 코드를 만드는 비결 파이썬 클린코드 2nd Edition - 마리아노 아나야

 

 

   

728x90
반응형

'파이썬 > 클린코드' 카테고리의 다른 글

결합도  (0) 2024.08.19
방어적 프로그래밍  (0) 2024.08.19
dataclasses 모듈  (1) 2024.06.17
파이썬에서의 밑줄  (2) 2024.06.14
매직메소드(__enter__와 __exit__, 그리고 contextlib)  (0) 2024.06.13