假设我有一个看起来像这样的类:
class Test(object):
def __init__(self, a, b):
self.a = a
self.b = b
self.c = self.a + self.b
Run Code Online (Sandbox Code Playgroud)
我希望self.c每当属性值self.a或self.b相同实例的更改时更改值.
例如
test1 = Test(2,4)
print test1.c # prints 6
test1.a = 3
print test1.c # prints = 6
Run Code Online (Sandbox Code Playgroud)
我知道为什么它仍会打印6,但有没有一种机制可以用来触发更新self.c时self.a更改.或者我唯一的选择是有一个方法,它返回self.c基于当前状态self.a和的值self.b
Ric*_*ica 25
就在这里!它被称为属性.
class Test(object):
def __init__(self,a,b):
self.a = a
self.b = b
@property
def c(self):
return self.a + self.b
Run Code Online (Sandbox Code Playgroud)
使用上面的代码,c现在是类的只读属性Test.
您还可以为属性提供一个setter,它将使其可读/写并允许您直接设置其值.它看起来像这样:
class Test(object):
def __init__(self, c = SomeDefaultValue):
self._c = SomeDefaultValue
@property
def c(self):
return self._c
@c.setter
def c(self,value):
self._c = value
Run Code Online (Sandbox Code Playgroud)
但是,在这种情况下,有一个setter是没有意义的self.c,因为它的值取决于self.a和self.b.
该@property位是一个称为装饰器的例子.装饰器实际上将它装饰的函数(或类)包装到另一个函数(装饰器函数)中.在函数被装饰之后,当它被调用时,它实际上是用函数(及其参数)作为参数调用的装饰器.通常(但不总是!)装饰函数做一些有趣的事情,然后像通常那样调用原始(装饰)函数.例如:
def my_decorator(thedecoratedfunction):
def wrapped(*allofthearguments):
print("This function has been decorated!") #something interesting
thedecoratedfunction(*allofthearguments) #calls the function as normal
return wrapped
@my_decorator
def myfunction(arg1, arg2):
pass
Run Code Online (Sandbox Code Playgroud)
这相当于:
def myfunction(arg1, arg2):
pass
myfunction = my_decorator(myfunction)
Run Code Online (Sandbox Code Playgroud)
所以这意味着在上面的类示例中,您可以执行以下操作,而不是使用装饰器:
def c(self):
return self.a + self.b
c = property(c)
Run Code Online (Sandbox Code Playgroud)
它们完全是一回事.这@property只是用语法myobject.cgetter和setter 替换调用的语法糖(删除器也是一个选项).
你可能想知道为什么只做这一次:
myfunction = my_decorator(myfunction)
Run Code Online (Sandbox Code Playgroud)
...导致如此剧烈的变化!所以,从现在开始,当打电话时:
myfunction(arg1, arg2)
Run Code Online (Sandbox Code Playgroud)
...你实际上正在呼叫my_decorator(myfunction),arg1, arg2并发送到内部的内部wrapped功能my_decorator.不仅如此,所有这一切都发生了,即使你甚至没有提及my_decorator或wrapped在你的函数调用中!
所有这些都是通过称为闭包的东西来实现的.当函数以这种方式传递给装饰器时(例如property(c)),函数的名称被重新绑定到函数的包装版本而不是原始函数,并且原始函数的参数总是传递给wrapped原始函数而不是原始函数.这就是闭包的工作方式,并没有什么神奇之处.以下是有关闭包的更多信息.
所以总结到目前为止:@property只是一种将类方法包装在property()函数内部的方法,因此调用包装的类方法而不是原始的,未解包的类方法.但是属性功能是什么?它有什么作用?
属性函数向类中添加一个称为描述符的东西.简而言之,描述符是一个对象类,可以有单独的get,set和delete方法.当你这样做:
@property
def c(self):
return self._c
Run Code Online (Sandbox Code Playgroud)
...您正在向Test被调用的类添加一个描述符,并将描述符c的get方法(实际上__get__())c定义为等于该c(self)方法.
当你这样做:
@c.setter
def c(self,value):
self._c
Run Code Online (Sandbox Code Playgroud)
...您将描述符的set方法(实际上__set__())c定义为等于c(self,value)方法.
只需添加@property到您的def c(self)方法即可完成大量的工作!在实践中,您可能不需要立即了解所有这些以开始使用它.但是,我建议记住,当你使用时@property,你正在使用装饰器,闭包和描述符,如果你非常认真地学习Python,那么你应该花时间自己研究这些主题.
最简单的解决方案是使其c成为只读property:
class Test(object):
def __init__(self, a, b):
self.a = a
self.b = b
@property
def c(self):
return self.a + self.b
Run Code Online (Sandbox Code Playgroud)
现在,每次访问时test_instance.c,它都会调用属性getter并从其他属性计算适当的值.正在使用:
>>> t = Test(2, 4)
>>> t.c
6
>>> t.a = 3
>>> t.c
7
Run Code Online (Sandbox Code Playgroud)
请注意,这意味着您无法c直接设置:
>>> t.c = 6
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
t.c = 6
AttributeError: can't set attribute
Run Code Online (Sandbox Code Playgroud)