【问题标题】:Class that takes another class as argument, copies behavior将另一个类作为参数的类,复制行为
【发布时间】:2016-11-30 18:16:13
【问题描述】:

我想在 Python 中创建一个类,它在构造函数中接受一个参数,即另一个 Python 类。 Copy 类的实例应该具有原始类的所有属性和方法,而不需要事先知道它们应该是什么。这是一些几乎可以工作的代码:

import copy

class A():
    l = 'a'

class Copy():

    def __init__(self, original_class):
        self = copy.deepcopy(original_class)
        print(self.l)

c = Copy(A)
print(c.l)

构造函数中的打印语句打印'a',但最后一个给出错误AttributeError: Copy instance has no attribute 'l'

【问题讨论】:

  • 您有什么特别的理由希望它成为一个吗?为什么不直接使用copy.deepcopy
  • 您应该研究 python 元编程、__new__ 方法和 type() 函数。
  • 请注意,您实际上并未将copy.deepcopy() 的结果分配给Copy 实例。由于 Python 的按值传递行为,赋值有效地改变了 self 指向的内容——它不再是你认为的意思。

标签: python class oop inheritance metaclass


【解决方案1】:

这是一个有趣的问题,指出 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

【讨论】:

    【解决方案2】:

    你需要复制__dict__:

    import copy
    
    class A():
        l = 'a'
    
    class Copy():
        def __init__(self, original_class):
            self.__dict__ = copy.deepcopy(original_class.__dict__)
            print(self.l)
    
    c = Copy(A)  # -> a
    print(c.l)  # -> a
    

    【讨论】:

      【解决方案3】:

      我不确定你为什么要这样做,但你可能有你的理由。

      您可以利用正常的继承:

      例如:

          class A(object):
              l = 'a'
      
          class C(object):
              l = 'c'
      
          def createClass(cls):
      
              class B(cls):
                  pass
      
              return B
      
          cls = createClass(C) # or A or whatever
          print cls.l
      
      => result: 'c'
      

      【讨论】:

        猜你喜欢
        • 2016-07-12
        • 2023-01-18
        • 2012-10-18
        • 1970-01-01
        • 2020-10-31
        • 1970-01-01
        • 1970-01-01
        • 2016-09-23
        • 1970-01-01
        相关资源
        最近更新 更多