[python] @property 데코레이터는 어떻게 작동합니까?

내장 함수의 property작동 방식을 이해하고 싶습니다 . 나를 혼란스럽게 property하는 것은 데코레이터로도 사용할 수 있지만 데코레이터로 사용될 때가 아니라 내장 함수로 사용될 때만 인수를 취한다는 것입니다.

이 예제는 문서 에서 가져온 것입니다 .

class C(object):
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

property의 인수는 getx, setx, delx및 문서화 문자열.

아래 코드 property에서 데코레이터로 사용됩니다. 그 객체는 x함수이지만 위의 코드에는 인수에 객체 함수를위한 장소가 없습니다.

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

그리고, 어떻게되어 x.setterx.deleter장식 만들어? 혼란 스러워요.



답변

property()함수는 특수 디스크립터 객체를 반환 합니다 .

>>> property()
<property object at 0x10ff07940>

추가 메소드 가있는 것은이 오브젝트입니다 .

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

이것들은 데코레이터로 도 사용 됩니다. 그들은 새로운 속성 객체를 반환합니다 :

>>> property().getter(None)
<property object at 0x10ff079f0>

그것은 이전 객체의 사본이지만 기능 중 하나가 교체되었습니다.

@decorator구문은 단지 구문 설탕 이라는 것을 기억하십시오 . 문법 :

@property
def foo(self): return self._foo

정말 같은 것을 의미합니다

def foo(self): return self._foo
foo = property(foo)

그래서 foo함수가로 대체 property(foo)우리가 위에서 본 특별한 목적이다. 그런 다음을 사용할 때 @foo.setter()하는 일은property().setter 하면 위에서 보여준 메소드를 하여 속성의 새 사본을 반환하지만 이번에는 setter 함수가 장식 된 메소드로 대체되었습니다.

다음 순서는 또한 데코레이터 메소드를 사용하여 완전한 특성을 작성합니다.

먼저 propertygetter를 사용하여 함수와 객체를 만듭니다 .

>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

다음으로 .setter()메소드를 사용하여 세터를 추가합니다.

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

마지막으로 .deleter()메소드 와 함께 삭제기를 추가합니다 .

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

마지막으로는 property객체는 역할을 설명 객체 가있다, 그래서 .__get__(), .__set__().__delete__()방법 설정 및 삭제, 점점 인스턴스 속성에 후크 :

>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

Descriptor Howto에는 다음 유형 의 순수 Python 샘플 구현 이 포함되어 있습니다 property().

class Property:
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

답변

문서에 따르면 읽기 전용 속성을 만드는 바로 가기 일뿐입니다. 그래서

@property
def x(self):
    return self._x

에 해당

def getx(self):
    return self._x
x = property(getx)


답변

다음은 @property구현 방법의 최소 예입니다 .

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

그렇지 않으면 word속성 대신 메서드가 유지됩니다.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'


답변

첫 번째 부분은 간단합니다.

@property
def x(self): ...

와 같다

def x(self): ...
x = property(x)
  • 이는 property게터만으로 를 만드는 간단한 구문입니다 .

다음 단계는이 속성을 setter와 deleter로 확장하는 것입니다. 그리고 이것은 적절한 방법으로 발생합니다 :

@x.setter
def x(self, value): ...

x주어진 setter와 old의 모든 것을 상속받는 새로운 속성을 반환합니다 .

x.deleter 같은 방식으로 작동합니다.


답변

이것은 다음과 같습니다.

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

와 같다:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del,
                    "I'm the 'x' property.")

와 같다:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

와 같다:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

다음과 같습니다.

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x


답변

아래는 여기@property 에서 가져온 코드를 리팩터링해야 할 때 도움이 될 수있는 또 다른 예입니다 (아래 에서 간단히 요약합니다).

다음 Money과 같이 클래스를 생성했다고 가정 하십시오.

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

사용자는이 클래스에 따라 라이브러리를 생성합니다. 예 :

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

이제 Money클래스 를 변경 하고 dollarscents속성을 제거 하기로 결정하고 대신 총 센트 양만 추적하기로 결정한다고 가정 해 봅시다 .

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

위에서 언급 한 사용자가 이전과 같이 라이브러리를 실행하려고하면

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

오류가 발생합니다

AttributeError : ‘Money’오브젝트에 ‘dollars’속성이 없습니다.

지금 당신의 원본에 의존하는 모든 사람 즉, Money클래스는 모든 코드 라인을 변경해야 dollars하고 cents매우 고통 스러울 수 사용은 … 그래서, 어떻게 피할 수 있을까? 사용하여 @property!

그 방법입니다 :

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

우리가 지금 도서관에서 전화 할 때

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

예상대로 작동하며 라이브러리에서 한 줄의 코드를 변경할 필요가 없었습니다! 실제로 우리가 의존하는 라이브러리가 변경되었음을 알 필요조차 없습니다.

또한 setter잘 작동합니다.

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

@property추상 클래스에서도 사용할 수 있습니다 . 여기에 최소한의 예가 있습니다 .


답변

나는 모든 게시물을 읽었으며 실제 사례가 필요할 수 있음을 깨달았습니다. 실제로 @property가있는 이유는 무엇입니까? 따라서 인증 시스템을 사용하는 Flask 앱을 ​​고려하십시오. 다음에서 모델 사용자를 선언합니다 models.py.

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

이 코드에서는 @ property.setter를 사용하여 실제 인스턴스 변수를 설정하는 동안 직접 트리거 하려고 할 때 어떤 트리거 어설 션 password을 사용하여 “숨겨진”속성 을 사용 했습니다 .@propertyAttributeErrorpassword_hash

이제 다음을 사용 auth/views.py하여 사용자를 인스턴스화 할 수 있습니다.

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

password사용자가 양식을 채울 때 등록 양식에서 나오는 알림 속성 입니다. 비밀번호 확인은 프론트 엔드에서 이루어집니다 EqualTo('password', message='Passwords must match')(궁금한 점이 있지만 Flask 양식과 다른 주제 일 경우).

이 예제가 도움이 되길 바랍니다.