@property装饰器如何工作?

时间:2013-06-26 20:47:15

标签: python properties decorator python-decorators python-internals

我想了解内置函数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装饰器是如何创建的? 我很困惑。

15 个答案:

答案 0 :(得分:851)

property()函数返回特殊descriptor object

>>> 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函数替换为修饰方法。

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

首先,我们使用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对象充当descriptor object,因此它有.__get__().__set__().__delete__()方法挂钩到实例属性获取,设置和删除:

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

描述符Howto包含property()类型的pure python sample implementation

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

答案 1 :(得分:135)

Documentation says它只是创建只读属性的快捷方式。所以

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

相当于

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

答案 2 :(得分:77)

以下是@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'

答案 3 :(得分:75)

第一部分很简单:

@property
def x(self): ...

相同
def x(self): ...
x = property(x)
  • 反过来又是使用getter创建property的简化语法。

下一步是使用setter和deleter扩展此属性。这种情况会发生在适当的方法中:

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

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

x.deleter的工作方式相同。

答案 4 :(得分:36)

以下内容:

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

答案 5 :(得分:22)

下面是另一个示例,说明@property如何在必须重构从here提取的代码时提供帮助(我仅在下面进行总结):

想象一下,您像这样创建了一个类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类的人都必须更改使用dollarscents的所有代码行,这可能会非常痛苦。 ,如何避免这种情况?通过使用@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.

答案 6 :(得分:14)

我在这里阅读了所有帖子并意识到我们可能需要一个真实的例子,为什么,实际上,我们有@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)

在此代码中,我们通过使用password“隐藏”属性@property,当您尝试直接访问它时触发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表单不同的主题)。

我希望这个例子很有用

答案 7 :(得分:9)

很多人已经清除了这一点,但这是我一直在寻找的直接点。 我觉得从@property装饰器开始很重要。 例如:-

t enter, doesn

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

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

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

答案 8 :(得分:7)

让我们从Python装饰器开始。

Python装饰器是一个函数,可以帮助向已经定义的函数添加一些其他功能。

在Python中,一切都是对象,在Python中,一切都是对象。 Python中的函数是一流的对象,这意味着它们可以被变量引用,添加到列表中,作为参数传递给另一个函数等。

请考虑以下代码段。

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

在这里,我们可以说装饰器函数修改了say_hello函数并在其中添加了一些额外的代码行。

装饰器的Python语法

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

让我们总结完所有案例,而不是案例,但是在此之前,让我们先讨论一些糟糕的原则。

在许多面向对象的编程语言中使用字母和设置器来确保数据封装的原理(被视为将数据与对这些数据进行操作的方法捆绑在一起)。

这些方法当然是用于获取数据的吸气剂和用于更改数据的设置器。

根据此原理,将类的属性设为私有,以隐藏它们并保护它们免受其他代码的攻击。<​​/ p>

是的, @property 基本上是使用getter和setter的 pythonic方式。

Python有一个伟大的概念,称为属性,它使面向对象的程序员的生活变得更加简单。

让我们假设您决定做一个可以存储摄氏温度的课程。

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

重构代码,这是我们可以通过属性实现的方法。

在Python中,property()是一个内置函数,用于创建并返回属性对象。

属性对象具有三种方法,getter(),setter()和delete()。

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

在这里

temperature = property(get_temperature,set_temperature)

可能已被细分为

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

注意点:

  • get_temperature仍然是属性而不是方法。

现在您可以通过写入访问温度值。

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

我们可以继续,不要定义名称​​ get_temperature set_temperature ,因为它们是不必要的,并污染了类名称空间。

处理上述问题的 pythonic方法是使用 @property

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

注意点-

  1. 用于获取值的方法以“ @property”修饰。
  2. 必须用作设置器的方法用“ @ temperature.setter”修饰,如果该函数被称为“ x”,则必须用“ @ x.setter”修饰。
  3. 我们用相同的名称和不同数量的参数“ def temperature(self)”和“ def temperature(self,x)”编写了“两个”方法。

如您所见,代码肯定不太优雅。

现在,让我们谈谈一个现实的实用场景。

假设您设计了如下类:

class OurClass:

    def __init__(self, a):
        self.x = a


y = OurClass(10)
print(y.x)

现在,让我们进一步假设我们的类在客户中很受欢迎,并且他们开始在程序中使用它,他们对对象进行了各种分配。

有朝一日,一个值得信赖的客户来找我们,建议“ x”的值必须在0到1000之间,这确实是一个可怕的情况!

由于属性的原因,这很容易:我们创建了属性版本“ x”。

class OurClass:

    def __init__(self,x):
        self.x = x

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

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

这很棒,不是吗:您可以从可以想到的最简单的实现开始,并且以后可以随意迁移到属性版本,而不必更改接口!因此,属性不仅仅是吸气剂和塞特剂的替代品!

您可以检查此实现here

答案 9 :(得分:3)

