How does the @property decorator work?
我想了解一下内置函数
此示例来自文档:
1 2 3 4 5 6 7 8 9 10 11 | 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.") |
在下面的代码中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
还有,
1 2 | >>> property() <property object at 0x10ff07940> |
正是这个对象有额外的方法:
1 2 3 4 5 6 | >>> 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> |
它们也起装饰作用。它们返回一个新的属性对象:
1 2 | >>> property().getter(None) <property object at 0x10ff079f0> |
这是旧对象的副本,但替换了其中一个函数。
记住,
1 2 | @property def foo(self): return self._foo |
真正的意义和
1 2 | def foo(self): return self._foo foo = property(foo) |
因此,
下面的序列还使用这些修饰器方法创建了一个完全打开属性。
首先,我们创建一些函数和一个只有getter的
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> 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 |
接下来,我们使用
1 2 3 4 5 6 7 | >>> prop = prop.setter(setter) >>> prop.fget is getter True >>> prop.fset is setter True >>> prop.fdel is None True |
最后我们用
1 2 3 4 5 6 7 | >>> prop = prop.deleter(deleter) >>> prop.fget is getter True >>> prop.fset is setter True >>> prop.fdel is deleter True |
最后但并非最不重要的是,
1 2 3 4 5 6 7 8 | >>> class Foo(object): pass ... >>> prop.__get__(Foo(), Foo) Get! >>> prop.__set__(Foo(), 'bar') Set to 'bar'! >>> prop.__delete__(Foo()) Delete! |
描述符howto包含
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 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 2 3 | @property def x(self): return self._x |
等于
1 2 3 | def getx(self): return self._x x = property(getx) |
第一部分很简单:
1 2 | @property def x(self): ... |
是一样的
1 2 | def x(self): ... x = property(x) |
- 反过来,它是只使用getter创建
property 的简化语法。
下一步是使用setter和deleter扩展此属性。这是通过适当的方法实现的:
1 2 | @x.setter def x(self, value): ... |
返回一个新属性,该属性继承旧
下面是如何实现
1 2 3 4 5 6 7 8 9 | class Thing: def __init__(self, my_word): self._word = my_word @property def word(self): return self._word >>> print( Thing('ok').word ) 'ok' |
否则,
1 2 3 4 5 6 7 8 | class Thing: def __init__(self, my_word): self._word = my_word def word(self): return self._word >>> print( Thing('ok').word() ) 'ok' |
以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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.") |
相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) |
相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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) |
同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
下面是另一个示例,说明当必须重构从这里提取的代码时,
假设您创建了一个类
1 2 3 4 | class Money: def __init__(self, dollars, cents): self.dollars = dollars self.cents = cents |
用户根据他/她使用的这个类创建一个库,例如
1 2 3 4 | money = Money(27, 12) print("I have {} dollar and {} cents.".format(money.dollars, money.cents)) # prints I have 27 dollar and 12 cents. |
现在让我们假设您决定更改
1 2 3 | class Money: def __init__(self, dollars, cents): self.total_cents = dollars * 100 + cents |
如果上述用户现在试图像以前一样运行其库
1 2 3 | money = Money(27, 12) print("I have {} dollar and {} cents.".format(money.dollars, money.cents)) |
它会导致一个错误
AttributeError: 'Money' object has no attribute 'dollars'
这意味着现在每个依赖于您原来的
就是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
当我们现在从图书馆打电话时
1 2 3 4 | money = Money(27, 12) print("I have {} dollar and {} cents.".format(money.dollars, money.cents)) # prints I have 27 dollar and 12 cents. |
它将如预期的那样工作,我们不需要在我们的库中更改一行代码!事实上,我们甚至不必知道我们所依赖的图书馆发生了变化。
同时,
1 2 3 4 5 6 7 | 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?所以,考虑使用认证系统的flask应用程序。您在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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) |
在这段代码中,我们使用
现在在
1 2 3 4 5 6 7 8 9 10 11 | ... @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() ... |
注意,属性
我希望这个例子有用
这一点已被许多人澄清,但这里有一个直接点,我正在搜索。我觉得从@property decorator开始这一点很重要。例如:
1 2 3 4 | class UtilityMixin(): @property def get_config(self): return"This is property" |
函数"get_config()"的调用将像这样工作。
1 2 | util = UtilityMixin() print(util.get_config) |
如果您注意到我没有使用"()"括号来调用函数。这是我搜索@property decorator的基本内容。这样就可以像变量一样使用函数。
让我们从Python装饰器开始。
python修饰器是一个帮助向已经定义的函数添加一些附加功能的函数。
在python中,一切都是一个对象,在python中,一切都是一个对象。python中的函数是第一类对象,这意味着它们可以被变量引用、添加到列表中、作为参数传递给另一个函数等。
考虑下面的代码段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
在这里,我们可以说decorator函数修改了say ou hello函数并在其中添加了一些额外的代码行。
decorator的python语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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() |
让我们总结所有的事情,而不是一个案例场景,但在此之前,让我们来谈谈一些OOPS的Priniciples。
许多面向对象编程语言中都使用getter和setter来确保数据封装的原则(被视为数据与对这些数据进行操作的方法的组合)。
这些方法当然是检索数据的getter和更改数据的setter。
根据这个原则,类的属性被设置为私有的,以隐藏和保护它们不受其他代码的影响。
是的,@property基本上是一种使用getter和setter的Python式方法。
python有一个伟大的概念,叫做属性,它使面向对象的程序员的生活简单得多。
让我们假设您决定创建一个以摄氏度为单位存储温度的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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) |
在这里,
1 | temperature = property(get_temperature,set_temperature) |
可能被分解为,
1 2 3 4 5 6 | # make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature) |
注意事项:
- get_temperature保持属性而不是方法。
现在你可以通过写来获取温度值。
1 2 3 | C = Celsius() C.temperature # instead of writing C.get_temperature() |
我们可以继续,而不定义名称get-temperature和set-temperature,因为它们是不必要的,会污染类名称空间。
解决上述问题的方法是使用@property。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 2 3 4 5 6 7 8 | class OurClass: def __init__(self, a): self.x = a y = OurClass(10) print(y.x) |
现在,让我们进一步假设我们的类在客户中变得流行,并且他们开始在他们的程序中使用它,他们对对象做了各种分配。
有一天,一位值得信赖的客户来到我们这里,建议"x"的值必须在0到1000之间,这真是一个可怕的场景!
由于属性的原因,这很容易:我们创建了一个属性版本"x"。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
这太好了,不是吗:您可以从最简单的实现开始,然后您可以自由地迁移到属性版本,而无需更改接口!所以属性不仅仅是getter和setter的替代品!
您可以在这里检查这个实现
属性可以用两种方式声明。
- 为属性创建getter、setter方法,然后将这些方法作为参数传递给属性函数
- 使用@property decorator。
您可以看看我在python中编写的几个关于属性的示例。
下面是另一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | ## ## 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 ' ' ## ## ## _test = GetterSetterExample( ) print( _test ) ## For some reason the deleter isn't being called... del _test.x |
基本上,与c(object)示例相同,只是我使用x来代替…我也不会在初始化中初始化…好。。是的,但是它可以被删除,因为uux被定义为类的一部分……
输出是:
1 2 3 | [ Test Class ] Set x = 1234 [ Test Class ] Get x = -1234 [ x ] -1234 |
如果我在init中注释掉self.x=1234,那么输出是:
1 2 | [ Test Class ] Get x = None [ x ] None |
如果我在getter函数中将默认值=none设置为默认值=0(因为所有getter都应该有一个默认值,但它不是由我看到的属性值传入的,所以您可以在这里定义它,实际上这并不坏,因为您可以定义一次默认值并在任何地方使用它),即:def x(self,_default=0):
1 2 | [ Test Class ] Get x = 0 [ x ] 0 |
注意:getter逻辑只是让值被它操纵,以确保它被它操纵-对于print语句也是如此…
注意:我习惯于Lua,当我调用单个函数时可以动态地创建10多个助手,并且我在不使用属性的情况下为python创建了类似的东西,并且它在一定程度上起作用,但是,即使在使用之前已经创建了这些函数,但在创建之前调用它们时仍然存在一些问题,这就是范围,因为它没有以这种方式编码…我更喜欢Lua元表的灵活性,我可以使用实际的setter/getter,而不是直接访问变量……我很喜欢用python构建一些东西的速度,比如gui程序。虽然我设计一个不可能没有很多额外的图书馆-如果我在AutoHotkey编码,我可以直接访问我需要的DLL调用,同样可以在爪哇,C,C,和更多-也许我还没有找到正确的东西,但对于这个项目,我可以从Python切换。
注意:这个论坛中的代码输出是中断的-我必须在代码的第一部分添加空格,以便它工作-复制/粘贴时,请确保将所有空格转换为制表符…我在python中使用制表符,因为在一个10000行的文件中,文件大小可以是512KB到1MB(含空格),而制表符可以是100到200KB(含制表符),这相当于文件大小的巨大差异,并且减少了处理时间…
标签也可以调整每个用户-因此,如果你喜欢2个空间宽度,4, 8或任何你可以做的,这意味着它是有思想的开发有视力缺陷。
注意:在论坛中定义的所有函数不会因为论坛软件中的错误而被正确地缩进——如果您复制/粘贴,请确保您缩进它。
一句话:对于我来说,对于python 2.x,@property在我没有继承表单对象时没有像广告中那样工作:
1 2 | class A(): pass |
但工作时间:
1 2 | class A(object): pass |
对于Pyton 3,一直都在工作