首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何在Python中创建一个不可变的对象?

如何在Python中创建一个不可变的对象?
EN

Stack Overflow用户
提问于 2011-01-28 20:14:07
回答 24查看 103.6K关注 0票数 214

尽管我从来不需要这个,但我突然意识到,在Python中创建一个不可变的对象可能有些棘手。您不能只覆盖__setattr__,因为那样您甚至不能在__init__中设置属性。对元组进行子类化是一个有效的技巧:

代码语言:javascript
复制
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]self[1]访问ab变量,这很烦人。

这在Pure Python中是可能的吗?如果不是,我该如何使用C扩展呢?

(只能在Python 3中工作的答案是可以接受的)。

更新:

从Python3.7开始,方法是使用@dataclass装饰器,请参阅新接受的答案。

EN

回答 24

Stack Overflow用户

回答已采纳

发布于 2019-11-22 18:57:42

使用冻结的数据类

对于Python 3.7+,您可以使用带有frozen=True option的,这是一种非常Python风格且可维护的方式来做您想做的事情。

它看起来像这样:

代码语言:javascript
复制
from dataclasses import dataclass

@dataclass(frozen=True)
class Immutable:
    a: Any
    b: Any

因为数据类的字段需要类型提示,所以我使用了Any from the typing module

不使用命名元组的原因

在Python3.7之前,经常会看到命名元组被用作不可变对象。这可能在很多方面都很棘手,其中之一是命名元组之间的__eq__方法不考虑对象的类。例如:

代码语言:javascript
复制
from collections import namedtuple

ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])

obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)

obj1 == obj2  # will be True

正如您所看到的,即使obj1obj2的类型不同,即使它们的字段名称不同,obj1 == obj2仍然提供True。这是因为使用的__eq__方法是元组的方法,它只比较给定位置的字段的值。这可能是一个巨大的错误来源,特别是当您对这些类进行子类化时。

票数 46
EN

Stack Overflow用户

发布于 2011-01-28 21:39:09

我刚刚想到的另一个解决方案是:获得与原始代码相同的行为的最简单方法是

代码语言:javascript
复制
Immutable = collections.namedtuple("Immutable", ["a", "b"])

它没有解决可以通过[0]等访问属性的问题,但至少它相当短,并提供了与picklecopy兼容的额外优势。

namedtuple创建了一个类似于我在this answer中描述的类型,即从tuple派生并使用__slots__。Python 2.6或更高版本中提供。

票数 128
EN

Stack Overflow用户

发布于 2011-01-28 20:17:03

最简单的方法是使用__slots__

代码语言:javascript
复制
class A(object):
    __slots__ = []

A的实例现在是不可变的,因为您不能为它们设置任何属性。

如果您希望类实例包含数据,则可以将其与从tuple派生结合使用

代码语言:javascript
复制
from operator import itemgetter
class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    x = property(itemgetter(0))
    y = property(itemgetter(1))

p = Point(2, 3)
p.x
# 2
p.y
# 3

编辑:如果您也想摆脱索引,您可以覆盖__getitem__()

代码语言:javascript
复制
class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    @property
    def x(self):
        return tuple.__getitem__(self, 0)
    @property
    def y(self):
        return tuple.__getitem__(self, 1)
    def __getitem__(self, item):
        raise TypeError

注意,在这种情况下,您不能对属性使用operator.itemgetter,因为这将依赖于Point.__getitem__()而不是tuple.__getitem__()。此外,这不会阻止tuple.__getitem__(p, 0)的使用,但我很难想象这会成为一个问题。

我不认为创建不可变对象的“正确”方法是编写C扩展。Python通常依赖于库实现者和库用户是consenting adults,而不是真正强制接口,接口应该在文档中清楚地说明。这就是为什么我不认为通过调用object.__setattr__()来规避被覆盖的__setattr__()的可能性是一个问题。如果有人这么做,那是她自己承担的风险。

票数 90
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4828080

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档