How to make an immutable object in Python? How to make an immutable object in Python? python python

How to make an immutable object in Python?


Yet another solution I just thought of: The simplest way to get the same behaviour as your original code is

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

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 with pickle and copy.

namedtuple creates a type similar to what I described in this answer, i.e. derived from tuple and using __slots__. It is available in Python 2.6 or above.


The easiest way to do this is using __slots__:

class A(object):    __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:

from operator import itemgetterclass 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# 2p.y# 3

Edit: If you want to get rid of indexing either, you can override __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

Note that you can't use operator.itemgetter for the properties in thise case, since this would rely on Point.__getitem__() instead of tuple.__getitem__(). Fuerthermore this won't prevent the use of tuple.__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 calling object.__setattr__() a problem. If someone does this, it's on her own risk.


..howto do it "properly" in C..

You could use Cython to create an extension type for 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

It works both Python 2.x and 3.

Tests

# compile on-the-flyimport pyximport; pyximport.install() # $ pip install cythonfrom immutable import Immutableo = Immutable(1, 2)assert o.a == 1, str(o.a)assert o.b == 2try: o.a = 3except AttributeError:    passelse:    assert 0, 'attribute must be readonly'try: o[1]except TypeError:    passelse:    assert 0, 'indexing must not be supported'try: o.c = 1except AttributeError:    passelse:    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 objectassert o.b == [3]try: o.cexcept AttributeError:    passelse:    assert 0, 'no c attribute'o = Immutable(b=3,a=1)assert o.a == 1 and o.b == 3try: del o.bexcept AttributeError:    passelse:    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 == 3try: object.__setattr__(o, 'a', 1)except AttributeError:    passelse:    assert 0, 'attributes are readonly'try: object.__setattr__(o, 'c', 1)except AttributeError:    passelse:    assert 0, 'no new attributes'try: Immutable(1,c=3)except TypeError:    passelse:    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'

If you don't mind indexing support then collections.namedtuple suggested by @Sven Marnach is preferrable:

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