Python 类函数返回 super()

问题描述 投票:0回答:1

所以我正在摆弄一个只读可修改的类模式,这在

java
中很常见。它涉及创建一个包含只读属性的基类,并扩展该类以获得可修改的版本。通常有一个函数
readonly()
或类似的东西可以将可修改版本恢复为其自身的只读版本。

不幸的是,你不能直接为Python中超类中定义的属性定义setter,但你可以简单地重新定义它,如下所示。更有趣的是,在Python中,你得到了“神奇函数”

super
,返回父类/超类的代理对象,它允许一个厚颜无耻的
readonly()
实现,感觉真的很hacky,但据我测试,它确实有效。

class Readonly:
    _t:int
    
    def __init__(self, t: int):
        self._t = t
    
    @property
    def t(self) -> int:
        return self._t



class Modifyable(Readonly):
    def __init__(self, t: int):
        super().__init__(t)
    
    @property
    def t(self) -> int:
        return self._t # can also be super().t
    
    @t.setter
    def t(self, t):
        self._t = t
    
    def readonly(self) -> Readonly:
        return super()

在上面的模式中,我可以调用

readonly
函数并获取父对象的代理,而无需实例化只读版本。唯一的问题是,当设置只读属性而不是抛出
AttributeError: can't set attribute
时,它会抛出
AttributeError: 'super' object has no attribute '<param-name>'

所以这是这个问题:

  • 这会导致问题(在类本身之外暴露
    super
    代理对象)吗?
  • 我在 python 3.8.5 上测试了这个,但不确定这是否是偶然的并且可以这么说违反“python 语义”?
  • 是否有更好/更理想的方法来实现这一目标? (我不知道它是否值得在性能方面考虑模棱两可的错误消息)

我很想听听对此的意见和/或见解

python python-3.x design-patterns super readonly
1个回答
2
投票

这会导致问题吗(将超级代理对象暴露在 类本身)?

不直接。 但 super() 对象有时会以奇怪的方式表现。如果您想要的只是由

property
保护的属性,那么它会起作用,但是,例如,如果您有一个直接访问的简单实例属性(这是 Python 中更常见的模式),则它无法与 super 一起使用() 代理。

我在 python 3.8.5 上测试了这个,但不确定这是否是偶然的 可以这么说违背“Python 语义”吗?

不,从 3.0(甚至更早)开始(包括 Python 3.12 alpha),您所依赖的行为非常一致

是否有更好/更理想的方法来实现这一目标? (我不知道 如果它甚至值得模棱两可的错误消息 例如性能)

是的。 正如您所看到的,这不会提供子类的只读版本,而是返回一个超级代理,该代理主要表现为父类的实例。

如果返回父类的实例就足够了,并且可修改是唯一的区别,您可以创建一个新的只读实例,用当前实例值加载它 - 这可以工作:

def readonly(self):
    # retrieves the superclass without name hardcoding
    # this is the actual class, not a proxy:
    parent = __class__.__mro__[1]
    # creates an instance without going though it's `__init__`:
    instance = parent.__new__(parent)
    # updates the instance `__dict__` internally:
    instance.__dict__.update(self.__dict__)
    return instance

实现克隆的

__dict__.update()
看起来不安全,但它相当可靠。 Python 序列化工具 - pickle - 本身在反序列化对象时使用这种方法(默认情况下 - 类可以自定义它。

另一种选择,“不太推荐”,因为......也许它会吓到人们,但相当可靠,并且会起作用,就是在调用 .readonly 时将实例本身更改为

ReadOnly
的实例。

只需将

readonly
写为:

der readonly(self):
     self.__class__ = __class__.__mro__[1]
     return self

如果根本不需要更改类,可以选择在“可修改”类本身中设置一个状态,将其切换为可写或不可写。调用

readonly
后,您可以创建一个新的只读实例,或在内部更改状态。

更简单的方法是使用另一个属性,前缀为

_
,并在设置器中使用
if
进行测试。

如果有很多这样的属性,当然这是不合需要的,您可以自定义

__setattr__
来捕获所有分配 - 或创建一个自定义描述符,而不是
property
来执行此操作。

TBH,如果我的目标是拥有一个在某些时候不再可编辑的可编辑对象,我会采用上面的

__class__
更改方法。如果您使用打字,请将
typing.cast
.__class__
分配一起使用,以便静态工具可以理解它。


更新

一年多后,这个问题被接受了,回到这里后,我有了一个新的方法: 可以有一个超级只读类,它直接与原始可变类共享实例

__dict__
- 就这么简单:

class A:
    @property 
    def a(self):
        return self._a
class B:
    # note how to create a new property,
    # keeping the "getter"  of the superclass:
    @A.a.setter
    def a(self, value):
        self._a = value
    @property
    def readonly(self):
        # a new, non initialized instance: 
        a = A.__new__(A)
        # which will share the same namespace,
        # but lack any setters:
        a.__dict__ = self.__dict__
        return a
# and this can tested on the repl:

b = B()
b.a = 23

a = b.readonly
print(a.a)
a.a = 42
# raises AttributeError
b.a = 42  # also updates a.a
© www.soinside.com 2019 - 2024. All rights reserved.