我是问题 OP,我将回答我自己的问题,因为我想我在输入过程中找到了答案。在其他人确认它是正确的之前,我不会将其标记为正确。
This question here 特别相关,但问题与这个问题不一样,虽然答案非常有启发性(尽管 cmets 变成了关于 C 和 Python 以及“pythonic”的有启发性但深奥的争论),但它应该在这里更清楚地列出来专门解决这个问题。我希望这将有助于未来的读者。此答案中的代码已在 Python 3.6.1 中验证。
关于一个不可变对象,显然你不想在它创建后设置它的成员。在 Python 中这样做的方法是将 __setattr__() 特殊方法覆盖为 raise 一个错误 (AttributeError),这样人们就不能做类似 my_immutable_object.x = 3 的事情。以下面的自定义不可变类为例。
class Immutable(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __setattr__(self, key, value):
raise AttributeError("LOL nope.")
让我们尝试使用它。
im = Immutable(2, 3)
print(im.a, im.b, sep=", ")
输出:
AttributeError: LOL nope.
“但是什么!?”,我听到你问,“我在创建它之后没有设置任何属性!”啊,但是是的,你做了,在__init__()。由于__init__() 在对象创建之后被调用,self.a = a 和self.b = b 行正在设置属性a 和b在创建之后im。您真正想要的是在创建不可变对象之前设置属性a 和b。。一个明显的方法是首先创建一个可变类型(你允许在__init__()中设置它的属性),然后使不可变 键入它的一个子类,并确保你实现了不可变子类的__new__() 方法,以先构造一个可变版本,然后使其不可变,如下所示。 p>
class Mutable(object):
def __init__(self, a, b):
self.a = a
self.b = b
class ActuallyImmutable(Mutable):
def __new__(cls, a, b):
thing = Mutable(a, b)
thing.__class__ = cls
return thing
def __setattr__(self, key, value):
raise AttributeError("LOL nope srsly.")
现在让我们尝试运行它。
im = ActuallyImmutable(2, 3)
print(im.a, im.b, sep=", ")
输出:
AttributeError: LOL nope srsly.
“WTF!?这次__setattr__()什么时候被叫到的?”问题是,ActuallyImmutable 是Mutable 的子类,并且在没有显式实现其__init__() 的情况下,父类的__init__() 在ActuallyImmutable 对象创建之后自动调用,所以总共调用了父级的__init__() 两次,一次是在创建im 之前(可以),一次是之后(这是不可以)。所以让我们再试一次,这次覆盖AcutallyImmutable.__init__()。
class Mutable(object):
def __init__(self, a, b):
print("Mutable.__init__() called.")
self.a = a
self.b = b
class ActuallyImmutable(Mutable):
def __new__(cls, a, b):
thing = Mutable(a, b)
thing.__class__ = cls
return thing
# noinspection PyMissingConstructor
def __init__(self, *args, **kwargs):
# Do nothing, to prevent it from calling parent's __init__().
pass
def __setattr__(self, key, value):
raise AttributeError("LOL nope srsly.")
现在应该可以了。
im = ActuallyImmutable(2, 3)
print(im.a, im.b, sep=", ")
输出:
2, 3
很好,成功了。哦,别担心# noinspection PyMissingConstructor,这只是一个 PyCharm hack,它可以阻止 PyCharm 抱怨我没有给父母的 __init__() 打电话,这显然是我们在这里的意图。最后只是为了检查im 是否真的是不可变的,验证im.a = 42 会给你AttributeError: LOL nope srsly.。