본문 바로가기

진리는어디에/Python

[Python] 클래스(class) #4 property

이번 시간에는 클래스 필드의 getter와 setter를 쉽게 만들어 줄 수 있는 property라는 문법에 대해 살펴 보도록 하겠다. 이번 포스트에서는 사용법 위주로 익히고, 원리에 대한 자세한 설명은 뒤에 descriptor를 다루면서 추가 하도록 하겠다.

목차

  1. 파이썬 클래스 소개
  2. instance vs static
  3. dict vs slots
  4. >> property의 활용
  5. special method
  6. callable object
  7. 클래스 데코레이터(class decorator)
  8. 상속
  9. 추상 클래스와 추상 메소드

getter와 setter

먼저 프로퍼티에 대해 살펴보기 전에 우리는 getter와 setter의 개념에 대해 알아야 한다. getter와 setter는 C#이나 Java에서도 많이 사용되는 개념으로써, 객체의 필드(멤버 변수)에 직접적인 접근 대신, 함수 통해 접근하도록하는 방법을 말한다.

지금 부터 예제들을 통해 파이썬에서 getter와 setter가 어떤 역할을 하며 왜 필요한지를 살펴 보도록 하겠다.

class Person :
    
    def __init__(self, name : str, age : int) :
        self.name = name
        self.age = age

person = Person('Jason', '24')
print(person.name, person.age) # Jason 24

person.name = 10               # 문자열로 쓰고 싶었지만 정수를 저장함
person.age = -100              # 나이는 마이너스가 없지만 마이너스 값을 저장함
print(person.name, person.age) # 10 -100

위 예제에서 Person 클래스는 문자열을 저장하는 name이라는 필드와 정수를 저장하는 age라는 필드를 가지고 있다. 하지만 타입에 대한 강제성이 없는 파이썬은 10, 11라인과 같이, 외부에서 필드에 직접 접근하여 엉뚱한 값으로 수정할 때 방어 할 수 있는 방법이 없다.

필드를 외부에서 직접 접근하면..
"객체가 잘못된 상태"를 가질 수 있다. 그래서 getter와 setter를 사용하는 것이 좋다.

이와 같은 상황을 방지하기 위해 아래와 같이 name, age 필드를 직접 접근하는 대신 getter와 setter함수를 만들어 값을 저장하거나 참조 할 때, 값에 대한 유효성을 검사할 수 있다.

class Person :
    
    def __init__(self, name : str, age : int) :
        self.__name = name
        self.__age = age

    def get_name(self) :        # getter of name
        return self.__name
    
    def set_name(self, name) :  # setter of name
        if True != isinstance(name, str) :
            raise ValueError('name should be str type')
        self.__name = name
        
    def get_age(self) :         # getter of age
        return self.__age
    
    def set_age(self, age) :    # setter of age
        if True != isinstance(age, int) or 0 > age :
            raise ValueError('age should be int type and not be negative')
        self.__age = age
        

person = Person('Jason', '24')
print(person.get_name(), person.get_age()) # Jason 24

#person.name = 10
#person.age = -100

person.set_name(10)             # ValueError: name should be str type
person.set_age(-100)            # ValueError: age should be int type and not be negative

위 예제의 핵심은 name과 age 필드의 값을 저장 할 때 잘못된 값에 대해 getter와 setter로 방어 할 수 있다는 것이다. 여기까지는 어렵지 않은 내용이다.

property 클래스

class property(fget=None,fset=None, fdel=None, doc=None)

  • fget : 필드 값을 리턴하는 함수
  • fset : 필드 값을 셋팅하는 함수
  • fdel: 필드 값을 삭제하기 위한 함수
  • doc : 필드에 대한 문서 생성

위에서 getter와 setter에 대해서 살펴 보았다. getter와 setter를 사용하면 값을 저장하거나 읽을 때 유효성을 검증할 수 있어 좋긴한대, 필드에 직접 접근하는 대신 get_name, set_name과 같은 함수를 이용해야 하니 약간 번거롭게 느껴진다.

프로퍼티는 이런 getter, setter를 사용 할 때, 번거롭게 getter, setter들을 사용하는 대신, 필드를 직접 접근하는 것과 도일한 코드를 이용할 수 있도록 도와준다.

프로퍼티를 사용하면 getter와 setter를 일반 필드를 사용하는 것과 동일하게 사용할 수 있다.

Person 클래스를 아래와 같이 수정해 보았다.

