所以我正在摆弄一个只读可修改的类模式,这在
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
代理对象)吗?我很想听听对此的意见和/或见解
这会导致问题吗(将超级代理对象暴露在 类本身)?
不直接。 但 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