在 Python 中使类不可变的方法

Posted

技术标签:

【中文标题】在 Python 中使类不可变的方法【英文标题】:Ways to make a class immutable in Python 【发布时间】:2011-06-27 04:16:04 【问题描述】:

我正在做一些分布式计算,其中几台机器在假设它们都具有各种类的相同版本的情况下进行通信。因此,使这些类不可变似乎是一个不错的设计。并不是说它必须阻止怀有恶意的用户,只是足够不可变,永远不会被意外修改。

我该怎么做呢?例如,如何实现一个元类,使使用它的类在定义后不可变?

>>> class A(object):
...     __metaclass__ = ImmutableMetaclass
>>> A.something = SomethingElse # Don't want this
>>> a = A()
>>> a.something = Whatever # obviously, this is still perfectly fine.

替代方法也可以,例如接受一个类并返回一个不可变类的装饰器/函数。

【问题讨论】:

@porgarmingduod:请详细说明immutable enough that it is never modified by accident 的实际含义。谢谢 @eat:因为我正在创建一个系统,其中 instances 的类被神奇地分布,程序员可能会忘记实际的类不是。当变量在一些复杂的编码中被抛出时,如果有人试图将某些东西分配给不可变类,我希望抛出一个异常。至于accident 部分,我的意思是解决方案不必阻止白痴试图撬开解决方案的内部结构。 How to make an immutable object in Python? 的可能重复项 @eat:您的评论以“无冒犯”开头这一事实并不能减轻我认为您的import this 回答令人反感的事实。 @eat:这个问题有什么不明白的?不清楚我在问一种方法来分配/删除类中的属性会引发异常吗?我知道对于此类功能是否是正确的解决方案可能存在异议,但这不是问题的一部分。我可以写一个冗长的辩护词来解释我为什么这么认为。或者我可能会失败。但问题仍然是如何制作不可变的类 【参考方案1】:

如果使用 __slots__ 的旧技巧不适合您,则此方法或其中的一些变体可以: 只需编写元类的__setattr__ 方法作为您的后卫。在此示例中,我阻止分配新属性,但允许修改现有属性:

def immutable_meta(name, bases, dct):
    class Meta(type):
        def __init__(cls, name, bases, dct):
            type.__setattr__(cls,"attr",set(dct.keys()))
            type.__init__(cls, name, bases, dct)

        def __setattr__(cls, attr, value):
            if attr not in cls.attr:
                raise AttributeError ("Cannot assign attributes to this class")
            return type.__setattr__(cls, attr, value)
    return Meta(name, bases, dct)


class A:
    __metaclass__ = immutable_meta
    b = "test"

a = A()
a.c = 10 # this works
A.c = 20 # raises valueError

【讨论】:

请注意,您仍然可以执行type.__setattr__(A, 'c', 10),然后A.c 存在。这并不能真正使它不可变,但在大多数情况下,它会是“足够好”的不可变性。 最正确的解决方法是object.__setattr__ - 它是类本身的setattr,而不是它的类型。 type.__setattr__ 之所以有效,是因为它与对象的相同。但实际上——在纯 Python 中,很可能不可能避免解决任何可以提出的此类限制——而且它应该是“Pythonic”的方式。 其实你不能在继承自type的对象上调用object__setattr__。如果你尝试object.__setattr__(A, 'c', 10),你会得到一个错误:“TypeError: can't apply this __setattr__ to type object”。 确实 - 我没有注意到这一点。 我复制,粘贴。并运行此代码,它不会在 Python 3.7 中引发 valueError。在底部添加 print(A.c) 会产生“20”。【参考方案2】:

不要在不可变的类上浪费时间。

您可以做的事情远比尝试创建不可变对象要简单得多。

这里有五种不同的技术。您可以从中挑选。任何一个都会起作用。一些组合也可以。

    文档。事实上,他们不会忘记这一点。给他们点赞。

    单元测试。使用将__setattr__ 作为异常处理的简单模拟来模拟您的应用程序对象。对对象状态的任何更改都是单元测试中的失败。这很简单,不需要任何复杂的编程。

    覆盖 __setattr__ 以在每次尝试写入时引发异常。

    collections.namedtuple。它们开箱即用是不可变的。

    collections.Mapping。它是不可变的,但您确实需要实现一些方法才能使其工作。

【讨论】:

@S.Lott:当然还有更可靠的方法,比如覆盖类使用的dictproxy?但是我以前没有做过这样的事情,因此提出了这个问题。附加说明:我认为您可以让 cmets 不考虑为什么不这样做,因为 cmets 对于原始问题肯定会给予足够的重视。 @porgarmingduod:这是一个答案,而不是更多的 cmets。它是孤独的。其他有相同问题的人不会通过 cmets 进行讨论。没有“更可靠的方法”。这是对大脑卡路里的浪费。程序员并不愚蠢、健忘、容易发生事故或反社会。构建您的应用程序。让它工作。提出例外。这就是 Pythonic 的方式。 @S.Lott:好吧,我想你可以将我的问题解释为“我怎样才能创建一个不可变的类,或者,谁能告诉我为什么我不应该?”。也许我会有更多的运气,“如果我们认为我确实有充分的理由这样做,我怎么能创建一个不可变的类。”我可能不会得到任何答案,但我会接受这一点,而不是有人告诉我我不知道自己想要什么,一再重复,即使在我明确要求回答手头的实际问题之后也是如此。 @S.Lott:我在哪里要求你为我做任何事?暗示我对你有任何要求是非常不合理的;你不可能在我所说的任何事情中找到任何依据来提出这种主张。而且我早就承认我正在尝试做的事情可能不是一个好主意。我什至不想为这一点辩护。我只是试图为实际问题辩护,这个问题非常具体,可以回答。您在最初的 cmets 中提出了“这可能是一个愚蠢的想法”的观点。此外,将您自己的意见称为“我们”,这是一种荣誉。你是python社区共识吗? 这对于学习 Python 的数据模型以练习不可变类非常有指导意义。了解为什么 object.__setattr__type.__setattr__ 仍然可以绕过大多数自制尝试是一种很好的学习体验。很遗憾,您选择听到有人说“我想做 X”而您只是回答“停止想做 X”的问题的方法。想做 X 可能非常有用,如果只是为了教学,答案空间不是您对问题前提进行个人规范判断的好位置。发表评论。或者,更好的是,忽略它。【参考方案3】:

如果您不介意重复使用他人的作品:

http://packages.python.org/pysistence/

不可变的持久性(在功能上,不是写到桌面意义上的)数据结构。

即使您不按原样使用它们,源代码也应该提供一些灵感。例如,他们的 expando 类在其构造函数中获取一个对象并返回它的不可变版本。

【讨论】:

不幸的是,expando 类不是不可变的。我在阅读文档时犯了同样的错误,但该软件包仅提供不可变列表和字典。阅读源代码后,我很快意识到 expando 类只是一个允许您复制具有更改的类的类 - 但是它不能防止更改。 好地方,我错过了。

以上是关于在 Python 中使类不可变的方法的主要内容,如果未能解决你的问题,请参考以下文章

String为什么不可变?

String为什么不可变?

如何使类属性不可变?

如何使以下类不可变?

python中使类方法像属性一样被使用

不可变类特征