我想了解内置函数属性是如何工作的。让我困惑的是,属性也可以用作装饰器,但它只在用作内置函数时接受参数,而在用作装饰器时不接受参数。

下面的例子来自文档:

class C:
    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.")

属性的参数是getx, setx, delx和一个doc字符串。

在下面的代码中,属性被用作装饰器。它的对象是x函数,但在上面的代码中,参数中没有对象函数的位置。

class C:
    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.setter和x.deleter装饰器?


当前回答

我读了这里所有的帖子,意识到我们可能需要一个现实生活中的例子。为什么要用@property? 因此,考虑一个使用身份验证系统的Flask应用程序。 在models.py中声明一个模型User:

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来“隐藏”属性密码,当你试图直接访问它时,它会触发AttributeError断言,而我们使用@property。Setter设置实际的实例变量password_hash。

现在在auth/views.py中,我们可以实例化一个User:

...
@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()
...

注意用户填写注册表单时来自注册表单的属性密码。密码确认发生在EqualTo的前端(' Password ', message='密码必须匹配')(如果你想知道,但这是一个与Flask表单相关的不同主题)。

我希望这个例子对大家有用

其他回答

最好的解释在这里: Python @属性解释-如何使用和何时?(例子) 作者:塞尔瓦·普拉巴卡兰|发表于2018年11月5日

它帮助我理解为什么,而不仅仅是如何。

https://www.machinelearningplus.com/python/python-property/

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被替换为属性(foo),上面我们看到的是一个特殊的对象。然后,当您使用@foo.setter()时,您所做的就是调用该属性()。setter方法,它返回属性的一个新副本,但这一次setter函数被修饰过的方法取代。

下面的序列还通过使用这些装饰器方法创建了一个完整的属性。

首先,我们创建一些函数和一个只有getter的属性对象:

>>> 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

最后,属性对象作为一个描述符对象,所以它有.__get__(), .__set__()和.__delete__()方法来挂钩到实例属性的获取,设置和删除:

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

描述符Howto包含属性()类型的纯Python示例实现:

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装饰器开始很重要的一点。 如:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

函数“get_config()”的调用将像这样工作。

util = UtilityMixin()
print(util.get_config)

如果您注意到,我没有使用“()”括号来调用函数。这是我搜索@property装饰器的基本内容。这样你就可以像使用变量一样使用函数。

下面是@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如何在重构代码时提供帮助的另一个例子(我只在下面总结):

假设你像这样创建了一个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类,去掉美元和美分属性,而是决定只跟踪美分的总数:

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类的人都必须更改所有使用美元和美分的代码行,这可能是非常痛苦的……那么,如何避免这种情况呢?通过使用@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;这里我举一个最小的例子。