Как создать неизменяемый объект в 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

Но тогда у вас есть доступ к переменным a и b через self[0] и self[1], что раздражает.

Возможно ли это в Pure Python? Если нет, как мне сделать это с расширением C?

(Ответы, которые работают только на Python 3, приемлемы).

Обновление:

Так что подклассифицирующий кортеж - это способ сделать это в Pure Python, который хорошо работает, за исключением дополнительной возможности доступа к данным с помощью [0], [1] и т.д. Итак, для завершения этого вопроса все, что отсутствует, сделайте это "правильно" на C, который, как я подозреваю, будет довольно простым, просто не реализуя никаких geititem или setattribute и т.д. Но вместо того, чтобы делать это сам, я предлагаю щедрость за это, потому что я ленив,:)

Ответ 1

Еще одно решение, о котором я только подумал: самый простой способ получить то же поведение, что и исходный код,

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

Он не решает проблему, с которой доступны атрибуты через [0] и т.д., но по крайней мере значительно короче и обеспечивает дополнительное преимущество совместимости с pickle и copy.

namedtuple создает тип, подобный тому, что я описал в этом ответе, т.е. получена из tuple и используя __slots__. Он доступен в Python 2.6 или выше.

Ответ 2

Самый простой способ сделать это - использовать __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__(). Fuerthermore это не помешает использованию tuple.__getitem__(p, 0), но я не могу себе представить, как это должно стать проблемой.

Я не думаю, что "правильный" способ создания неизменяемого объекта - писать расширение C. Python обычно полагается на разработчиков библиотек, а пользователи библиотек соглашаются со взрослыми, и вместо реального обеспечения интерфейса интерфейс должен быть четко указан в документации. Вот почему я не рассматриваю возможность обхода переопределенного __setattr__() путем вызова object.__setattr__() проблемы. Если кто-то это сделает, это на свой страх и риск.

Ответ 3

.. как сделать это "правильно" в 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")

Ответ 4

Другой идеей было бы полностью запретить __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, но исходная реализация страдает одной и той же проблемой (попробуйте tuple.__setattr__(i, "x", 42) в экземпляре Immutable).

Вы можете применить тот же трюк в своей первоначальной реализации: избавиться от __getitem__() и использовать tuple.__getitem__() в ваших функциях свойств.

Ответ 5

Вы можете создать декоратор @immutable, который либо переопределит __setattr__, либо изменит __slots__ на пустой список, а затем украсит его __init__.

Изменить: как отметил OP, изменение атрибута __slots__ предотвращает создание новых атрибутов, а не модификацию.

Edit2: Здесь реализована реализация:

Edit3: Использование __slots__ нарушает этот код, потому что если останавливается создание объекта __dict__. Я ищу альтернативу.

Edit4: Ну, это так. Это но хакерское, но работает как упражнение: -)

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

Ответ 6

Я не думаю, что это возможно, только с использованием кортежа или именованного набора. Независимо от того, что, если вы переопределяете __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)

и a 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)
>>>      

Ответ 7

В дополнение к отличным другим ответам я хотел бы добавить метод для python 3.4 (или, может быть, 3.3). Этот ответ основывается на нескольких предыдущих ответах на этот вопрос.

В python 3.4 вы можете использовать свойства без seters для создания членов класса, которые не могут быть изменены. (В более ранних версиях возможно присвоение свойств без сеттера.)

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

Объяснение: свойства без сеттеров - очень недавняя функция python 3.4 (и я думаю, 3.3). Если вы попытаетесь присвоить такое свойство, будет вызвана ошибка. Используя слоты, я ограничиваю membervariables __A_a (который равен __a).

Проблема. Назначение _A__a по-прежнему возможно (instance._A__a=2). Но если вы назначаете частную переменную, это ваша собственная ошибка...

Этот ответ среди других, однако, препятствует использованию __slots__. Использование других способов предотвращения создания атрибутов может быть предпочтительным.

Ответ 8

Я создал неизменяемые классы, переопределив __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

Этот способ не останавливает 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__) в зависимости от варианта использования.

Ответ 10

Мне нужно было это немного назад и решил сделать для него пакет 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

Надеюсь, что это поможет, он обертывает namedtuple, как обсуждалось, но упрощает создание экземпляра.

