如何在Python中创建不可变对象?

时间:2011-01-28 12:14:07

标签: python python-3.x immutability

虽然我从来没有需要这个,但让我感到震惊的是在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

但是,您可以通过ab访问self[0]self[1]变量,这很烦人。

纯Python中有可能吗?如果没有,我将如何使用C扩展名?

(仅适用于Python 3的答案可以接受)。

更新:

所以子类化元组是在纯Python中实现它的方法,除了通过[0][1]等访问数据的额外可能性之外,它运行良好。所以,要完成这个问题所有这些缺少的是如何在C中“正确”地做到这一点,我怀疑这很简单,只是没有实现任何geititemsetattribute等等。但我不是自己做,而是提供赏金为此,因为我很懒。 :)

24 个答案:

答案 0 :(得分:100)

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

Immutable = collections.namedtuple("Immutable", ["a", "b"])

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

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

答案 1 :(得分:71)

最简单的方法是使用__slots__

class A(object):
    __slots__ = []

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

如果您希望类实例包含数据,您可以将其与从tuple派生:

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__()

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,而不是真正强制执行接口,应该在文档中明确说明接口。这就是为什么我不考虑通过调用__setattr__()问题绕过被覆盖的object.__setattr__()的可能性。如果有人这样做,那将是她自己的风险。

答案 2 :(得分:49)

  

..如何在C中“正确”地做到这一点。

您可以使用Cython为Python创建扩展类型:

cdef class Immutable:
    cdef readonly object a, b
    cdef object __weakref__ # enable weak referencing support

    def __init__(self, a, b):
        self.a, self.b = a, b

它适用于Python 2.x和3.

测试

# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable

o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2

try: o.a = 3
except AttributeError:
    pass
else:
    assert 0, 'attribute must be readonly'

try: o[1]
except TypeError:
    pass
else:
    assert 0, 'indexing must not be supported'

try: o.c = 1
except AttributeError:
    pass
else:
    assert 0, 'no new attributes are allowed'

o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []

o.b.append(3) # attribute may contain mutable object
assert o.b == [3]

try: o.c
except AttributeError:
    pass
else:
    assert 0, 'no c attribute'

o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3

try: del o.b
except AttributeError:
    pass
else:
    assert 0, "can't delete attribute"

d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']

o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3

try: object.__setattr__(o, 'a', 1)
except AttributeError:
    pass
else:
    assert 0, 'attributes are readonly'

try: object.__setattr__(o, 'c', 1)
except AttributeError:
    pass
else:
    assert 0, 'no new attributes'

try: Immutable(1,c=3)
except TypeError:
    pass
else:
    assert 0, 'accept only a,b keywords'

for kwd in [dict(a=1), dict(b=2)]:
    try: Immutable(**kwd)
    except TypeError:
        pass
    else:
        assert 0, 'Immutable requires exactly 2 arguments'

如果您不介意索引支持,那么collections.namedtuple建议的@Sven Marnach是可取的:

Immutable = collections.namedtuple("Immutable", "a b")

答案 3 :(得分:38)

另一个想法是完全禁止__setattr__并在构造函数中使用object.__setattr__

class Point(object):
    def __init__(self, x, y):
        object.__setattr__(self, "x", x)
        object.__setattr__(self, "y", y)
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError

当然,您可以使用object.__setattr__(p, "x", 3)修改Point实例p,但您的原始实施会遇到同样的问题(在{{1}上尝试tuple.__setattr__(i, "x", 42) }实例)。

您可以在原始实现中应用相同的技巧:摆脱Immutable,并在属性函数中使用__getitem__()

答案 4 :(得分:18)

您可以创建@immutable装饰器,覆盖__setattr__ __slots__更改为空列表,然后装饰__init__用它的方法。

编辑:正如OP指出的那样,更改__slots__属性只会阻止创建新属性,而不是修改。

Edit2:这是一个实现:

Edit3:使用__slots__打破此代码,因为如果停止创建对象的__dict__。我正在寻找替代方案。

编辑4:嗯,就是这样。这是一个但是很神圣,但是作为一种练习: - )

