是否有推荐的方法来确保不变性

Is there a recommended way of ensuring immutability

我正在观察以下行为,因为 python 通过引用传递对象?

class Person(object):
    pass

person = Person()

person.name = 'UI'
def test(person):
    person.name = 'Test'

test(person)
print(person.name)

>>> Test

我发现 copy.deepcopy() 可以深度复制对象以防止修改传递的对象。还有其他建议吗?

import copy

class Person(object):
    pass

person = Person()

person.name = 'UI'
def test(person):
    person_copy = copy.deepcopy(person)
    person_copy.name = 'Test'

test(person)
print(person.name)

>>> UI


I am observing following behavior since python passes object by reference?

不是真的。这是一个微妙的问题。你可以看看python - How do I pass a variable by reference? - Stack Overflow

就我个人而言,我并不完全同意接受的答案并推荐您google call by sharing。然后,你可以在这个微妙的问题上做出自己的决定。

I found copy.deepcopy() to deepcopy object to prevent modifying the passed object. Are there any other recommendations ?

据我所知,如果不使用第三个包,没有其他更好的方法。

您可以使用 __setattr__ 魔术方法来实现一个基础 class,它允许您在完成后 "freeze" 一个对象。

这不是bullet-proof;你仍然可以访问 __dict__ 来改变对象,你也可以通过取消设置 _frozen 来解冻对象,如果属性的值本身是可变的,这没有多大帮助(x.things.append('x')将适用于 things).

的列表
class Freezable:
    def freeze(self):
        self._frozen = True

    def __setattr__(self, key, value):
        if getattr(self, "_frozen", False):
            raise RuntimeError("%r is frozen" % self)
        super().__setattr__(key, value)


class Person(Freezable):
    def __init__(self, name):
        self.name = name


p = Person("x")
print(p.name)
p.name = "y"
print(p.name)
p.freeze()
p.name = "q"

产出

x
y
Traceback (most recent call last):
  File "freezable.py", line 21, in <module>
    p.name = 'q'
RuntimeError: <__main__.Person object at 0x10f82f3c8> is frozen

没有真正 100% 无懈可击的方法,但是您可以使您难以无意中改变要冻结的对象;对于大多数人来说,推荐的方法可能是使用冷冻 DataClass 或冷冻 attrs class

在他的talk on DataClasses (2018), @RaymonHettinger mentions three approaches: one way, is with a metaclass, another, like in the fractions module是给属性一个read only 属性; DataClass 模块 扩展了 __setattr____delattr__,并覆盖了 __hash__:

-> 使用元class.

好的资源包括@DavidBeasley 的书籍和在 python 的讲座。

-> 给属性一个只读 属性

class SimpleFrozenObject:
    def __init__(self, x=0):
        self._x = x
    @property
    def x(self):
        return self._x

f = SimpleFrozenObject()
f.x = 2  # raises AttributeError: can't set attribute

-> 扩展 __setattr____delattr__,并覆盖 `hash

class FrozenObject:

    ...

    def __setattr__(self, name, value):
        if type(self) is cls or name in (tuple of attributes to freeze,):
            raise FrozenInstanceError(f'cannot assign to field {name}')
        super(cls, self).__setattr__(name, value)

    def __delattr__(self, name):
        if type(self) is cls or name in (tuple of attributes to freeze,):
            raise FrozenInstanceError(f'cannot delete field {name}')
        super(cls, self).__delattr__(name, value)

    def __hash__(self):
        return hash((tuple of attributes to freeze,))

    ...

attrs 还提供了创建不可变对象的选项。