오히려 좋아..

상황이 나쁘게만 흘러가는 것 같을 때 외쳐보자.. .

궁금한 마음으로 포트폴리오 보기

Language/Python

[Python] @property 너 누구야? 후아유

junha6316 2021. 3. 28. 09:51

이번 포스트에서는 파이썬 클래스에서 종종 보이는 @property에 대해 알아보겠다.

결론부터 이야기하자면 @property 데코레이터는 객체의 프로퍼티를 보호해주는 함수라고 할 수 있다.

먼저 본격적인 포스팅에 앞에서 꼭 집고가야할 것이 있다.

바로 접근제어자(Access Modifier)이다.

1. 접근 제어자(Access Modifier)

1.1  접근 제어자 정의와 필요성

접근제어자(Access Modifier)는 객체의 프로퍼티의 접근을 제한하는 명령어로 객체내에 선언되어 있는 프로퍼티를 접근할 수 있는 범위를 제한하는 것이다. 대표적인 객체 지향 언어인 자바에서는 public, private, protected 가 있다.

각 접근 제어자의 접근 가능 범위는 아래와 같다

 

1. public : 모든 접근 허용(어떤 것이든 객체에 프로퍼티에 접근가능하다)

2. private : 현재 객체 내에서만 접근 가능

3. protected :  같은 패키지내에 있는 객체들과 상속관계에 있는 객체들만 접근 가능

 

그렇다면 접근제한자는 왜 존재하는 것일까? 모두 public을 줘서 수정을 편하게 만들면 코딩하기 더 쉬워지는게 아닐까? 라고 생각할 수도 있다. 하지만 로버트 마틴의 관점에서 볼 떄 접근을 제한하는 것이 장기적인 관점에서 더 효율적이다. 로버트 마틴은 소프트웨어의 목적을 아래와 같이 정의한다.

 

1. 첫번째 목적은 실행중에 제대로 동작하는 것

2. 변경을 위해 존재하는 것

3. 코드를 읽는 사람과 의사소통하는 것

 

위 3개의 목적중 특히 "변경을 위해 존재하는 것"이 접근 제한자가 존재하는 이유이다.  소프트웨어는 만들고 장땡이 아니라 끊임없는 비즈니스 요구에 유연하게 변화할 수 있어야한다. 만약 모든 객체의 property가 public하게 선언되어 있다고 가정해보자. 위에서 알 수 있듯이 public하게 선언되어 있다는 의미는 모든 접근에 열려있다는 의미이고 모든 접근에 열려 있다면 모든 객체가 중구난방으로 연결되어 있다는 말이고 결과적으로 해당 객체의 프로퍼티가 변경되었을 때 연결된 모든 객체를 변경해줘야 한다는 말과 동일하다. 연결되어 있는 객체가 1개라면 괜찮겠지만 10개라면? 20개라면? 100개라면? 어떤가? 매우 번거롭고 버그가 발생하기 좋은 코드일 것이다. 이러한 이유로 객체지향 언어에서는 접근 제한자를 두어 적당히 폐쇄적이고 적당히 열려있는 객체를 만든다. 하지만 프로그래밍을 하다보면 필연적으로 private와 같은 접근제한자가 걸려있는 프로퍼티에 접근(읽거나 지정하거나)해야 하는 일이 발생한다. 이런 기능을 수행하기 위해 직접 접근하는 것이아닌 getter, setter 메소드를 만들어 접근한다. getter는 해당 프로퍼티를 읽어오는 메서드이고 setter는 프로퍼티를 변경하는 메서드이다.

class 침착맨:

    def __init__(self):
        self.__bullshit = "나 때는 말이야" #파이썬에서 private property는 앞에 __를 붙여준다.
        
    def get_bullshit(self):
        return self.__bullshit
        
    def set_bullshit(self, bullshit):
        self.__bullshit = bullshit
        return
        
    

 

1.2 Python에서 접근 제한자

그렇다면 파이썬에서는 접근 제한자를 어떻게 구현해 놓았을까? 

파이썬에서는 별다른 접근제한자가 존재하지 않고  Naming Convention으로 이를 정의해놓았다. 즉 그냥 개발자 사이의 약속으로만 이를 정의해놓은 것이다. 접근 제한자별 표기는 아래와 같다.

class TestClass:

    def __init__(self):
        self._protected_property = 1
        self.__privated_property = 1

