这是一个有趣的问题,指出 Python 的值传递语义的一个非常酷的特性,因为它与为什么您的原始代码不能正常工作以及为什么 @martineau 的解决方案工作良好密切相关。
为什么你写的代码不起作用
Python 不支持纯按引用传递或按值传递语义 - 相反,它执行以下操作:
# Assume x is an object
def f(x):
# doing the following modifies `x` globally
x.attribute = 5
# but doing an assignment only modifies x locally!
x = 10
print(x)
要查看实际情况,
# example
class Example(object):
def __init__(self):
pass
x = Example()
print(x)
>>> <__main__.Example instance at 0x020DC4E0>
f(e) # will print the value of x inside `f` after assignment
>>> 10
print(x) # This is unchanged
>>> <__main__.Example instance at 0x020DC4E0>
e.attribute # But somehow this exists!
>>> 5
会发生什么?赋值创建一个 local x,然后分配一个值。一旦发生这种情况,作为参数传入的原始参数将无法访问。
但是,只要namex绑定到传入的对象上,你就可以修改属性,它会在你传入的对象中体现出来。你给的那一刻将名称 x 移到其他名称,但是,该名称不再绑定到您传入的原始参数。
为什么这与这里相关?
如果您仔细注意__init__ 的签名,您会注意到它采用self 作为参数。 self 是什么?
通常,self 指的是对象实例。所以名称self 绑定到对象实例。
这就是乐趣的开始。 通过在您的代码中分配给self,此属性不再成立!
def __init__(self, original_class):
# The name `self` is no longer bound to the object instance,
# but is now a local variable!
self = copy.deepcopy(original_class)
print(self.l) # this is why this works!
离开__init__ 的那一刻,这个新的局部变量self 就超出了范围。这就是为什么 c.l 在构造函数之外会产生错误的原因 - 你从来没有真正分配给对象!
为什么@martineau 的解决方案有效
@martineau 只是利用此行为注意到__dict__ 属性存在于self 对象上,并将其分配给它:
class Copy():
def __init__(self, original_class):
# modifying attributes modifies the object self refers to!
self.__dict__ = copy.deepcopy(original_class.__dict__)
print(self.l)
这现在起作用了,因为__dict__ 属性是 Python 在看到命名空间运算符 . 时需要查找方法签名或属性时调用的属性,还因为 self 尚未更改但仍引用对象实例。通过分配给self.__dict__,您可以获得原始类的几乎完全相同的副本(“几乎完全相同”,因为即使deepcopy 也有限制)。
故事的寓意应该很清楚:永远不要直接将任何东西分配给self。相反,如果需要,仅分配给self 的属性。 Python 的元编程在这方面提供了很大程度的灵活性,在这方面您应该始终咨询the documentation。