python - @property装饰器如何工作?




python property装饰器 (5)

我想了解内置函数property如何工作。 令我困惑的是,该property也可以用作装饰器,但只有在作为内置函数使用时才会使用参数,而在用作装饰器时不会使用参数。

这个例子来自documentation

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的参数是getxsetxdelx和一个文档字符串。

在下面的代码中, 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装饰器是如何创建的? 我很困惑。


documentation这只是创建只读属性的快捷方式。 所以

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

相当于

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

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 @foo.setter()方法我向你展示了上面的内容,它返回属性的一个新副本,但这次用setter函数替换装饰方法。

以下序列还通过使用这些装饰器方法创建了一个全开属性。

首先我们用一个getter创建一些函数和一个property对象:

>>> 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()方法添加一个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(object): pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

Descriptor Howto包含property()类型的纯python示例实现

class Property(object):
    "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__)

以下内容:

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? 所以,考虑一下你使用认证系统的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来“隐藏”属性password ,当你尝试直接访问它时触发AttributeError声明,而我们使用@ property.setter来设置实际实例变量password_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表单不同的话题)。

我希望这个例子会有用


这是一个如何实现@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'






python-internals