Ответ 11

Если вас интересуют объекты с поведением, то namedtuple - это почти ваше решение.

Как описано в нижней части документа namedaupup , вы можете получить свой собственный класс из namedtuple; и затем вы можете добавить нужное поведение.

Например (код, взятый непосредственно из документации):

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).
Единственным недостатком является то, что дерево наследования немного странно; но это не то, с чем вы обычно играете.

Ответ 12

Классы, которые наследуют следующий класс Immutable, неизменяемы, как и их экземпляры, после завершения метода __init__. Поскольку это чистый питон, как указывали другие, ничего не мешает кому-либо использовать мутирующие специальные методы из базы object и type, но этого достаточно, чтобы остановить кого-либо из мутаций класса/экземпляра случайно.

Он работает путем захвата процесса создания класса с помощью метакласса.

"""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 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 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 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__')

Ответ 13

Сторонний attr модуль предоставляет эту функциональность.

$ 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__) (если вы не отключите их), включая реестр, init, хэш и все функции сравнения.

attr также предоставляет помощник для __slots__.

Ответ 14

Альтернативный подход - создать оболочку, которая делает экземпляр неизменным.

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.

Ответ 15

Я использовал ту же идею, что и Alex: мета-класс и "маркер init", но в сочетании с надписью __setattr __:

>>> from abc import ABCMeta
>>> _INIT_MARKER = '[email protected][email protected]_'
>>> 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]

Ответ 16

Одна вещь, которая на самом деле не включена здесь, - это полная неизменность... не только родительский объект, но и все дети. Например, кортежи/фризоцеты могут быть неизменными, но объекты, из которых он не может быть частью. Здесь небольшая (неполная) версия, которая делает достойную работу по обеспечению неизменности до конца:

# Initialize lists
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]

l = [a,b]

# We can reassign in a list 
l[0] = c

# But not a tuple
t = (a,b)
#t[0] = c -> Throws exception
# But elements can be modified
t[0][1] = 4
t
([1, 4, 3], [4, 5, 6])
# Fix it back
t[0][1] = 2

li = ImmutableObject(l)
li
[[1, 2, 3], [4, 5, 6]]
# Can't assign
#li[0] = c will fail
# Can reference
li[0]
[1, 2, 3]
# But immutability conferred on returned object too
#li[0][1] = 4 will throw an exception

# Full solution should wrap all the comparison e.g. decorators.
# Also, you'd usually want to add a hash function, i didn't put
# an interface for that.

class ImmutableObject(object):
    def __init__(self, inobj):
        self._inited = False
        self._inobj = inobj
        self._inited = True

    def __repr__(self):
        return self._inobj.__repr__()

    def __str__(self):
        return self._inobj.__str__()

    def __getitem__(self, key):
        return ImmutableObject(self._inobj.__getitem__(key))

    def __iter__(self):
        return self._inobj.__iter__()

    def __setitem__(self, key, value):
        raise AttributeError, 'Object is read-only'

    def __getattr__(self, key):
        x = getattr(self._inobj, key)
        if callable(x):
              return x
        else:
              return ImmutableObject(x)

    def __hash__(self):
        return self._inobj.__hash__()

    def __eq__(self, second):
        return self._inobj.__eq__(second)

    def __setattr__(self, attr, value):
        if attr not in  ['_inobj', '_inited'] and self._inited == True:
            raise AttributeError, 'Object is read-only'
        object.__setattr__(self, attr, value)

Ответ 17

Вы можете просто переопределить setAttr в финальной инструкции init. Вы можете построить, но не изменить. Очевидно, что вы все еще можете переопределить объект usint. setAttr, но на практике большинство языков имеют некоторую форму отражения, поэтому нерушимость всегда является негерметичной абстракцией. Неизменность больше связана с тем, что клиенты случайно не нарушают контракт объекта. Я использую:

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

Исходное предлагаемое решение было неверным, оно было обновлено на основе комментариев с помощью решения здесь

Исходное решение является неправильным интересным способом, поэтому оно включено внизу.

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

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

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

Исходная реализация:

В комментариях было правильно указано, что это на самом деле не работает, поскольку это предотвращает создание более чем одного объекта, поскольку вы переопределяете метод classattattr, что означает, что вторая не может быть создана как "я". 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")