我是问题OP,我要回答我自己的问题,因为我想我在打字的过程中找到了答案。在其他人确认它是正确的之前,我不会将其标记为正确。
这个问题在这里特别相关,但问题与这个问题不同,尽管答案非常有启发性(尽管评论变成了关于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=", ")
Output:
AttributeError: LOL nope.
“但是什么!?”,我听到你问,“我在创建它后没有设置它的任何属性!”啊但是是的,你做到了, 在里面__init__()
. Since __init__()
叫做after对象被创建,线条self.a = a
and self.b = b
正在设置属性a
and b
after的创造im
。你真正想要的是设置属性a
and b
before创建不可变对象。一个明显的方法是创建一个mutable首先输入(您的属性被允许设置在__init__()
),然后使不可变的 type a subclass并确保您实施__new__()
不可变子类的方法首先构造一个可变版本,然后使其不可变,如下所示。
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=", ")
Output:
AttributeError: LOL nope srsly.
“WTF!?什么时候的?__setattr__()
这次接到电话了吗?”问题是,ActuallyImmutable
是一个子类Mutable
,并且没有明确实施其__init__()
, 父类的__init__()
被自动调用after的创建ActuallyImmutable
对象,所以父母的总数__init__()
被调用两次,一次在创建之前im
(没关系)并且一次after(这是not OK)。所以让我们再试一次,这次覆盖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=", ")
Output:
2, 3
很好,成功了。哦,不用担心# noinspection PyMissingConstructor
,这只是 PyCharm 的 hack,目的是阻止 PyCharm 抱怨我没有给父母打电话__init__()
,这显然是我们的意图。最后只是为了检查一下im
确实是不可变的,验证一下im.a = 42
会给你AttributeError: LOL nope srsly.
.