protected는 프로퍼티의 변수명에 "_ "를 private는 프로퍼티의 변수명에 "__"를 붙여준다고 일반적으로 알려져있다.  한편 자바에서는 접근의 제한을 위와 같은 접근 제한자를 통해 지정하며 만약 지정된 접근 제한자 밖에서 접근하려고 했을 때 에러가 발생하게 된다. 그렇다면 파이썬은 어떨까? 한번 살펴 보도록하자 위에서 작성한 테스트 클래스에서 각 변수에 접근해 보도록 하자

testclass_instance = TestClass()
testclass_instance._protected_property 
>>> 1

testclass_instance.__private_property
>>> AttributeError: 'TestClass' object has no attribute '__private_property'

위의 결과를 보면 protected_property는 접근가능하지만 private_property는 접근 불가한 것을 볼 수 있다. 여기서 멈추면 안된다. 왜 이런 일이 일어나는지 확인 해야한다. 먼저 __dict__로 인스턴스의 변수를 살펴보자.

>>> testclass_instance = TestClass()
>>> testclass_instance.__dict__
{'_protected_property':1, '_TestClass__private_property':1}

우리가 선언하지 않은 인스턴스 변수 '_TestClass__private_property'가 생긴 것을 볼 수 있다. 더블 언더바로 선언할 경우 위와 같이 _<class-name>__<attribute-name>으로 변수가 변하게 되는데 이를 mangling이라고 한다. 그렇다면 이런식으로 mangling하는 것이 private 한 변수에 접근하지 못하게 하기 위함일까? 아니다. 파이썬에서 더블 언더바는 주로 메서드에 붙는데 메서드 앞에 더블 언더바가 있는 메서드의 경우 앞으로 지속적으로 오버라이드 될 메서드임을 알려주고 다른 메서드의 이름과 충돌하지 않기 위해 이런식으로  mangling하는 것이다.(Clean Code in Python 2nd Edition, 76pg, Mariano Ananyo) 따라서  더블 언더스코어를 사용해 변수를 선언하는 것은 파이써닉하지 못하다.  따라서  private과 protected 모두 언더스코어로 표기하는 편이 파이썬 컨벤션을 지향하는 쪽이라고 할 수 있다.  개인적인 생각으로는 파이썬 protected와 private의 구분이 의미 없는 것이라고 생각한 것으로 여겨진다. 

 

그렇다면 접근 가능한 변수를 직접 가져다가 수행하면 되냐? 그건 아니다. 그건 미래의 나 자신을 힘들게 만드는 일이다. 파이썬에서도 자바와 마찬가지로 접근제한자가 지정되어 있는 변수들에 대한 getter/setter 메서드를 만들어 객체의 프로퍼티를 읽거나 수정할 수 있다. getter를 property, setter는 @[property].setter를 이용해서 구현 할 수 있다.

 

2. @property, @[property].setter

이론적인 내용은 충분하니 코드를 보면서 확인해보자. 아래 코드는 get, set 함수를 직접 만들어 구현한 Dog 클래스이다. 

class Dog:
    
    def __init__(self):
        self.__legs = legs
    
    def get_legs(self):
        return self.__legs
    
    def set_legs(self, legs):
        self.__legs = legs

이제 이 클래스를 @property와 @[property].setter를 이용해 구현해보자. 아래와 같다

class Dog:
    
    def __init__(self):
	    self.__legs = 4
    
    @property
    def legs(self):
        return self.__legs
    
    @legs.setter
    def legs(self, legs):
        self.__legs = legs

위에서 설명했다시피 @property는 getter 값을 읽어오는 역할을 하고 @[property].setter는 값을 지정하는 역할을 하게 된다.

이렇게 사용해서 얻을 수 있는 이점은 값을 읽을 때마다 getter/fuction을 따로 불러주는게 아니라 property이름으로 접근할 수 있게 된다.

#property를 사용하지 않을 때
dog =Dog()
print(dog.get_legs())
>>> 4

dog.set_legs(5)
print(dog.get_legs())
>>> 5


#property를 사용할 때

dog =Dog()
print(dog.legs)
>> 4

dog.legs= 6
print(dog.legs)
>> 6

 

3. 예시

아래와 같이 활용 할 수 있다. 객체 내부 값이 객체 외부에서 접근 할 때와 다를 때 사용하면 유용하거나 내부값을 이용해 어떤 값을 만들어내야 한다면 유용하게 사용할 수 있을거 같다

cocook.tistory.com/109

 

[프로그래머스] 파일명 정렬 python

1. 파일명 정렬  세 차례의 코딩 테스트와 두 차례의 면접이라는 기나긴 블라인드 공채를 무사히 통과해 카카오에 입사한 무지는 파일 저장소 서버 관리를 맡게 되었다. 저장소 서버에는 프로그

cocook.tistory.com