Logo

[파이썬] property 사용법 (함수/데코레이터)

파이썬에 내장되어 있는 property() 함수와 @property 데코레이터에 대해서 알아보겠습니다.

필드명

사람의 이름, 성, 나이 데이터를 담기 위한 간단한 클래스를 하나 작성해보겠습니다. 아래 Person 클래스는 이름 first_name, last_name, age 이렇게 3개의 필드로 이루어져 있습니다.

class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

Person 클래스의 인스턴스를 생성 후에, 현재 필드 값을 읽거나 새로운 필드 값 쓰는 것은 매우 자유롭습니다.

>>> person = Person("John", "Doe", 20)
>>> person.age
20
>>> person.age = person.age + 1
>>> person.age
21

이렇게 필드명을 사용해서 객체의 내부 데이터에 접근하는 것은 편리하지만, 해당 데이터는 외부로 부터 무방비 상태에 놓이게 됩니다.

Getter/Setter

클래스 인스턴스의 내부 데이터를 보호하기 위해서 데이터의 접근용 메서드를 작성하는 것은 객체 지향 프로그래밍에서 흔히 볼 수 있는 패턴입니다. 일반적으로 데이터를 읽어주는 메서드를 getter(게터), 데이터를 변경해주는 메서드를 setter(세터)라고 합니다.

Person 클래스에 age 필드에 대한 get_age()set_age() 메서드를 추가해보겠습니다. 나이는 음수가 될 수 없으므로 set_age() 메서드에 음수가 인자로 넘어오면 예외가 발생하도록 하였습니다.

class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.set_age(age)

    def get_age(self):
        return self._age

    def set_age(self, age):
        if age < 0:
            raise ValueError("Invalid age")
        self._age = age

여기서 놓치기 쉬운 부분은 _로 시작하는 이름을 가진 변수는 외부에서 직접 접근하지 않는 파이썬의 관행에 따라, 인스턴스 변수명을 age 대신에 _age로 변경하였다는 것입니다.

>>> person = Person("John", "Doe", 20)
>>> person.get_age()
20
>>> person.set_age(-1)
ValueError: Invalid age
>>> person.set_age(person.get_age() + 1)
>>> person.get_age()
21

이제 Person 클래스의 인스턴스에 저장되어 있는 나이 데이터에 접근하거나 변경하려면 메서드를 이용해야 합니다.

getter/setter 메서드를 통해서 객체의 내부 데이터에 대한 접근을 좀 더 통제할 수 있게되었지만 기존에 필드명을 바로 사용할 때 보다는 코드가 조금 지저분해졌습니다. 뿐만 아니라, Person 클래스의 프로그래밍 인터페이스가 변경됨에 따라 하위 호환성도 깨지게 된다는 큰 단점이 있습니다.

property() 함수

파이썬의 내장 함수인 property()를 사용하면 마치 필드명을 사용하는 것처럼 깔끔하게 getter/setter 메서드가 호출되게 할 수 있습니다.

class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    def get_age(self):
        return self._age

    def set_age(self, age):
        if age < 0:
            raise ValueError("Invalid age")
        self._age = age

    age = property(get_age, set_age)

property() 함수의 첫 번째 인자로 getter 메서드를 두 번째 인자로 setter 메서드를 넘겨주면 age라는 필드명을 이용해서 다시 나이 데이터에 접근할 수 있게 됩니다.

>>> person = Person("John", "Doe", 20)
>>> person.age
20
>>> person.age = -1
ValueError: Invalid age
>>> person.age = person.age + 1
>>> person.age
21

클래스를 사용하는 측면에서는 일반 필드에 접근하는 것처럼 보이지만 내부적으로 getter와 setter 메서드가 호출이 됩니다. 따라서 나이를 음수로 변경하려고 하면 set_age() 메서드를 직접 호출하는 것과 동일하게 예외가 발생하는 것입니다.

@property 데코레이터

파이썬의 내장 데코레이터인 @property를 사용하면 위와 동일하게 작동하는 코드를 좀 더 간결하고 읽기 편하게 작성할 수 있습니다.

class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        if age < 0:
            raise ValueError("Invalid age")
        self._age = age

기존의 getter 메서드 위에 @property 데코레이터를 선언하고, 메서드 이름으로부터 get_을 삭제합니다. setter 메서드의 경우에는 @<필드명>.setter 데코레이터를 선언하고, 메서드 이름으로부터 set_을 삭제합니다.

>>> person = Person("John", "Doe", 20)
>>> person.age
20
>>> person.age = -1
ValueError: Invalid age
>>> person.age = person.age + 1
>>> person.age
21

property 함수나 @property 데코레이터를 사용했을 때 가장 큰 이점은 외부에 티 내지 않고 내부적으로 클래스의 필드 접근 방법을 바꿀 수 있다는 것입니다. Person 클래스를 사용하는 관점에서 봤을 때 나이 데이터는 항상 age라는 필드명으로 접근하고 변경할 수 있다는 사실은 변하지 않기 때문입니다.

@property 데코레이터 활용

클래스를 작성하다보면 다른 필드로 부터 값이 유추되는 읽기 전용 필드가 필요할 때가 있습니다. 예를 들어, Person 클래스에서 전체 이름을 얻고 싶다면 다음과 같이 @property 데코레이터를 이용해서 full_name 필드를 추가해줄 수 있습니다.

class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    @property
    def full_name(self):
        return self.first_name + " " + self.last_name
>>> person = Person("John", "Doe", 20)
>>> person.full_name
'John Doe'

마치면서

이상으로 파이썬에 내장되어 있는 property() 함수와 @property 데코레이터에 대해서 살펴보았습니다. 본 포스팅에서는 다루지는 않았지만 이 방법을 통해 setter와 getter 메서드 뿐만 아니라 deleter 메서드도 함께 선언할 수 있습니다. property() 함수와 @property 데코레이터에 대한 좀 더 자세한 내용은 파이썬 공식 문서를 참고 바랍니다.