以您建议的方式重载赋值运算符并不是很pythonic。
Python 中的赋值运算符旨在引用相同的变量,而不是创建副本。
因此,这样的对象在某些情况下可能不会像预期的那样表现,例如在这样的对象上hash(object.attr)
使用pickle
模块的结果。
然而,如果你想要一些黑暗魔法......
需要明确的是,我提供这个答案只是为了表明 Python 确实提供了执行此类操作的能力。
一种方法是在访问__getattribute__()
时使用该函数创建任何属性的副本。
import copy
class MyClass:
def __init__(self):
self.attr = ["foo", "bar"]
def __getattribute__(self, name):
"""Access an attribute of this class"""
attr = object.__getattribute__(self, name)
return copy.copy(attr)
a = MyClass()
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True
更多信息__getattribute__()
可以在这里找到:https ://docs.python.org/3/reference/datamodel.html
或者,property()
可用于仅对类上的单个属性执行此操作。
class MyClass:
def __init__(self):
self.__attr = ["foo", "bar"]
@property
def attr(self):
"""Get the attribute."""
# Copy could be used instead
# This works too, if we assume the attribute supports slicing
return self.__attr[:]
@attr.setter
def attr(self, value):
"""Setting the attribute."""
# Makes assignment work on this attribute
# a.attr = []
# a.attr is a.attr (False)
self.__attr = value
a = MyClass()
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True
这些解决方案适用于大多数对象类型。但这在某些情况下实际上会失败。这是因为一些字符串和一些整数将被标记为具有相同的标识。
比如4 is 2*2
which will be True
, 但是
a = -6
b = -6
print(a is b) # Will print False
这称为“实习”,并在sys
模块中简要讨论:https ://docs.python.org/3/library/sys.html?highlight=intern#sys.intern
根据 Real Python:https ://realpython.com/lessons/intern-objects/
在 CPython 3.7 中,-5 到 256 之间的整数被保留,与少于 20 个字符且仅包含 ASCII 字母、数字或下划线的字符串一样。
例如,如果attr = 5
上述attr = 'foo'
两种方法都失败了。
a = MyClass()
a.attr = 5
b = a
print(b.attr is a.attr) # prints True
print(b.attr == a.attr) # prints True
这可以通过将这些类型包装在子类中来规避。根据 RealPython,只有字符串和一些整数需要修改。这种方法的问题是类型比较会失败:
print(type(b.attr) is type(a.attr)) # prints False
所以如果你想包装对象,确保is
操作总是失败,你可以这样做:
import copy
from collections import UserString # Because of course Python has this built in
class UserInt(int): pass
class MyClass:
def __init__(self, attr = ["foo", "bar"]):
self.attr = attr
def __getattribute__(self, name):
"""Access an attribute of this class"""
attr = object.__getattribute__(self, name)
if isinstance(attr, int) and -5 <= attr <= 256:
return UserInt(attr) # Prevent a.attr is b.attr if this is an int
elif isinstance(attr, str):
return UserString(attr) # Prevent a.attr is b.attr for strings
#else
return copy.copy(attr)
a = MyClass()
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True
a.attr = 7
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True
a.attr = "Hello"
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True