될때까지

Python 왓더@property? 본문

학습/살이되고 뼈가되어라

Python 왓더@property?

랖니 2022. 4. 29. 17:11
728x90

요새는 책이랑 유튜브를 같이 보면서 공부를 하고 있다. 깔끔한 파이썬 탄탄한 백엔드 책을 보면서 플라스크를, 유튜브 오지랖 파이썬을 보면서는 장고를 공부하고 있다. 백엔드 책을 보면서 이제까지 잘 따라가다가 mysql설치하고 실행하는 부분에서 계속 오류가 뜬다.. ERROR! The server quit without updating PID file (/usr/local/var/mysql/~~~~-MacBook-Pro-2019.local.pid) 구글링해서 나오는 해결법 다 해봤지만 여전히 실패 실패 실패. 스트레스 ^^ 이럴 땐 잠시 쉬었다가 다시 도전해야지.. 후 다시 해보면 또 될꺼야 힘내자!

    @property      # 응???? 너는 뭐니??? 
    def coupon(self):
        if self.coupon_id:
            return Coupon.objects.get(id=self.coupon_id)
        return None
    
    def get_discount_total(self):
        if self.coupon:
            if self.get_product_total() >= self.coupon.amount:
                return self.coupon.amount
        return Decimal(0)
    
    def get_total_price(self):
        return self.get_product_total() - self.get_discount_total()

그렇게 답답했던 터미널과 flask는 치우고, 평화롭게 오지랖 파이썬 웹 프로그래밍 강의를 보며 장고 프로젝트를 따라하고 있었다. 근데 오늘은 너까지 왜그러니 근데 갑자기 등장한 @property... 너는 누구니? 영상에서도 깊게 설명을 해주지않았다. 그냥 원래는 coupon_id로 접근해야하는데 @property때문에 coupon이라고 쓸 수 있는거다라는 게 끝.. 😭 안돼요 저는 이게 뭔 지 모르는 걸요! 쿠폰으로 쓸 수 잇다니 이게 무슨 소리일까 궁금해서 유튜브, 책, 구글링 등 찾아보면서 내가 알아낸 부분들을 정리해보고자 한다. 잘 정리할 수... 있겠지?

먼저 @property를 이해하는 데 가장 큰 도움이 된 유튜브 링크 첨부!

class Employee:
    
    def __init__(self, first, last):
        self.first = first
        self.last = last
        self.email = first + '.' + last + '@email.com'
        
    def fullname(self):
        return '{} {}'.format(self.first, self.last)
    

emp_1 = Employee('John', 'Smith')

print(emp_1.first)       # John
print(emp_1.email)       # John.Smith@email.com
print(emp_1.fullname())  # John Smith

위에 첨부한 유튜브 영상를 보면서 배운 내용을 정리해보자. 먼저 Employee 클래스는 first, last를 받아 first, last, email을 정의한다. 그리고 fullname이란 메소드도 있는 데, 이 메소드는 전체 이름을 출력하는 역할을 담당한다. 각각 프린트문으로 출력을 해보면, 입력한 값이 그대로 잘 출력되는 걸 볼 수 있다.

그렇다면 emp_1객체를 생성하고 나서, John으로 입력했던 first를 변수에 직접 접근하여 Jim으로 바꿔보면 어떤 일이 일어날까?

emp_1 = Employee('John', 'Smith')

emp_1.first = 'Jim'

print(emp_1.first)       # Jim
print(emp_1.email)       # John.Smith@email.com
print(emp_1.fullname())  # Jim Smith

짜---잔!!  emp_1.first와 emp_1.fullname()은 Jim으로 값이 잘 바뀌었지만, emp_1.email은 Jim이 아닌 John으로 출력된 걸 볼 수 있다. 메소드로 정의한 fullname은 현재의 first와 last의 값을 가져오기 때문에 문제가 없다. 우리가 first나 last의 값를 바꿀 때 마다 email을 매번 수동으로 바꿔줘야 한다니.. 이게 얼마나 귀찮은 일인가. 우리가 이름이나 성을 수정하면 이메일도 자동으로 업데이트되는 게 편하다. 그렇다면 fullname 메소드처럼 email도 메소드로 만들면 되지 않을까? email도 fullname처럼 메소드로 정의해보자.

class Employee:
    
    def __init__(self, first, last):
        self.first = first
        self.last = last
        
    # __init__메소드에서 정의한 email을 아래에 메소드로 새로 정의했다.    
    def email(self):
        return '{}.{}@email.com'.format(self.first, self.last)
        
    def fullname(self):
        return '{} {}'.format(self.first, self.last)
    

emp_1 = Employee('John', 'Smith')

