在Python中,我可以同时重写抽象访问器(get/set)吗?

huangapple go评论81阅读模式
英文:

Can I override both abstract accessors (get/set) at one time in Python?

问题

以下是您要翻译的代码部分:

我尝试编写以下类似的代码

from abc import ABC, abstractmethod

class C(ABC):
    @property
    @abstractmethod
    def x(self):
        pass

    @x.setter
    @abstractmethod
    def x(self, val):
        pass

class D(C):
    @C.x.getter
    def x(self):
        pass

    @C.x.setter
    def x(self, val):
        pass

def examine_property(p):
    print('get', p.fget, getattr(p.fget, '__isabstractmethod__', False) if p.fget is not None else None)
    print('set', p.fset, getattr(p.fset, '__isabstractmethod__', False) if p.fset is not None else None)
    print('del', p.fdel, getattr(p.fdel, '__isabstractmethod__', False) if p.fdel is not None else None)

print("C.x:")
examine_property(C.x)
print("D.x:")
examine_property(D.x)

但似乎不起作用:

C.x:
get <function C.x at 0x1017af2e0> True
set <function C.x at 0x1017af380> True
del None None
D.x:
get <function C.x at 0x1017af2e0> True
set <function D.x at 0x1017af4c0> False
del None None

我是不是做错了什么,或者是不可能同时重写抽象访问器(get/set)?为什么D.x的getter仍然使用抽象的C.x实现?

英文:

I am trying to write code like the following:

from abc import ABC, abstractmethod

class C(ABC):
    @property
    @abstractmethod
    def x(self):
        pass

    @x.setter
    @abstractmethod
    def x(self, val):
        pass

class D(C):
    @C.x.getter
    def x(self):
        pass

    @C.x.setter
    def x(self, val):
        pass