class immutable(object):
    def __init__(self, immutable_params):
        self.immutable_params = immutable_params

    def __call__(self, new):
        params = self.immutable_params

        def __set_if_unset__(self, name, value):
            if name in self.__dict__:
                raise Exception("Attribute %s has already been set" % name)

            if not name in params:
                raise Exception("Cannot create atribute %s" % name)

            self.__dict__[name] = value;

        def __new__(cls, *args, **kws):
            cls.__setattr__ = __set_if_unset__

            return super(cls.__class__, cls).__new__(cls, *args, **kws)

        return __new__

class Point(object):
    @immutable(['x', 'y'])
    def __new__(): pass

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2) 
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z

答案 5 :(得分:10)

除非使用元组或命名元组,否则我认为完全不可能。无论如何,如果您覆盖__setattr__(),用户可以通过直接调用object.__setattr__()来绕过它。任何依赖于__setattr__的解决方案都不会起作用。

以下是关于不使用某种元组的最接近的内容:

class Immutable:
    __slots__ = ['a', 'b']
    def __init__(self, a, b):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', b)
    def __setattr__(self, *ignored):
        raise NotImplementedError
    __delattr__ = __setattr__

但如果你足够努力就会中断:

>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2

但是Sven对namedtuple的使用确实是不可改变的。

<强>更新

由于问题已经更新以询问如何在C中正确地执行此操作,以下是我在Cython中如何正确执行此操作的答案:

首先immutable.pyx

cdef class Immutable:
    cdef object _a, _b

    def __init__(self, a, b):
        self._a = a
        self._b = b

    property a:
        def __get__(self):
            return self._a

    property b:
        def __get__(self):
            return self._b

    def __repr__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

setup.py来编译它(使用命令setup.py build_ext --inplace

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("immutable", ["immutable.pyx"])]

setup(
  name = 'Immutable object',
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

然后尝试一下:

>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>      

答案 6 :(得分:4)

除了优秀的其他答案,我还想为python 3.4(或者3.3)添加一个方法。这个答案建立在这个问题的几个前面的答案之上。

在python 3.4中,您可以使用属性 without setters 来创建无法修改的类成员。 (在早期版本中,可以分配给没有setter的属性。)

class A:
    __slots__=['_A__a']
    def __init__(self, aValue):
      self.__a=aValue
    @property
    def a(self):
        return self.__a

你可以像这样使用它:

instance=A("constant")
print (instance.a)

将打印"constant"

但是调用instance.a=10会导致:

AttributeError: can't set attribute

解释:没有setter的属性是python 3.4的最新特性(我认为3.3)。如果您尝试分配给此类属性,则会引发错误。 使用广告位我将membervariables限制为__A_a__a)。

问题:仍然可以分配到_A__ainstance._A__a=2)。但是如果你分配给一个私有变量,这是你自己的错......

但是,

This answer不鼓励使用__slots__。使用其他方法来防止属性创建可能是首选。

答案 7 :(得分:4)

这是一个优雅的解决方案:

class Immutable(object):
    def __setattr__(self, key, value):
        if not hasattr(self, key):
            super().__setattr__(key, value)
        else:
            raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))

从此类继承,在构造函数中初始化字段,一切就绪。

答案 8 :(得分:3)

我通过覆盖__setattr__创建了不可变类,如果调用者是__init__则允许该集:

import inspect
class Immutable(object):
    def __setattr__(self, name, value):
        if inspect.stack()[2][3] != "__init__":
            raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
        object.__setattr__(self, name, value)

这还不够,因为它允许任何人的___init__更改对象,但你明白了。

答案 9 :(得分:3)

如果您对具有行为的对象感兴趣,那么namedtuple 几乎您的解决方案。

如namedtuple documentation的底部所述,您可以从namedtuple派生自己的类;然后,您可以添加所需的行为。