最好的解释可以在这里找到: Python @Property解释–如何使用和何时使用? (完整示例) Selva Prabhakaran |发表于2018年11月5日

它帮助我不仅理解为什么。

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

答案 10 :(得分:1)

可以通过两种方式声明属性。

  • 为属性创建getter,setter方法,然后将这些方法作为参数传递给 property function
  • 使用 @property 装饰器。

您可以查看我撰写的有关properties in python的一些示例。

答案 11 :(得分:0)

这里是另一个示例:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

基本上,与C(object)示例相同,只不过我使用的是 x ...我也没有在__init 中初始化-...好吧。可以删除它,因为__x被定义为该类的一部分。...

输出为:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

如果我在 init 中注释掉self.x = 1234,则输出为:

[ Test Class ] Get x = None
[ x ] None

,如果我在getter函数中将_default = None设置为_default = 0(因为所有getter都应具有默认值,但不会被我所看到的属性值传入,因此可以定义它在这里,它实际上还不错,因为您可以定义一次默认值并在所有地方使用它),即:def x(self,_default = 0):

[ Test Class ] Get x = 0
[ x ] 0

注意:这里的getter逻辑只是为了控制它的值,以确保它被它操纵-与打印语句相同...

注意:我习惯了Lua,并且在调用单个函数时能够动态创建10个以上的助手,并且我在不使用属性的情况下为Python做了类似的事情,并且在一定程度上可以正常工作,但是,即使这些函数是在使用之前创建,有时仍然存在一些问题,因为在创建之前会被调用,这很奇怪,因为它不是以这种方式编码的。我更喜欢Lua元表的灵活性,而且我可以使用实际的setters / getters而不是本质上直接访问变量...我确实喜欢用Python可以快速构建某些东西-例如gui程序。尽管没有大量其他库,尽管我正在设计的库可能无法实现-如果我在AutoHotkey中对其进行编码,则可以直接访问所需的dll调用,并且可以在Java,C#,C ++等中完成同样的操作-也许我尚未找到正确的方法,但对于该项目,我可能会从Python切换到此。

注意:此论坛中的代码输出已损坏-我必须在代码的第一部分添加空格才能使其正常工作-复制/粘贴时,请确保将所有空格都转换为制表符。... Python,因为在一个10,000行的文件中,文件大小可以为512KB至1MB(带空格)和100至200KB(带制表符),这在文件大小和减少处理时间方面存在巨大差异...

制表符也可以针对每个用户进行调整-因此,如果您希望使用2个空格宽度,4个,8个或其他任意宽度,这对于有视力缺陷的开发人员来说是很周到的。

注意:由于论坛软件中的错误,该类中定义的所有功能均未正确缩进-请确保在复制/粘贴时将其缩进

答案 12 :(得分:0)

property@property装饰器后面的类。

您始终可以检查以下内容:

print(property) #<class 'property'>

我改写了help(property)中的示例,以显示@property语法

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

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

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

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

c = C()
c.x="a"
print(c.x)

在功能上与property()语法相同:

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

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

如您所见,我们使用该属性的方式没有什么不同。

要回答问题,{​​{1}}装饰器是通过@property类实现的。


因此,问题是要对property类进行一些解释。 这行:

property

进行初始化。我们可以这样重写它:

prop = property(g,s,d)

prop = property(fget=g,fset=s,fdel=d) fgetfset的含义:

fdel

下一张图片显示了 | fget | function to be used for getting an attribute value | fset | function to be used for setting an attribute value | fdel | function to be used for del'ing an attribute | doc | docstring 类中的三胞胎:

enter image description here

property__get____set__将有overridden。这是Python中描述符模式的实现。

  

通常,描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖。

我们还可以使用属性__delete__settergetter方法将函数绑定到属性。检查下一个示例。类deleter的方法s2将属性设置为 doubled

C

答案 13 :(得分:0)

Decorator是一个将函数作为参数并返回闭包的函数。闭包是一组内部函数和自由变量。内部函数正在关闭自由变量,这就是为什么它被称为“关闭”。自由变量是在内部函数外部并通过docorator传入内部的变量。

顾名思义,装饰器正在装饰接收到的函数。

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

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

   undecorated_function= decorator(undecorated_func) 

现在内部函数称为“ undecorated_func”。由于inner现在被命名为“ undecorated_func”,我们将“ undecorated_func”传递给装饰器,然后返回“ undecorated_func”并打印出“我在内部”。因此,此打印语句装饰了我们的“ undecorated_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) 

如上所述,decorator返回内部函数,并使用传递的函数名称来命名内部函数。

这是需要注意的重要事项。 “名称”是不可变的。在第一个装饰中,我们得到了这个:

  name=property(name)

在第二个中我们得到了这个

  name=name.setter(name)

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

答案 14 :(得分:-2)

一句话: 对我来说,对于Python 2.x,当我不继承自@property时,object并没有像宣传的那样工作:

class A():
    pass

但在以下情况下有效:

class A(object):
    pass

对于Python 3,始终有效。