def examine_property(p):
    print(&#39;get&#39;, p.fget, getattr(p.fget, &#39;__isabstractmethod__&#39;, False) if p.fget is not None else None)
    print(&#39;set&#39;, p.fset, getattr(p.fset, &#39;__isabstractmethod__&#39;, False) if p.fset is not None else None)
    print(&#39;del&#39;, p.fdel, getattr(p.fdel, &#39;__isabstractmethod__&#39;, False) if p.fdel is not None else None)

print(&quot;C.x:&quot;)
examine_property(C.x)
print(&quot;D.x:&quot;)
examine_property(D.x)

But it does not seem to work:

C.x:
get &lt;function C.x at 0x1017af2e0&gt; True
set &lt;function C.x at 0x1017af380&gt; True
del None None
D.x:
get &lt;function C.x at 0x1017af2e0&gt; True
set &lt;function D.x at 0x1017af4c0&gt; False
del None None

Am I doing something wrong, or is it simply impossible to override both both abstract accessors (get/set) at one time?

Why is the getter on D.x still using the abstract C.x implementation?

答案1

得分: 1

这里的问题并不是特定于抽象方法,对于基类上定义的任何属性对象,都会遇到相同的问题。问题在于,当你在D上定义setter时,引用的是错误的属性,实际上你不需要引用C.x

想象一下一个杂物柜里有一把损坏的扫帚。你已经有它很长时间了,但杆子已经裂开,刷毛也掉落了。很久以前有人在上面标记了字母C,但你忘记了原因,因为它太旧了。

你想通过用新的杆子替换裂开的杆子并附上新的刷头来修复它,一旦完成,你将称这把扫帚为“Dustdevil”,因为它会像没有别的一样彻底清扫!

请耐心等待,一会儿你就会明白了。

你已经准备好了新的扫帚杆,新的扫帚刷头也在附近。你从杂物柜中取出仍然标有C的旧扫帚,将旧杆从扫帚刷头上拆下来,然后把裂开的杆子扔回杂物柜。你将新杆连接到旧刷头上。工作完成了一半!然而,正当你准备继续时,有人敲门,你放下修理好的扫帚,靠在杂物柜旁边一会儿,去看是谁。

结果发现只是一个叫罗德尼的门到门扫帚推销员。为了明确表示你不打算购买他们的产品,你告诉他们关于你的新扫帚,以及你将如何将其命名为“Dustdevil”,并谈论了一个好扫帚的特点。罗德尼随后提到了他们曾经认识的一个人,一个名叫特里格的人,因为照顾了他们的扫帚20年而获得了勋章!但最终推销员离开了,没有做成交。

所以你回到杂物柜继续修理。你拿出新的扫帚刷头,从杂物柜里取出裂开的杆子,将它连接到新的扫帚刷头上。你完成了,于是在新扫帚刷头上写了个大大的D,以便在使用扫帚时能看到。

然而,当你用新扫帚扫地时,你开始注意到杆子仍然是裂开的,而杆子上仍然有个字母C。发生了什么,你问?

发生的是你应该只将新扫帚杆连接到新扫帚刷头上,忘掉杂物柜里的旧扫帚!毕竟,当你成功替换所有部件时,它已经不再是同一把扫帚,就像忒修斯之船一样,你不再用最初的部件扫地了。

对于property对象,你需要做同样的事情。当你同时替换getter和setter时,你不需要旧的property

class D(C):
    @property
    def x(self):
        pass

    @x.setter
    def x(self, val):
        pass

当你使用@property装饰器时,结果是一个新的property()实例,带有附加的getter,存储为局部变量,使用修饰的getter函数的名称。然后,你可以使用该局部名称作为进一步的.setter.deleter甚至.getter装饰器的起点,为setter和deleter附加更多的钩子函数,每个都会生成一个新的property()实例,从旧实例复制了所有内容,但具有替换的钩子函数之一。

你具体的错误在于忽略了具有替换getter的新property对象,而是从杂物柜中拿出旧的property来替换不同的部分。

如果你确实需要保留Cproperty构造的某个部分(例如,因为你有要保留的deleter或docstring),那么你只需要引用C.x 一次,因为property对象。结果是一个新的property对象,然后你可以附加一个不同的setter:

class D(C):
    @C.x.getter
    def x(self):
        pass

    # 在这里使用新的`x`属性!
    @x.setter
    def x(self, val):
        pass

只需继续链式使用你的property装饰器来应用更多的更改。

通过包括deleter函数来说明后者:

from abc import ABC, abstractmethod

def inspect_x(cls):
    print(f'{cls.__name__}.x:')
    print(f'  get: {cls.x.fget}, abstract:', cls.x.fget and getattr(cls.x.fget, "__isabstractmethod__", False))
    print(f'  set: {cls.x.fset}, abstract:', cls.x.fset and getattr(cls.x.fset, "__isabstractmethod__", False))
    print(f'  del: {cls.x.fdel}, abstract:', cls.x.fdel and getattr(cls.x.fdel, "__isabstractmethod__", False))

class C(ABC):
    @property
    @abstractmethod
    def x(self):
        pass

    @x.setter
    @abstractmethod
    def x(self, val):
        pass

    @x.deleter
    @abstractmethod
    def x(self):
        pass

class D(C

<details>
<summary>英文:</summary>

The issue here isn&#39;t specific to abstract methods, you&#39;d have the same issue with any property object defined on a base class. The issue is that you using the wrong reference when you are defining the setter on `D`, and you *don&#39;t actually need to reference `C.x` here*.

Imagine a cupboard with a broken push-broom in it. You&#39;ve had it for a long time, but the shaft is cracked and the bristles are falling out. A long time ago someone marked it with the letter C but you forgot why, it&#39;s that old.

You want to repair it by replacing the cracked shaft with a new shaft and attach a new broom head with bristles, and once thats done youll call the broom *Dustdevil* because itll tackle cleaning like no other!

Just bare with me, youll see the point in a bit.

You have your new broom handle ready and the new broom head is nearby. You take the old broom, still marked with C, from the cupboard and you detach the old handle from the broom head, the handle that&#39;s cracked, and toss the handle back into the cupboard. You attach the new handle to the old head. Job halfway done! Before you can continue however, you are interrupted by someone knocking at your front door, so you put the result of your repair job against the wall next to the cupboard for a bit so you can go see who it is.

It turns out its just a door-to-door broom-salesman, named Rodney. To make it clear you are not in the market for their wares, you told them about your new broom, how you are going to name it &#39;Dustdevil&#39;, and you talked a bit about what makes a good broom. Rodney then talked about someone they knew once, someone named Trigger and [how they got a *medal* for taking care of their broom for 20 years](https://www.youtube.com/watch?v=LAh8HryVaeY)! But in the end the salesman had to leave without having made a sale.

So you return to the cupboard to continue your repair. You take the new broom head, and you take the cracked handle from the cupboard, and attach it to the new broom head. You are done, so you write a big D on the new broom head, where you can see it when you use the broom.

When you try sweeping with the new broom, however, you start noticing that the handle is still cracked, and that there&#39;s a letter C on the handle still. **What happened** you ask?

What happened is that you should just have attached the new broom handle to the new broom head and forget about the old broom in the cupboard, altogether! After all, when you do succeed in replacing all the parts, It&#39;s not the same broom anymore, just like the [ship of Theseus](https://en.wikipedia.org/wiki/Ship_of_Theseus) you no longer are sweeping with the parts you started out with.

You need to do the same with the `property` object. When you are replacing both the getter and the setter, **you don&#39;t need the old property here**:

class D(C):
@property
def x(self):
pass

@x.setter
def x(self, val):
pass

When you use the `@property` decorator, the result is a new `property()` instance with a getter attached, stored as a local variable using the name of the decorated getter function. You can then attach more hook functions for the setter and deleter by using that local name as a starting point for further `.setter`, `.deleter` and even `.getter` decorators, which each in turn produce a *new* `property()` instance with everything copied across from the old but with a replacement hook function for one of the hooks.
Your specific mistake was to ignore the new property object with the replacement getter attached, and instead you took the old property back out from the cupboard to replace a different part.
If you *do* need to keep *some* part of the property you constructed for `C` (e.g. because you have a deleter or a docstring to preserve), then you only need to reference `C.x` **once**, because a property object. The result is a new property object you can then attach a different setter to:

class D(C):
@C.x.getter
def x(self):
pass

# Use the new `x` property here!
@x.setter
def x(self, val):
pass

Just keep chaining your property decorators to apply more changes to it.
Illustrating the latter with a *deleter* function included:

>>> from abc import ABC, abstractmethod
>>> def inspect_x(cls):
... print(f'{cls.name}.x:')
... print(f' get: {cls.x.fget}, abstract:', cls.x.fget and getattr(cls.x.fget, "isabstractmethod", False))
... print(f' set: {cls.x.fset}, abstract:', cls.x.fset and getattr(cls.x.fset, "isabstractmethod", False))
... print(f' del: {cls.x.fdel}, abstract:', cls.x.fdel and getattr(cls.x.fdel, "isabstractmethod", False))
...
>>> class C(ABC):
... @property
... @abstractmethod
... def x(self):
... pass
... @x.setter
... @abstractmethod
... def x(self, val):
... pass
... @x.deleter
... @abstractmethod
... def x(self):
... pass
...
>>> class D(C):
... @C.x.getter
... def x(self):
... pass
... @x.setter
... def x(self, val):
... pass
... # note: **the deleter is still the original
...
>>> inspect_x(C)
C.x:
get: <function C.x at 0x1017af240>, abstract: True
set: <function C.x at 0x1017aede0>, abstract: True
del: <function C.x at 0x1017af560>, abstract: True
>>> inspect_x(D)
D.x:
get: <function D.x at 0x1017af1a0>, abstract: False
set: <function D.x at 0x1017af600>, abstract: False
del: <function C.x at 0x1017af560>, abstract: True


</details>

huangapple
  • 本文由 发表于 2023年2月18日 01:38:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75487566.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定