覆盖python中的属性
Posted
技术标签:
【中文标题】覆盖python中的属性【英文标题】:Overriding properties in python 【发布时间】:2011-10-24 13:56:27 【问题描述】:所以,我试图找出最好的(用最少的代码最优雅)的方法来允许在 python 中覆盖属性的特定函数(例如,只是 getter、只是 setter 等)。我喜欢以下处理属性的方式,因为它们的所有方法都封装在同一个缩进的代码块中(更容易看出处理一个属性的函数在哪里停止以及处理下一个开始):
@apply
def foo():
"""A foobar"""
def fget(self):
return self._foo
def fset(self, val):
self._foo = val
return property(**locals())
但是,如果我想从一个以这种方式定义属性的类继承,然后,比如说,重写foo
setter 函数,这似乎很棘手。我进行了一些搜索,发现的大多数答案都是在基类中定义单独的函数(例如getFoo
和setFoo
),从它们显式创建属性定义(例如foo = property(lambda x: x.getFoo(), lambda x, y: x.setFoo(y), lambda x: x.delFoo())
),然后根据需要覆盖getFoo
、setFoo
和delFoo
。
我不喜欢这个解决方案,因为这意味着我必须为每个属性定义lambas,然后写出每个函数调用(之前我本来可以完成property(**locals())
)。我也没有得到我最初的封装。
理想情况下,我希望能够做的事情是这样的:
class A(object):
def __init__(self):
self.foo = 8
@apply
def foo():
"""A foobar"""
def fget(self):
return self._foo
def fset(self, val):
self._foo = val
return property(**locals())
class ATimesTwo(A):
@some_decorator
def foo():
def fset(self, val):
self._foo = val * 2
return something
然后输出看起来像:
>>> a = A()
>>> a.foo
8
>>> b = ATimesTwo()
>>> b.foo
16
基本上,ATimesTwo
继承自 A
的 getter 函数,但覆盖了 setter 函数。有谁知道这样做的方法(以类似于上面示例的方式)? some_decorator
看起来像什么函数,foo
函数应该返回什么?
【问题讨论】:
我看不到任何方法可以单独使用装饰器来做你想做的事情,因为它无法访问对象,只能访问函数。我猜你有一个装饰器,它返回一个函数,该函数在第一次调用该方法时会做进一步的工作,但是你已经覆盖了初始的 foo。另外 - 我猜你知道 - 你并没有真正按照预期的风格使用语言......但你可能会找到一种方法来使用元类来做到这一点。 这个类似的问题可能会有所帮助:***.com/questions/237432/… 【参考方案1】:stderr 的答案满足了大部分用例。
我想为您想要扩展getter
、setter
和/或deleter
的情况添加一个解决方案。有两种方法:
1。子类property
第一种方法是继承内置的property
并添加作为getter
、setter
和/或deleter
版本的装饰器,扩展当前获取、设置并删除回调
支持将方法附加到集合函数的属性示例:
class ExtendableProperty(property):
def append_setter(self, fset):
# Create a wrapper around the new fset that also calls the current fset
_old_fset = self.fset
def _appended_setter(obj, value):
_old_fset(obj, value)
fset(obj, value)
# Use that wrapper as setter instead of only the new fset
return self.setter(_appended_setter)
用法与普通属性相同,只是现在可以向属性设置器添加方法:
class A(object):
@ExtendableProperty
def prop(self):
return self._prop
@prop.setter
def prop(self, v):
self._prop = v
class B(A):
@A.prop.append_setter
def prop(self, v):
print('Set', v)
>>> a = A()
>>> a.prop = 1
>>> a.prop
1
>>> b = B()
>>> b.prop = 1
Set 1
>>> b.prop
1
2。覆盖 getter、setter 和/或 deleter
使用普通属性,覆盖getter、setter或deleter,然后在父类的属性中添加对fget
、fset
或fdel
的调用。
属性类型示例如示例 1:
class A(object):
@property
def prop(self):
return self._prop
@prop.setter
def prop(self, v):
self._prop = v
class B(A):
@A.prop.setter
def prop(self, v):
A.prop.fset(self, v) # This is the call to the original set method
print('Set '.format(v))
我认为第一个选项看起来更好,因为不需要调用超级属性的 fset
【讨论】:
【参考方案2】:property
装饰器上的 Python docs 建议使用以下成语:
class C(object):
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
然后子类可以像这样覆盖单个 setter/getter:
class C2(C):
@C.x.getter
def x(self):
return self._x * -1
这有点棘手,因为覆盖多个方法似乎需要您执行以下操作:
class C3(C):
@C.x.getter
def x(self):
return self._x * -1
# C3 now has an x property with a modified getter
# so modify its setter rather than C.x's setter.
@x.setter
def x(self, value):
self._x = value * 2
当然,当您覆盖 getter、setter 和 deleter 时,您可能只需重新定义 C3 的属性。
【讨论】:
非常感谢与x.setter
相关的C.x.getter
,在其他任何地方都找不到这个技巧,这绝对是应该改变的,因为它非常混乱!它需要洞察我不拥有的财产对象。 +1,这应该是公认的答案,因为它是最完整的答案。【参考方案3】:
我相信您之前已经听说过,但自 Python 2.3 以来,apply
已被弃用 八年。不要使用它。您对locals()
的使用也违背了Python 的禅宗——显式优于隐式。如果你真的喜欢增加的缩进,没有必要创建一个一次性的对象,只需这样做
if True:
@property
def foo(self):
return self._foo
@foo.setter
def foo(self, val):
self._foo = val
这不会滥用locals
,使用apply
,需要创建额外的对象,或者需要在后面加上foo = foo()
使得更难看到块的结尾。它同样适用于您使用 property
的老式方式 - 只需照常使用 foo = property(fget, fset)
。
如果要覆盖任意子类中的属性,可以使用recipe like this。
如果子类知道属性的定义位置,那么就这样做:
class ATimesTwo(A):
@A.foo.setter
def foo(self, val):
self._foo = val * 2
【讨论】:
"if True:
" ??
@Jean-FrançoisCorbett:这只是一种能够缩进紧随其后的代码块的方法,几乎没有其他副作用。
@Jean-FrançoisCorbett 我完全同意。缩进是 python 语法的一部分,我看不出有什么好的理由要以非标准方式使用它。实际上,减少必要的缩进是常见的问题,没有太多的问题......像尝试实现 switch 语句之类的东西已经在 PEP 中讨论过,python 仍然没有 switch 语句的原因之一是因为一种选择是编写 switch(variable),然后缩进 case(variable value),然后在 case 中再次缩进,这被认为是不可取的。嗯是的。没看到
注意,如果除了setter之外还需要deleter,那么deleter的decorator必须是@foo.deleter
,而不是@A.foo.deleter
,因为后者会定义一个没有setter的属性,就在deleter的上方.以上是关于覆盖python中的属性的主要内容,如果未能解决你的问题,请参考以下文章