我想了解内置函数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
的参数是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.setter
和x.deleter
装饰器是如何创建的?
我很困惑。
答案 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)
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
类并摆脱dollars
和cents
属性,而是决定仅跟踪总分:
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
和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.
答案 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)
注意点:
现在您可以通过写入访问温度值。
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
注意点-
如您所见,代码肯定不太优雅。
现在,让我们谈谈一个现实的实用场景。
假设您设计了如下类:
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日
它帮助我不仅理解为什么。
答案 10 :(得分:1)
可以通过两种方式声明属性。
您可以查看我撰写的有关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)
如您所见,我们使用该属性的方式没有什么不同。
@property
类实现的。因此,问题是要对property
类进行一些解释。
这行:
property
进行初始化。我们可以这样重写它:
prop = property(g,s,d)
prop = property(fget=g,fset=s,fdel=d)
,fget
和fset
的含义:
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
类中的三胞胎:
property
,__get__
和__set__
将有overridden。这是Python中描述符模式的实现。
通常,描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖。
我们还可以使用属性__delete__
,setter
和getter
方法将函数绑定到属性。检查下一个示例。类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,始终有效。