class Person :
    
    def __init__(self, name : str, age : int) :
        self.__name = name
        self.__age = age

    def get_name(self) :
        return self.__name
    
    def set_name(self, name) :
        if True != isinstance(name, str) :
            raise ValueError('name should be str type')
        self.__name = name
        
    name = property(get_name, set_name) # name에 대한 property
    
    def get_age(self) :
        return self.__age
    
    def set_age(self, age) :
        if True != isinstance(age, int) or 0 > age :
            raise ValueError('age should be int type and not be negative')
        self.__age = age
        
    age = property(get_age, set_age)    # age에 대한 property
        

person = Person('Jason', '24')

# print(person.get_name(), person.get_age()) 
print(person.name, person.age)   # Jason 24

# person.set_name(10)
# person.set_age(-100)            
person.name = 10                 # ValueError: name should be str type
person.age = -100                # ValueError: age should be int type and not be negative

15라인과 25라인에 프로퍼티를 사용한 것을 주목하자. property의 첫번째 인자로 필드의 값을 리턴하는 getter 함수를, 두번째 인자로 setter 함수를 넘겨 주었고, 각각 name과 age라는 property 객체를 만들어 저장했다.

35, 36라인에서 볼수 있듯이 위 예제에서는 getter, setter 함수가 아닌, 일반 필드에 접근하는 것과 같이 name과 age를 사용했지만 내부적으로 set_name, set_age를 통해 값에 대한 유효성 검증을 하는 것을 볼 수 있다.

반응형

만일 getter가 필요 없거나, setter가 필요 없는 경우에는 아래 처럼 프로퍼티 객체 생성시 인자를 조절해주면 된다.

name = property(get_name, set_name) # getter와 setter 둘다 필요한 경우
name = property(get_name)           # getter만 필요한 경우
name = property(None, set_name)     # setter만 필요한 경우
property는 getter/setter를 필드처럼 접근하는 기술

@property 데코레이터

property 클래스를 이용해 getter, setter를 일반 필드 처럼 사용 할 수 있다는 것을 앞에서 살펴 보았다. 지금 부터는 데코레이터를 이용하는 방법에 대해 살펴보도록 한다. 기능은 property 클래스와 동일하지만 데코레이터를 이용하면 데코레이터 자체가 함수의 주석과 같은 역할을 하여 코드의 가독성이 좀더 높아지게 된다. 하지만 이 부분은 개인적 취향에 따라 다르므로 그냥 데코레이터를 이용하는 방법도 있다는 것에 주목하자.

class Person :
    
    def __init__(self, name : str, age : int) :
        self.__name = name
        self.__age = age

    @property
    def name(self) :
        return self.__name
    
    @name.setter
    def name(self, name) :
        if True != isinstance(name, str) :
            raise ValueError('name should be str type')
        self.__name = name
       
    @property
    def age(self) :
        return self.__age
    
    @age.setter
    def age(self, age) :
        if True != isinstance(age, int) or 0 > age :
            raise ValueError('age should be int type and not be negative')
        self.__age = age
        
person = Person('Jason', '24')

print(person.name, person.age)   # Jason 24

person.name = 10                 # ValueError: name should be str type
person.age = -100                # ValueError: age should be int type and not be negative

코드에 몇 가지 변화가 있었다. 우선 클래스 내부에 property 클래스를 통해 만들어진 name과 age 필드가 없어지고, 대신 getter, setter 함수의 이름이 각각 name과 age로 변경 되었다.

7라인과 17라인을 보면 getter라는 것을 표시하기 위해 함수 앞에 @property 데코레이터를 추가했다. @property 데코레이터가 선언된 함수는 getter로 사용된다. 그리고 11라인과 21라인에서 setter를 사용하기 위해서는 '@필드이름.setter'라는 데코레이터를 선언해준다.

위와 같이 데코레이터를 각 getter/setter 함수에 적용 하면 바로 이전에 살펴 보았던 property 클래스를 이용한 getter/setter와 동일한 효과를 얻을 수 있다.

마치며

이상으로 getter/setter를 일반 필드 처럼 사용 할 수 있게 해주는 property에 대해 알아 보았다. 이번 장에서는 사용법을 익히는데 집중하도록하고 자세한 원리는 뒤에 추가로 다루도록 하겠다. 다음 장 [Python] 클래스(class) #5 special method 에서는 파이썬 클래스의 스페셜 메소드에 대해 살펴 보도록 하겠다.

부록 1. 같이 보면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!