例如(代码直接取自documentation):

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()
    @property
    def hypot(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
        return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

for p in Point(3, 4), Point(14, 5/7):
    print(p)

这将导致:

Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

这种方法适用于Python 3和Python 2.7(也在IronPython上测试过) 唯一的缺点是继承树有点奇怪;但这不是你经常玩的东西。

答案 10 :(得分:2)

这种方式不会阻止object.__setattr__工作,但我仍然觉得它很有用:

class A(object):

    def __new__(cls, children, *args, **kwargs):
        self = super(A, cls).__new__(cls)
        self._frozen = False  # allow mutation from here to end of  __init__
        # other stuff you need to do in __new__ goes here
        return self

    def __init__(self, *args, **kwargs):
        super(A, self).__init__()
        self._frozen = True  # prevent future mutation

    def __setattr__(self, name, value):
        # need to special case setting _frozen.
        if name != '_frozen' and self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__setattr__(name, value)

    def __delattr__(self, name):
        if self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__delattr__(name)

根据用例,您可能需要覆盖更多内容(例如__setitem__)。

答案 11 :(得分:2)

我不久前需要这个,并决定为它制作一个Python包。最初的版本现在在PyPI上:

$ pip install immutable

使用:

>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmitableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1

此处的完整文档:https://github.com/theengineear/immutable

希望它有所帮助,它包含了一个已经讨论过的命名元组,但使实例化变得更加简单。

答案 12 :(得分:2)

-使用冻结的数据类

对于Python 3.7+,您可以将 Data Class frozen=True option一起使用,这是一种非常Python化且可维护的方式来完成您想要的事情。

它看起来像这样:

from dataclasses import dataclass

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

由于数据类的字段需要类型提示,因此我使用了Any from the typing module

-不使用命名元组的原因

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

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__方法是元组的方法,该方法仅比较给定位置的字段的值。这可能是一个巨大的错误源,特别是如果您要对这些类进行子类化。

答案 13 :(得分:2)

从Python 3.7开始,您可以在类中使用@dataclass decorator,它将像结构一样是不可变的!但是,它可能会也可能不会向您的类添加__hash__()方法。引用:

  

哈希()由内置的hash()使用,并且在将对象添加到诸如字典和集合之类的哈希集合中时使用。带有 hash ()表示该类的实例是不可变的。可变性是一个复杂的属性,取决于程序员的意图, eq ()的存在和行为以及dataclass()装饰器中的eq和冻结标志的值。

     

默认情况下,除非安全,否则dataclass()不会隐式添加 hash ()方法。它既不会添加也不会更改现有的显式定义的 hash ()方法。如 hash ()文档中所述,设置类属性 hash = None对Python具有特定的含义。

     

如果未明确定义 hash (),或者将其设置为None,则dataclass()可能会添加隐式的 hash ()方法。尽管不建议这样做,但是您可以强制dataclass()使用unsafe_hash = True创建 hash ()方法。如果您的类在逻辑上是不可变的,但仍然可以进行突变,则可能是这种情况。这是一个特殊的用例,应仔细考虑。

这是上面链接的文档中的示例:

type buscase
baseMVA::Float64
bus::Array{Float64,13}
gen::Array{Float64,21}
branch::Array{Float64,13}
end

mpc=buscase(100.00,
[1  2   0   0   0   0   1   1   0   230 1   1.1 0.9;
2   1   300 98.61   0   0   1   1   0   230 1   1.1 0.9;
3   2   300 98.61   0   0   1   1   0   230 1   1.1 0.9;
4   3   400 131.47  0   0   1   1   0   230 1   1.1 0.9;
5   2   0   0   0   0   1   1   0   230 1   1.1 0.9;],

[1  40  0   30  -30 1   100 1   40  0   0   0   0   0   0   0   0   0   0   0   0;
1   170 0   127.5   -127.5  1   100 1   170 0   0   0   0   0   0   0   0   0   0   0   0;
3   323.49  0   390 -390    1   100 1   520 0   0   0   0   0   0   0   0   0   0   0   0;
4   0   0   150 -150    1   100 1   200 0   0   0   0   0   0   0   0   0   0   0   0;
5   466.51  0   450 -450    1   100 1   600 0   0   0   0   0   0   0   0   0   0   0   0;],

[1  2   0.00281 0.0281  0.00712 400 400 400 0   0   1   -360    360;
1   4   0.00304 0.0304  0.00658 0   0   0   0   0   1   -360    360;
1   5   0.00064 0.0064  0.03126 0   0   0   0   0   1   -360    360;
2   3   0.00108 0.0108  0.01852 0   0   0   0   0   1   -360    360;
3   4   0.00297 0.0297  0.00674 0   0   0   0   0   1   -360    360;
4   5   0.00297 0.0297  0.00674 240 240 240 0   0   1   -360    360;]
)

答案 14 :(得分:1)

所以,我正在编写python 3:

I)在数据类修饰器的帮助下并设置Frozen = True。 我们可以在python中创建不可变的对象。

