在Python中重写属性
我正在想办法找到一种最优雅、代码量最少的方法,来允许在Python中重写属性的特定函数(比如只重写获取器、只重写设置器等等)。我比较喜欢下面这种定义属性的方式,因为它把所有相关的方法都放在同一个缩进的代码块里,这样更容易看出处理一个属性的函数在哪里结束,处理下一个属性的函数又从哪里开始:
@apply
def foo():
"""A foobar"""
def fget(self):
return self._foo
def fset(self, val):
self._foo = val
return property(**locals())
不过,如果我想从一个以这种方式定义属性的类继承,然后重写foo
的设置函数,这就变得有点棘手。我查了一些资料,发现大多数答案都是在基类中定义单独的函数(比如getFoo
和setFoo
),然后从这些函数显式创建一个属性定义(比如foo = property(lambda x: x.getFoo(), lambda x, y: x.setFoo(y), lambda x: x.delFoo())
),接着根据需要重写getFoo
、setFoo
和delFoo
。
我不喜欢这个解决方案,因为这意味着我必须为每个属性都定义一个lambda表达式,然后写出每个函数调用(而之前我可以直接用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
继承了获取器函数,但重写了设置器函数。有没有人知道怎么做到这一点(以一种看起来和上面例子相似的方式)?some_decorator
应该是什么样的函数,foo
函数又应该返回什么?
3 个回答
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
的调用。
下面是与第一种示例相同类型的属性示例:
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。
Python的文档中提到,使用property
装饰器时,可以这样写:
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
然后,子类可以像这样重写一个单独的设置器/获取器:
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
当然,当你重写获取器、设置器和删除器的时候,你可能可以直接为C3重新定义这个属性。
我相信你之前一定听说过,apply
这个东西已经被淘汰有八年了,从Python 2.3开始就不推荐使用了。别用它。你用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)
就可以了。
如果你想在某个子类中重写一个属性,可以参考这样的做法。
如果子类知道这个属性是在哪里定义的,那就直接这样做:
class ATimesTwo(A):
@A.foo.setter
def foo(self, val):
self._foo = val * 2