虽然我从来都不需要这样做,但我突然意识到用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]和self[1]访问a和b变量,这很烦人。

这在Pure Python中可行吗?如果不是,我该如何用C扩展来做呢?

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

更新:

从Python 3.7开始,要使用的方法是使用@dataclass装饰器,参见最新接受的答案。


当前回答

所以,我在写python 3的相关内容:

I)借助数据类装饰器并设置frozen=True。 我们可以在python中创建不可变对象。

为此需要从data classes lib导入data class,并需要设置frozen=True

ex.

从数据类导入数据类

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

o/p:

>>> l = Location("Delhi", 112.345, 234.788)
>>> l.name
'Delhi'
>>> l.longitude
112.345
>>> l.latitude
234.788
>>> l.name = "Kolkata"
dataclasses.FrozenInstanceError: cannot assign to field 'name'
>>> 

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

其他回答

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

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

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

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

我找到了一种方法,不用子类化tuple, namedtuple等。你所需要做的就是在初始化后禁用setattr和delattr(如果你想让一个集合成为不可变的,也要禁用setitem和delitem):

def __init__(self, *args, **kwargs):
    # something here

    self.lock()

其中lock可以是这样的:

@classmethod
def lock(cls):
    def raiser(*a):
        raise TypeError('this instance is immutable')

    cls.__setattr__ = raiser
    cls.__delattr__ = raiser
    if hasattr(cls, '__setitem__'):
        cls.__setitem__ = raiser
        cls.__delitem__ = raiser

你可以用这个方法创建类Immutable,并像我展示的那样使用它。

如果你不想在每个init中都写self.lock(),你可以用元类自动实现:

class ImmutableType(type):
    @classmethod
    def change_init(mcs, original_init_method):
        def __new_init__(self, *args, **kwargs):
            if callable(original_init_method):
                original_init_method(self, *args, **kwargs)

            cls = self.__class__

            def raiser(*a):
                raise TypeError('this instance is immutable')

            cls.__setattr__ = raiser
            cls.__delattr__ = raiser
            if hasattr(cls, '__setitem__'):
                cls.__setitem__ = raiser
                cls.__delitem__ = raiser

        return __new_init__

    def __new__(mcs, name, parents, kwargs):
        kwargs['__init__'] = mcs.change_init(kwargs.get('__init__'))
        return type.__new__(mcs, name, parents, kwargs)


class Immutable(metaclass=ImmutableType):
    pass

Test

class SomeImmutableClass(Immutable):
    def __init__(self, some_value: int):
        self.important_attr = some_value

    def some_method(self):
        return 2 * self.important_attr


ins = SomeImmutableClass(3)
print(ins.some_method())  # 6
ins.important_attr += 1  # TypeError
ins.another_attr = 2  # TypeError

..如何在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'

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

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

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

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

也保护对象。__setattr__,但由于Python的动态特性,可能会被其他技巧所绊倒。

我刚才需要这个,并决定为它做一个Python包。最初的版本现在在PyPI上:

$ pip install immutable

使用方法:

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

完整的文档在这里:https://github.com/theengineear/immutable

希望它有帮助,它包装了一个namedtuple,但使实例化更简单。