为此,需要从数据类lib中导入数据类,并需要设置Frozen = True

例如。

从数据类导入数据类

@dataclass(frozen=True)
class Location:
    name: str
    longitude: float = 0.0
    latitude: float = 0.0

o / p:

  
    
      

l = Location(“ Delhi”,112.345,234.788)       名称       德里       经度       112.345       纬度       234.788       l.name =“ Kolkata”数据类。FrozenInstanceError:无法分配给字段“名称”

    
  

来源:https://realpython.com/python-data-classes/

答案 15 :(得分:1)

您可以覆盖 setattr ,并仍然使用 init 设置变量。您将使用超类 setattr 。这是代码。

class Immutable:
    __slots__ = ('a','b')
    def __init__(self, a , b):
        super().__setattr__('a',a)
        super().__setattr__('b',b)

    def __str__(self):
        return "".format(self.a, self.b)

    def __setattr__(self, *ignored):
        raise NotImplementedError

    def __delattr__(self, *ignored):
        raise NotImplementedError

答案 16 :(得分:1)

第三方attr模块提供this functionality

编辑:python 3.7已将此想法用于@dataclass的stdlib。

$ pip install attrs
$ python
>>> @attr.s(frozen=True)
... class C(object):
...     x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
   ...
attr.exceptions.FrozenInstanceError: can't set attribute
根据文档,

attr通过覆盖__setattr__来实现冻结类,并且在每个实例化时间都会产生轻微的性能影响。

如果您习惯使用类作为数据类型,attr可能特别有用,因为它会为您处理样板(但不会产生任何魔法)。特别是,它会为您编写九种dunder(__X__)方法(除非您关闭其中任何一种),包括repr,init,hash和所有比较函数。

attr还提供helper for __slots__

答案 17 :(得分:1)

Immutable方法完成执行后,继承自以下__init__类的类与其实例一样是不可变的。正如其他人所指出的那样,因为它是纯粹的python,所以没有什么可以阻止某人使用基类objecttype中的变异特殊方法,但这足以阻止任何人改变类/实例事故。

它通过使用元类劫持类创建过程来工作。

"""Subclasses of class Immutable are immutable after their __init__ has run, in
the sense that all special methods with mutation semantics (in-place operators,
setattr, etc.) are forbidden.

"""  

# Enumerate the mutating special methods
mutation_methods = set()
# Arithmetic methods with in-place operations
iarithmetic = '''add sub mul div mod divmod pow neg pos abs bool invert lshift
                 rshift and xor or floordiv truediv matmul'''.split()
for op in iarithmetic:
    mutation_methods.add('__i%s__' % op)
# Operations on instance components (attributes, items, slices)
for verb in ['set', 'del']:
    for component in '''attr item slice'''.split():
        mutation_methods.add('__%s%s__' % (verb, component))
# Operations on properties
mutation_methods.update(['__set__', '__delete__'])


def checked_call(_self, name, method, *args, **kwargs):
    """Calls special method method(*args, **kw) on self if mutable."""
    self = args[0] if isinstance(_self, object) else _self
    if not getattr(self, '__mutable__', True):
        # self told us it's immutable, so raise an error
        cname= (self if isinstance(self, type) else self.__class__).__name__
        raise TypeError('%s is immutable, %s disallowed' % (cname, name))
    return method(*args, **kwargs)


