如何在Python中创建不可变对象?
虽然我从来没有需要过这个,但我突然意识到在 Python 中创建一个不可变的对象可能有点棘手。您不能只覆盖 __setattr__
,因为这样你甚至无法在 __init__
。对元组进行子类化是一个有效的技巧:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
但是您可以通过 self[0]
和 访问
,这很烦人。a
和 b
变量self[1]
这在纯 Python 中可能吗?如果没有,我该如何使用 C 扩展来做到这一点?仅适用于 Python 3 的答案是可以接受的。
Although I have never needed this, it just struck me that making an immutable object in Python could be slightly tricky. You can't just override __setattr__
, because then you can't even set attributes in the __init__
. Subclassing a tuple is a trick that works:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
But then you have access to the a
and b
variables through self[0]
and self[1]
, which is annoying.
Is this possible in pure Python? If not, how would I do it with a C extension? Answers that work only in Python 3 are acceptable.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(27)
除了优秀的其他答案之外,我还喜欢为 python 3.4(或者可能是 3.3)添加一个方法。这个答案建立在之前对此问题的几个答案的基础上。
在 python 3.4 中,您可以使用不带 setter 的属性来创建无法修改的类成员。 (在早期版本中,可以在没有设置器的情况下分配给属性。)
您可以像这样使用它:
它将打印
"constant"
但调用
instance.a=10
将导致:解释:不带 setter 的属性是 python 3.4(我认为是 3.3)的一个最新功能。如果您尝试分配给此类属性,则会引发错误。
使用槽,我将成员变量限制为
__A_a
(即__a
)。问题:仍然可以分配给
_A__a
(instance._A__a=2
)。但是,如果您分配给私有变量,那是您自己的错...这个答案除其他外,不鼓励使用
__slots__
。使用其他方法来阻止属性创建可能更可取。In addition to the excellent other answers I like to add a method for python 3.4 (or maybe 3.3). This answer builds upon several previouse answers to this question.
In python 3.4, you can use properties without setters to create class members that cannot be modified. (In earlier versions assigning to properties without a setter was possible.)
You can use it like this:
which will print
"constant"
But calling
instance.a=10
will cause:Explaination: properties without setters are a very recent feature of python 3.4 (and I think 3.3). If you try to assign to such a property, an Error will be raised.
Using slots I restrict the membervariables to
__A_a
(which is__a
).Problem: Assigning to
_A__a
is still possible (instance._A__a=2
). But if you assign to a private variable, it is your own fault...This answer among others, however, discourages the use of
__slots__
. Using other ways to prevent attribute creation might be preferrable.因此,我正在编写 python 3 的相应内容:
I) 在数据类装饰器的帮助下并设置 freeze=True。
我们可以在 python 中创建不可变对象。
为此,需要从数据类库导入数据类,并且需要设置 freeze=True
ex。
from dataclasses import dataclass
o/p:
来源: https://realpython.com/python-data-classes /
So, I am writing respective of python 3:
I) with the help of data class decorator and set frozen=True.
we can create immutable objects in python.
for this need to import data class from data classes lib and needs to set frozen=True
ex.
from dataclasses import dataclass
o/p:
Source: https://realpython.com/python-data-classes/
如果您对具有行为的对象感兴趣,那么namedtuple几乎就是您的解决方案。
正如namedtuple 文档底部所述,您可以派生来自namedtuple的你自己的类;然后,您可以添加您想要的行为。
例如(代码直接取自文档)
:结果:
此方法适用于 Python 3 和 Python 2.7(也在 IronPython 上进行了测试)。
唯一的缺点是继承树有点奇怪;但这不是你通常玩的东西。
If you are interested in objects with behavior, then namedtuple is almost your solution.
As described at the bottom of the namedtuple documentation, you can derive your own class from namedtuple; and then, you can add the behavior you want.
For example (code taken directly from the documentation):
This will result in:
This approach works for both Python 3 and Python 2.7 (tested on IronPython as well).
The only downside is that the inheritance tree is a bit weird; but this is not something you usually play with.
从 Python 3.7 开始,您可以使用
@dataclass
装饰器< /a> 在你的类中,它将像结构一样不可变!不过,它可能会也可能不会添加__hash__()< /code>
方法到你的类中。引用:
这是上面链接的文档中的示例:
As of Python 3.7, you can use the
@dataclass
decorator in your class and it will be immutable like a struct! Though, it may or may not add a__hash__()
method to your class. Quote:Here the example from the docs linked above:
就像
dict
一样,我有一个开源库,我在其中以功能方式做事,因此在不可变对象中移动数据是有帮助的。但是,我不想必须转换我的数据对象才能让客户端与它们交互。所以,我想出了这个 - 它为您提供了一个类似字典的不可变对象 +一些辅助方法。
归功于 Sven Marnach 在他的 answer 限制属性更新和删除的基本实现。
辅助方法
示例
Just Like a
dict
I have an open source library where I'm doing things in a functional way so moving data around in an immutable object is helpful. However, I don't want to have to transform my data object for the client to interact with them. So, I came up with this - it gives you a dict like object thats immutable + some helper methods.
Credit to Sven Marnach in his answer for the basic implementation of restricting property updating and deleting.
Helper methods
Examples
这种方式不会阻止
object.__setattr__
工作,但我仍然发现它很有用:您可能需要根据使用情况覆盖更多内容(例如
__setitem__
)案件。This way doesn't stop
object.__setattr__
from working, but I've still found it useful:you may need to override more stuff (like
__setitem__
) depending on the use case.从以下
Immutable
类继承的类在其__init__
方法完成执行后是不可变的,它们的实例也是如此。正如其他人指出的那样,由于它是纯 python,因此没有什么可以阻止某人使用基本object
和type
中的变异特殊方法,但这足以阻止任何人意外改变类/实例。它的工作原理是用元类劫持类创建过程。
Classes which inherit from the following
Immutable
class are immutable, as are their instances, after their__init__
method finishes executing. Since it's pure python, as others have pointed out, there's nothing stopping someone from using the mutating special methods from the baseobject
andtype
, but this is enough to stop anyone from mutating a class/instance by accident.It works by hijacking the class-creation process with a metaclass.
第三方
attr
模块提供此功能。编辑:python 3.7已将这个想法采用到stdlib中
@dataclass
。根据文档,
attr
通过重写__setattr__
来实现冻结类,并且在每个实例化时间对性能产生较小的影响。如果您习惯使用类作为数据类型,attr 可能特别有用,因为它会为您处理样板文件(但不会产生任何魔法)。特别是,它为您编写了九个 dunder (__X__) 方法(除非您关闭其中任何一个),包括 repr、init、hash 和所有比较函数。
attr
还为__slots__< 提供了 帮助器/代码>。
The third party
attr
module provides this functionality.Edit: python 3.7 has adopted this idea into the stdlib with
@dataclass
.attr
implements frozen classes by overriding__setattr__
and has a minor performance impact at each instantiation time, according to the documentation.If you're in the habit of using classes as datatypes,
attr
may be especially useful as it takes care of the boilerplate for you (but doesn't do any magic). In particular, it writes nine dunder (__X__) methods for you (unless you turn any of them off), including repr, init, hash and all the comparison functions.attr
also provides a helper for__slots__
.您可以覆盖 setattr 并仍然使用 init 来设置变量。您将使用超类setattr。这是代码。
You can override setattr and still use init to set the variable. You would use super class setattr. here is the code.
下面的基本解决方案解决了以下场景:
__init__()
来访问属性。想法是重写
__setattr__
方法并在每次对象冻结状态更改时替换其实现。因此,我们需要一些方法(
_freeze
)来存储这两个实现并根据请求在它们之间进行切换。此机制可以在用户类内部实现,也可以从特殊的 Freezer 类继承,如下所示:
The basic solution below addresses the following scenario:
__init__()
can be written accessing the attributes as usual.The idea is to override
__setattr__
method and replace its implementation each time the object frozen status is changed.So we need some method (
_freeze
) which stores those two implementations and switches between them when requested.This mechanism may be implemented inside the user class or inherited from a special
Freezer
class as shown below:不久前我需要这个,并决定为其制作一个 Python 包。初始版本现已在 PyPI 上:
使用:
此处的完整文档: https://github.com/theengineear/immutable
希望它有帮助,它包装了一个已讨论的命名元组,但使实例化更加简单。
I needed this a little while ago and decided to make a Python package for it. The initial version is on PyPI now:
To use:
Full docs here: https://github.com/theengineear/immutable
Hope it helps, it wraps a namedtuple as has been discussed, but makes instantiation much simpler.
我找到了一种无需子类化 tuple、namedtuple 等的方法。您所需要做的就是禁用 setattr 和 delattr (以及 setitem和delitem(如果您想让集合不可变)启动后:
其中lock可以如下所示:
因此您可以创建类使用此方法>不可变并按照我展示的方式使用它。
如果您不想在每个 init 中编写 self.lock(),您可以使用元类自动实现:
Test
I found a way to do it without subclassing tuple, namedtuple etc. All you need to do is to disable setattr and delattr (and also setitem and delitem if you want to make a collection immutable) after the initiation:
where lock can look like this:
So you can create class Immutable with this method and use it the way I showed.
If you don't want to write self.lock() in every single init you can make it automatically with metaclasses:
Test
简短回答
使用 pandatic 的
BaseModel
和重写Config
:基于 OOP 的长回答
步骤 1:设置抽象
使用
pyndatic
-package 实现可重用ImmutableModel
:第 2 步:声明不可变结构
声明
Point
和Vector
类:第 3 步:测试结果
Short Answer
Use pandatic's
BaseModel
with overridingConfig
:Long OOP-based Answer
Step 1: Set abstraction
Use
pyndatic
-package for implementation of reusableImmutableModel
:Step 2: Declare immutable structures
Declare
Point
andVector
classes:Step 3: Test results
另一种方法是创建一个包装器,使实例不可变。
这在只有某些实例必须不可变的情况下非常有用(例如函数调用的默认参数)。
也可用于不可变工厂,例如:
还可以防止
object.__setattr__
,但由于 Python 的动态特性,容易受到其他技巧的影响。An alternative approach is to create a wrapper which makes an instance immutable.
This is useful in situations where only some instances have to be immutable (like default arguments of function calls).
Can also be used in immutable factories like:
Also protects from
object.__setattr__
, but fallable to other tricks due to Python's dynamic nature.我使用了与 Alex 相同的想法:一个元类和一个“init 标记”,但与重写 __setattr__ 相结合:
注意:我直接调用元类以使其适用于 Python 2.x 和3.x。
它也适用于插槽 ...:
... 和多重继承:
但是请注意,可变属性仍然是可变的:
I used the same idea as Alex: a meta-class and an "init marker", but in combination with over-writing __setattr__:
Note: I'm calling the meta-class directly to make it work both for Python 2.x and 3.x.
It does work also with slots ...:
... and multiple inheritance:
Note, however, that mutable attributes stay to be mutable:
这里没有真正包含的一件事是完全不变性......不仅仅是父对象,还有所有子对象。例如,元组/冻结集可能是不可变的,但它所属的对象可能不是。这是一个小(不完整)版本,它在强制执行不变性方面做得很好:
One thing that's not really included here is total immutability... not just the parent object, but all the children as well. tuples/frozensets may be immutable for instance, but the objects that it's part of may not be. Here's a small (incomplete) version that does a decent job of enforcing immutability all the way down:
您只需在 init 的最后语句中重写 setAttr 即可。那么你可以构建但不能改变。显然,您仍然可以通过 usint object.setAttr 进行覆盖,但实际上大多数语言都有某种形式的反射,因此不可变性始终是一个有漏洞的抽象。不变性更多的是为了防止客户端意外违反对象的契约。我使用:
===============================
提供的原始解决方案是不正确的,这是根据使用解决方案的评论进行更新的此处
原始解决方案以一种有趣的方式错误,因此包含在底部。
=================================
输出:
================= =====================
原始实现:
评论中正确地指出,这实际上不起作用,因为它阻止创建超过当您覆盖类 setattr 方法时,有一个对象,这意味着无法创建第二个对象,因为 self.a = 将在第二次初始化时失败。
You can just override setAttr in the final statement of init. THen you can construct but not change. Obviously you can still override by usint object.setAttr but in practice most languages have some form of reflection so immutablility is always a leaky abstraction. Immutability is more about preventing clients from accidentally violating the contract of an object. I use:
=============================
The original solution offered was incorrect, this was updated based on the comments using the solution from here
The original solution is wrong in an interesting way, so it is included at the bottom.
===============================
Output :
======================================
Original Implementation:
It was pointed out in the comments, correctly, that this does not in fact work, as it prevents the creation of more than one object as you are overriding the class setattr method, which means a second cannot be created as self.a = will fail on the second initialisation.
我创建了一个小型类装饰器装饰器来使类不可变(除了
__init__
内部)。作为 https://github.com/google/etils 的一部分。这也支持继承。
执行:
I've created a small class decorator decorator to make class immutable (except inside
__init__
). As part of https://github.com/google/etils.This support inheritance too.
Implementation:
我刚刚想到的另一个解决方案:获得与原始代码相同行为的最简单方法是
它并不能解决可以通过
[0]
等访问属性的问题,但至少它是相当短,并提供了与pickle
和copy
兼容的额外优势。namedtuple
创建类似于我的类型这个答案中描述,即源自元组
并使用__slots__
。它在 Python 2.6 或更高版本中可用。Yet another solution I just thought of: The simplest way to get the same behaviour as your original code is
It does not solve the problem that attributes can be accessed via
[0]
etc., but at least it's considerably shorter and provides the additional advantage of being compatible withpickle
andcopy
.namedtuple
creates a type similar to what I described in this answer, i.e. derived fromtuple
and using__slots__
. It is available in Python 2.6 or above.使用冻结数据类
对于 Python 3.7+,您可以使用数据类 带有
frozen=True
选项,这是一种非常Pythonic且可维护的方式来完成你想做的事情。它看起来像这样:
由于数据类的字段需要类型提示,我使用了
typing
模块中的任何内容。不使用命名元组的原因
在 Python 3.7 之前,经常会看到命名元组被用作不可变对象。它在很多方面都可能很棘手,其中之一是命名元组之间的 __eq__ 方法不考虑对象的类。例如:
如您所见,即使
obj1
和obj2
的类型不同,即使它们的字段名称不同,obj1 == obj2
code> 仍然给出True
。这是因为使用的 __eq__ 方法是元组的方法,它仅比较给定位置的字段的值。这可能是一个巨大的错误来源,特别是当您对这些类进行子类化时。Using a Frozen Dataclass
For Python 3.7+ you can use a Data Class with a
frozen=True
option, which is a very pythonic and maintainable way to do what you want.It would look something like that:
As type hinting is required for dataclasses' fields, I have used Any from the
typing
module.Reasons NOT to use a Namedtuple
Before Python 3.7 it was frequent to see namedtuples being used as immutable objects. It can be tricky in many ways, one of them is that the
__eq__
method between namedtuples does not consider the objects' classes. For example:As you see, even if the types of
obj1
andobj2
are different, even if their fields' names are different,obj1 == obj2
still givesTrue
. That's because the__eq__
method used is the tuple's one, which compares only the values of the fields given their positions. That can be a huge source of errors, specially if you are subclassing these classes.最简单的方法是使用
__slots__
:A
的实例现在是不可变的,因为您无法在它们上设置任何属性。如果您希望类实例包含数据,您可以将其与从
tuple
派生结合起来:编辑:如果您想摆脱索引,您可以覆盖
__getitem__()
:请注意,在这种情况下,您不能使用
operator.itemgetter
作为属性,因为这将依赖于Point.__getitem__()
而不是 tuple.__getitem__() 。此外,这不会阻止使用 tuple.__getitem__(p, 0),但我很难想象这会如何构成问题。我不认为创建不可变对象的“正确”方法是编写 C 扩展。 Python 通常依赖于同意的成年人的库实现者和库用户,并且不应真正强制执行接口,而应在文档中明确说明接口。这就是为什么我不认为通过调用
object.__setattr__()
来规避被重写的__setattr__()
的可能性是一个问题。如果有人这样做,风险由她自己承担。The easiest way to do this is using
__slots__
:Instances of
A
are immutable now, since you can't set any attributes on them.If you want the class instances to contain data, you can combine this with deriving from
tuple
:Edit: If you want to get rid of indexing either, you can override
__getitem__()
:Note that you can't use
operator.itemgetter
for the properties in thise case, since this would rely onPoint.__getitem__()
instead oftuple.__getitem__()
. Fuerthermore this won't prevent the use oftuple.__getitem__(p, 0)
, but I can hardly imagine how this should constitute a problem.I don't think the "right" way of creating an immutable object is writing a C extension. Python usually relies on library implementers and library users being consenting adults, and instead of really enforcing an interface, the interface should be clearly stated in the documentation. This is why I don't consider the possibility of circumventing an overridden
__setattr__()
by callingobject.__setattr__()
a problem. If someone does this, it's on her own risk.您可以使用 Cython 创建扩展类型对于 Python:
它适用于 Python 2.x 和 3。
测试
如果您不介意索引支持,则
collections.namedtuple
由 @Sven Marnach 更好:You could use Cython to create an extension type for Python:
It works both Python 2.x and 3.
Tests
If you don't mind indexing support then
collections.namedtuple
suggested by @Sven Marnach is preferrable:另一个想法是完全禁止
__setattr__
并在构造函数中使用object.__setattr__
:当然你可以使用
object.__setattr__(p, "x", 3 )
来修改Point
实例p
,但是您的原始实现遇到了同样的问题(尝试tuple.__setattr__(i, "x", 42)
在Immutable
实例上)。您可以在原始实现中应用相同的技巧:摆脱
__getitem__()
,并在属性函数中使用tuple.__getitem__()
。Another idea would be to completely disallow
__setattr__
and useobject.__setattr__
in the constructor:Of course you could use
object.__setattr__(p, "x", 3)
to modify aPoint
instancep
, but your original implementation suffers from the same problem (trytuple.__setattr__(i, "x", 42)
on anImmutable
instance).You can apply the same trick in your original implementation: get rid of
__getitem__()
, and usetuple.__getitem__()
in your property functions.您可以创建一个
@immutable
装饰器,它可以覆盖__setattr__
并将__slots__
更改为空列表,然后用它来装饰__init__
方法。编辑:正如OP所指出的,更改 __slots__ 属性只会阻止创建新属性,而不是修改。
Edit2:这是一个实现:
Edit3:使用 __slots__ 会破坏此代码,因为 if 会停止创建对象的 __dict__ 。我正在寻找替代方案。
编辑4:嗯,就是这样。虽然有点黑客,但可以作为练习:-)
You could create a
@immutable
decorator that either overrides the__setattr__
and change the__slots__
to an empty list, then decorate the__init__
method with it.Edit: As the OP noted, changing the
__slots__
attribute only prevents the creation of new attributes, not the modification.Edit2: Here's an implementation:
Edit3: Using
__slots__
breaks this code, because if stops the creation of the object's__dict__
. I'm looking for an alternative.Edit4: Well, that's it. It's a but hackish, but works as an exercise :-)
我认为除了使用元组或命名元组之外,这是完全可能的。无论如何,如果您重写
__setattr__()
,用户始终可以通过直接调用object.__setattr__()
来绕过它。任何依赖于__setattr__
的解决方案都保证不起作用。以下是您在不使用某种元组的情况下可以获得的最接近的值:
但如果您足够努力,它就会崩溃:
但是 Sven 对
namedtuple
的使用确实是不可变的。更新
由于问题已更新为询问如何在 C 中正确执行此操作,这是我关于如何在 Cython 中正确执行此操作的答案:
首先
immutable.pyx
:和
setup.py
编译它(使用命令setup.py build_ext --inplace
:然后尝试一下:
I don't think it is entirely possible except by using either a tuple or a namedtuple. No matter what, if you override
__setattr__()
the user can always bypass it by callingobject.__setattr__()
directly. Any solution that depends on__setattr__
is guaranteed not to work.The following is about the nearest you can get without using some sort of tuple:
but it breaks if you try hard enough:
but Sven's use of
namedtuple
is genuinely immutable.Update
Since the question has been updated to ask how to do it properly in C, here's my answer on how to do it properly in Cython:
First
immutable.pyx
:and a
setup.py
to compile it (using the commandsetup.py build_ext --inplace
:Then to try it out:
我通过重写 __setattr__ 来创建不可变类,并在调用者是 __init__ 时允许设置:
这还不够,因为它允许任何人的 __init__ 来改变对象,但你明白了。
I've made immutable classes by overriding
__setattr__
, and allowing the set if the caller is__init__
:This isn't quite enough yet, since it allows anyone's
__init__
to change the object, but you get the idea.这是一个优雅的解决方案:
从此类继承,在构造函数中初始化您的字段,然后就一切就绪了。
Here's an elegant solution:
Inherit from this class, initialize your fields in the constructor, and you'e all set.