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

下面的例子来自文档:

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

假设你像这样创建了一个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;这里我举一个最小的例子。

其他回答

在下面,我给出了一个例子来阐明@property

考虑一个名为Student的类,它有两个变量:name和class_number,并且希望class_number在1到5的范围内。

现在我将解释两个错误的解决方案,最后是正确的解决方案:


下面的代码是错误的,因为它没有验证class_number(在1到5的范围内)

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = class_number

尽管经过验证,但这个解决方案也是错误的:

def validate_class_number(number):
    if 1 <= number <= 5:
        return number
    else:
        raise Exception("class number should be in the range of 1 to 5")

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = validate_class_number(class_number)

因为class_number验证只在创建类实例时检查,在创建类实例后不检查(可以将class_number更改为1到5范围之外的数字):

student1 = Student("masoud",5)
student1.class_number = 7

正确的解决方法是:

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = class_number
        
    @property
    def class_number(self):
        return self._class_number

    @class_number.setter
    def class_number(self, class_number):
        if not (1 <= class_number <= 5): raise Exception("class number should be in the range of 1 to 5")
        self._class_number = class_number

文档说它只是创建只读属性的快捷方式。所以

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

等于

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

第一部分很简单:

@property
def x(self): ...

def x(self): ...
x = property(x)

反过来,这是用于仅使用getter创建属性的简化语法。

下一步是使用setter和delete扩展此属性。这需要适当的方法:

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

返回一个新属性,它继承了旧x加上给定setter的所有内容。

X.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被替换为属性(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__)

装饰器是一个函数,它接受一个函数作为参数并返回一个闭包。闭包是一组内部函数和自由变量。内部函数在自由变量上闭合,这就是为什么它被称为“闭包”。自由变量是内部函数外部的变量,通过修饰器传入内部函数。

顾名思义,decorator修饰接收到的函数。

function decorator(undecorated_func):
    print("calling decorator func")
    inner():
       print("I am inside inner")
       return undecorated_func
    return inner

这是一个简单的装饰函数。它接收到“unorated_func”并将其作为自由变量传递给inner(), inner()打印“I am inside inner”并返回unorated_func。当我们调用decorator(unorated_func)时,它将返回内部的。这里是关键,在decorator中,我们将内部函数命名为我们传递的函数的名称。

   undecorated_function= decorator(undecorated_func) 

现在内部函数被称为“unorated_func”。由于inner现在被命名为“unorated_func”,我们将“unorated_func”传递给装饰器,并返回“unorated_func”,并打印出“I am inside inner”。因此这个打印语句修饰了我们的“unorated_func”。

现在让我们定义一个带有属性装饰器的类:

class Person:
    def __init__(self,name):
        self._name=name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self.value):
        self._name=value

当我们用@property()修饰name()时,发生了这样的事情:

name=property(name) # Person.__dict__ you ll see name 

property()的第一个参数是getter。这是第二次装饰的情况:

   name=name.setter(name) 

如上所述,装饰器返回内部函数,我们用传递的函数名命名内部函数。

这里有一件重要的事情需要注意。name是不可变的。在第一个装饰中,我们得到了这个:

  name=property(name)

在第二个例子中,我们得到了这个

  name=name.setter(name)

我们没有修改名称obj。在第二个修饰中,python看到这是属性对象,并且它已经有getter。因此,python创建了一个新的“name”对象,从第一个obj中添加“fget”,然后设置“fset”。