def method_wrapper(_self, name):
    "Wrap a special method to check for mutability."
    method = getattr(_self, name)
    def wrapper(*args, **kwargs):
        return checked_call(_self, name, method, *args, **kwargs)
    wrapper.__name__ = name
    wrapper.__doc__ = method.__doc__
    return wrapper


def wrap_mutating_methods(_self):
    "Place the wrapper methods on mutative special methods of _self"
    for name in mutation_methods:
        if hasattr(_self, name):
            method = method_wrapper(_self, name)
            type.__setattr__(_self, name, method)


def set_mutability(self, ismutable):
    "Set __mutable__ by using the unprotected __setattr__"
    b = _MetaImmutable if isinstance(self, type) else Immutable
    super(b, self).__setattr__('__mutable__', ismutable)


class _MetaImmutable(type):

    '''The metaclass of Immutable. Wraps __init__ methods via __call__.'''

    def __init__(cls, *args, **kwargs):
        # Make class mutable for wrapping special methods
        set_mutability(cls, True)
        wrap_mutating_methods(cls)
        # Disable mutability
        set_mutability(cls, False)

    def __call__(cls, *args, **kwargs):
        '''Make an immutable instance of cls'''
        self = cls.__new__(cls)
        # Make the instance mutable for initialization
        set_mutability(self, True)
        # Execute cls's custom initialization on this instance
        self.__init__(*args, **kwargs)
        # Disable mutability
        set_mutability(self, False)
        return self

    # Given a class T(metaclass=_MetaImmutable), mutative special methods which
    # already exist on _MetaImmutable (a basic type) cannot be over-ridden
    # programmatically during _MetaImmutable's instantiation of T, because the
    # first place python looks for a method on an object is on the object's
    # __class__, and T.__class__ is _MetaImmutable. The two extant special
    # methods on a basic type are __setattr__ and __delattr__, so those have to
    # be explicitly overridden here.

    def __setattr__(cls, name, value):
        checked_call(cls, '__setattr__', type.__setattr__, cls, name, value)

    def __delattr__(cls, name, value):
        checked_call(cls, '__delattr__', type.__delattr__, cls, name, value)


class Immutable(object):

    """Inherit from this class to make an immutable object.

    __init__ methods of subclasses are executed by _MetaImmutable.__call__,
    which enables mutability for the duration.

    """

    __metaclass__ = _MetaImmutable


class T(int, Immutable):  # Checks it works with multiple inheritance, too.

    "Class for testing immutability semantics"

    def __init__(self, b):
        self.b = b

    @classmethod
    def class_mutation(cls):
        cls.a = 5

    def instance_mutation(self):
        self.c = 1

    def __iadd__(self, o):
        pass

    def not_so_special_mutation(self):
        self +=1

def immutabilityTest(f, name):
    "Call f, which should try to mutate class T or T instance."
    try:
        f()
    except TypeError, e:
        assert 'T is immutable, %s disallowed' % name in e.args
    else:
        raise RuntimeError('Immutability failed!')

immutabilityTest(T.class_mutation, '__setattr__')
immutabilityTest(T(6).instance_mutation, '__setattr__')
immutabilityTest(T(6).not_so_special_mutation, '__iadd__')

答案 18 :(得分:0)

您可以在init的最终语句中覆盖setAttr。你可以建造但不能改变。显然你仍然可以通过usint对象覆盖。 setAttr 但实际上大多数语言都有某种形式的反射,所以不可变性总是一个漏洞的抽象。不变性更多的是防止客户意外违反对象的合同。我用:

=============================

提供的原始解决方案不正确,使用here

中的解决方案根据评论进行了更新

原始解决方案以有趣的方式出错,因此它包含在底部。

===============================

class ImmutablePair(object):

    __initialised = False # a class level variable that should always stay false.
    def __init__(self, a, b):
        try :
            self.a = a
            self.b = b
        finally:
            self.__initialised = True #an instance level variable

    def __setattr__(self, key, value):
        if self.__initialised:
            self._raise_error()
        else :
            super(ImmutablePair, self).__setattr__(key, value)

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