emp_1.first = 'Jim'

print(emp_1.first)       # Jim
print(emp_1.email)       # 에러 발생
print(emp_1.fullname())

__init__메소드에서 정의했던 email을 메소드로 새로 정의했다. 잘 실행되면 좋으련만.. 에러가 발생한다. email은 def를 붙여 우리가 함수로 변경해줬으니, 실행시에도 email이 아닌 email()로 호출을 해야한다.

emp_1 = Employee('John', 'Smith')

emp_1.first = 'Jim'

print(emp_1.first)       # Jim
print(emp_1.email())     # Jim.Smith@email.com
print(emp_1.fullname())  # Jim Smith

그럼 John이였던 first가 Jim으로 잘 바뀐 걸 확인할 수 있다. 하지만 이 방법은 좋지 않다. 우리가 클래스 내부의 코드를 수정해버렸으므로, Employee 클래스를 사용하는 모든 곳에서도 코드 수정이 이루어져야한다. 오우 노.. 이 얼마나 좋지 않은 방법인가!? 저 괄호만 없앨 수 있다면 좋을텐데.. 저 괄호를 제거하고 속성처럼 이메일에 접근할 수 있는 방법이 없을까? 이럴때 property 데코레이터를 사용한다.

class Employee:
    
    def __init__(self, first, last):
        self.first = first
        self.last = last
        
    @property
    def email(self):
        return '{}.{}@email.com'.format(self.first, self.last)
        
    def fullname(self):
        return '{} {}'.format(self.first, self.last)
    

emp_1 = Employee('John', 'Smith')

emp_1.first = 'Jim'

print(emp_1.first)       # Jim
print(emp_1.email)       # Jim.Smith@email.com
print(emp_1.fullname())  # Jim Smith

클래스에서 이메일을 메소드로 정의했지만, 괄호없이 속성처럼 접근하여 실행이 제대로 이뤄지는 걸 볼 수 있다! 그러면 클래스를 쓰고 있는 다른 곳에서도 코드 수정할 필요가 없이 깔끔하게 해결이 된다. fullname메소드 역시 프로퍼티 데코레이터를 사용하면 괄호없이 호출이 가능하다. @property를 붙인 함수는 ()로 호출을 안해도 된다는 걸 알았다. 이어서 영상에서는 setter에 대해서도 알려준다.

class Employee:
    
    def __init__(self, first, last):
        self.first = first
        self.last = last
        
    @property
    def email(self):
        return '{}.{}@email.com'.format(self.first, self.last)
        
    @property
    def fullname(self):
        return '{} {}'.format(self.first, self.last)
    

emp_1 = Employee('John', 'Smith')

emp_1.fullname = 'Corey Schafer'     

print(emp_1.first)     # 에러 발생
print(emp_1.email)    
print(emp_1.fullname)

fullname을 입력하면, 저절로 first, email이 나눠지면 좋겠다. 이대로 실행하면 can't set attribute라는 에러를 만나게 된다. 이럴때 @setter를 사용하면 문제를 해결할 수 있다.

class Employee:
    
    def __init__(self, first, last):
        self.first = first
        self.last = last
        
    @property
    def email(self):
        return '{}.{}@email.com'.format(self.first, self.last)
        
    @property
    def fullname(self):
        return '{} {}'.format(self.first, self.last)
    
    # setter를 정의한다.
    # 메소드의 이름은 fullname으로 동일하게 정의할 것
    @fullname.setter
    def fullname(self, name):      # 여기서 name은 들어올 fullname을 의미
        first, last = name.split(' ')
        self.first = first
        self.last = last
    

emp_1 = Employee('John', 'Smith')

emp_1.fullname = 'Corey Schafer'    # name = Corey Schafer

print(emp_1.first)       # Corey
print(emp_1.email)       # Corey.Schafer@email.com
print(emp_1.fullname)    # Corey Schafer

메소드의 이름은 동일하게 지어주고, @setter를 붙여줌으로써 에러없이 코드가 실행되는 걸 확인했다. emp_1의 fullname을 Corey Schafer로 정의하는 순간, @fullname.setter로 찾아가서 대입된 이름 Corey Schafer를 공백 기준으로 나누고, first와 last에 각각 값을 set(할당)하게 된다. email을 실행하게 될 때도, 현재의 first와 last가 Corey, Schafer이므로 이 정보가 적용된 email이 출력된다.

property 데코레이터와 setter 데코레이터를 잘 사용하면, 클래스를 사용하는 사람들은 코드를 변경할 필요가 없이 동일한 방식으로 해당 속성에 계속 접근할 수 있기 때문에 편리한 방법이다.

그렇게 하나를 배웠다👍

728x90