动态更新依赖于同一对象的其他属性状态的对象的属性

nam*_*ked 9 python

假设我有一个看起来像这样的类:

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.aself.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.cself.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.aself.b.

@property是什么意思?

@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_decoratorwrapped在你的函数调用中!

所有这些都是通过称为闭包的东西来实现的.当函数以这种方式传递给装饰器时(例如property(c)),函数的名称被重新绑定到函数的包装版本而不是原始函数,并且原始函数的参数总是传递给wrapped原始函数而不是原始函数.这就是闭包的工作方式,并没有什么神奇之处.以下是有关闭包的更多信息.

所以总结到目前为止:@property只是一种将类方法包装在property()函数内部的方法,因此调用包装的类方法而不是原始的,未解包的类方法.但是属性功能是什么?它有什么作用?

属性函数向类中添加一个称为描述符的东西.简而言之,描述符是一个对象类,可以有单独的get,set和delete方法.当你这样做:

@property
def c(self):
    return self._c
Run Code Online (Sandbox Code Playgroud)

...您正在向Test被调用的类添加一个描述符,并将描述符cget方法(实际上__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,那么你应该花时间自己研究这些主题.


jon*_*rpe 6

最简单的解决方案是使其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)