if __name__ == "__main__":

    immutable_object = ImmutablePair(1,2)

    print immutable_object.a
    print immutable_object.b

    try :
        immutable_object.a = 3
    except Exception as e:
        print e

    print immutable_object.a
    print immutable_object.b

输出:

1
2
Attempted To Modify Immutable Object
1
2

======================================

原始实施:

在评论中正确地指出,这实际上并不起作用,因为它会阻止创建多个对象,因为你重写了类setattr方法,这意味着第二个不能创建为self。 a =将在第二次初始化时失败。

class ImmutablePair(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b
        ImmutablePair.__setattr__ = self._raise_error

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

答案 19 :(得分:0)

这里没有真正包含的一件事是完全不变性......不仅仅是父对象,还有所有孩子。例如,元组/ frozensets可能是不可变的,但它所属的对象可能不是。这是一个很小的(不完整的)版本,可以很好地执行不变性:

li a {
  text-decoration:none;
}

li a:hover {
    text-decoration: underline;
}

答案 20 :(得分:0)

以下基本解决方案可解决以下情况:

    可以像往常一样写
  • __init__()来访问属性。
  • 仅针对属性更改冻结对象之后:

想法是重写__setattr__方法并在每次对象冻结状态更改时替换其实现。

因此,我们需要一种方法(_freeze),该方法存储这两种实现并在需要时在它们之间切换。

此机制可以在用户类内部实现,也可以从特殊的Freezer类继承,如下所示:

class Freezer:
    def _freeze(self, do_freeze=True):
        def raise_sa(*args):            
            raise AttributeError("Attributes are frozen and can not be changed!")
        super().__setattr__('_active_setattr', (super().__setattr__, raise_sa)[do_freeze])

    def __setattr__(self, key, value):        
        return self._active_setattr(key, value)

class A(Freezer):    
    def __init__(self):
        self._freeze(False)
        self.x = 10
        self._freeze()

答案 21 :(得分:0)

我使用与Alex相同的想法:一个元类和一个&#34; init标记&#34;,但结合覆盖__setattr __:

>>> from abc import ABCMeta
>>> _INIT_MARKER = '_@_in_init_@_'
>>> class _ImmutableMeta(ABCMeta):
... 
...     """Meta class to construct Immutable."""
... 
...     def __call__(cls, *args, **kwds):
...         obj = cls.__new__(cls, *args, **kwds)
...         object.__setattr__(obj, _INIT_MARKER, True)
...         cls.__init__(obj, *args, **kwds)
...         object.__delattr__(obj, _INIT_MARKER)
...         return obj
...
>>> def _setattr(self, name, value):
...     if hasattr(self, _INIT_MARKER):
...         object.__setattr__(self, name, value)
...     else:
...         raise AttributeError("Instance of '%s' is immutable."
...                              % self.__class__.__name__)
...
>>> def _delattr(self, name):
...     raise AttributeError("Instance of '%s' is immutable."
...                          % self.__class__.__name__)
...
>>> _im_dict = {
...     '__doc__': "Mix-in class for immutable objects.",
...     '__copy__': lambda self: self,   # self is immutable, so just return it
...     '__setattr__': _setattr,
...     '__delattr__': _delattr}
...
>>> Immutable = _ImmutableMeta('Immutable', (), _im_dict)

注意:我直接调用元类,使其适用于Python 2.x和3.x.

>>> class T1(Immutable):
... 
...     def __init__(self, x=1, y=2):
...         self.x = x
...         self.y = y
...
>>> t1 = T1(y=8)
>>> t1.x, t1.y
(1, 8)
>>> t1.x = 7
AttributeError: Instance of 'T1' is immutable.

它也适用于插槽......:

>>> class T2(Immutable):
... 
...     __slots__ = 's1', 's2'
... 
...     def __init__(self, s1, s2):
...         self.s1 = s1
...         self.s2 = s2
...
>>> t2 = T2('abc', 'xyz')
>>> t2.s1, t2.s2
('abc', 'xyz')
>>> t2.s1 += 'd'
AttributeError: Instance of 'T2' is immutable.

...和多重继承:

>>> class T3(T1, T2):
... 
...     def __init__(self, x, y, s1, s2):
...         T1.__init__(self, x, y)
...         T2.__init__(self, s1, s2)
...
>>> t3 = T3(12, 4, 'a', 'b')
>>> t3.x, t3.y, t3.s1, t3.s2
(12, 4, 'a', 'b')
>>> t3.y -= 3
AttributeError: Instance of 'T3' is immutable.

但请注意,可变属性保持可变:

>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]

