覆盖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 函数,这似乎很棘手。我进行了一些搜索,发现的大多数答案都是在基类中定义单独的函数(例如getFoosetFoo),从它们显式创建属性定义(例如foo = property(lambda x: x.getFoo(), lambda x, y: x.setFoo(y), lambda x: x.delFoo())),然后根据需要覆盖getFoosetFoodelFoo

我不喜欢这个解决方案,因为这意味着我必须为每个属性定义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 的答案满足了大部分用例。

我想为您想要扩展gettersetter 和/或deleter 的情况添加一个解决方案。有两种方法:

1。子类property

第一种方法是继承内置的property 并添加作为gettersetter 和/或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,然后在父类的属性中添加对fgetfsetfdel的调用。

属性类型示例如示例 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中的属性的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring 覆盖外部属性文件中的属性(如果存在)

覆盖 Swift 中的存储属性

如何覆盖 UIViewController 中的“视图”属性?

Chef:如何覆盖角色中的默认属性?

如何覆盖 Quarkus 中的属性?

什么可以覆盖 html 输入中的属性?