答案 22 :(得分:0)

就像dict

我有一个开放源代码库,在其中我以功能的方式工作,因此在不可变对象中移动数据很有帮助。但是,我不需要为客户端与客户端交互而转换我的数据对象。因此,我想出了这一点-它为您提供了一个不变的对象之类的字典 +一些辅助方法。

在其Sven Marnach中向answer致谢,以获取限制属性更新和删除的基本实现。

import json 
# ^^ optional - If you don't care if it prints like a dict
# then rip this and __str__ and __repr__ out

class Immutable(object):

    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k,v in kwargs.items():
            object.__setattr__(self, k, v)

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')

    def __getitem__(self, item):
        """Allows for dict like access of properties
        val = item['prop']
        """
        return self.__dict__[item]

    def __repr__(self):
        """Print to repl in a dict like fashion"""
        return self.pprint()

    def __str__(self):
        """Convert to a str in a dict like fashion"""
        return self.pprint()

    def __eq__(self, other):
        """Supports equality operator
        immutable({'a': 2}) == immutable({'a': 2})"""
        if other is None:
            return False
        return self.dict() == other.dict()

    def keys(self):
        """Paired with __getitem__ supports **unpacking
        new = { **item, **other }
        """
        return self.__dict__.keys()

    def get(self, *args, **kwargs):
        """Allows for dict like property access
        item.get('prop')
        """
        return self.__dict__.get(*args, **kwargs)

    def pprint(self):
        """Helper method used for printing that
        formats in a dict like way
        """
        return json.dumps(self,
            default=lambda o: o.__dict__,
            sort_keys=True,
            indent=4)

    def dict(self):
        """Helper method for getting the raw dict value
        of the immutable object"""
        return self.__dict__

Helper方法

def update(obj, **kwargs):
    """Returns a new instance of the given object with
    all key/val in kwargs set on it
    """
    return immutable({
        **obj,
        **kwargs
    })

def immutable(obj):
    return Immutable(**obj)

示例

obj = immutable({
    'alpha': 1,
    'beta': 2,
    'dalet': 4
})

obj.alpha # 1
obj['alpha'] # 1
obj.get('beta') # 2

del obj['alpha'] # TypeError
obj.alpha = 2 # TypeError

new_obj = update(obj, alpha=10)

new_obj is not obj # True
new_obj.get('alpha') == 10 # True

答案 23 :(得分:0)

另一种方法是创建一个使实例不可变的包装器。

class Immutable(object):

    def __init__(self, wrapped):
        super(Immutable, self).__init__()
        object.__setattr__(self, '_wrapped', wrapped)

    def __getattribute__(self, item):
        return object.__getattribute__(self, '_wrapped').__getattribute__(item)

    def __setattr__(self, key, value):
        raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))

    __delattr__ = __setattr__

    def __iter__(self):
        return object.__getattribute__(self, '_wrapped').__iter__()

    def next(self):
        return object.__getattribute__(self, '_wrapped').next()

    def __getitem__(self, item):
        return object.__getattribute__(self, '_wrapped').__getitem__(item)

immutable_instance = Immutable(my_instance)

这在只有一些实例必须是不可变的情况下很有用(比如函数调用的默认参数)。

也可用于不可变的工厂,如:

@classmethod
def immutable_factory(cls, *args, **kwargs):
    return Immutable(cls.__init__(*args, **kwargs))

同样可以防止object.__setattr__,但由于Python的动态特性而导致其他技巧失效。