【问题标题】:Inheriting from instance in Python从 Python 中的实例继承
【发布时间】:2010-11-08 01:13:18
【问题描述】:

在 Python 中,我想直接从 Parent 类的实例构造 Child 类的实例。例如:

A = Parent(x, y, z)
B = Child(A)

这是一个我认为可能有效的 hack:

class Parent(object):

    def __init__(self, x, y, z):
        print "INITILIZING PARENT"
        self.x = x
        self.y = y
        self.z = z

class Child(Parent):

    def __new__(cls, *args, **kwds):
        print "NEW'ING CHILD"
        if len(args) == 1 and str(type(args[0])) == "<class '__main__.Parent'>":
            new_args = []
            new_args.extend([args[0].x, args[0].y, args[0].z])
            print "HIJACKING"
            return Child(*new_args)
        print "RETURNING FROM NEW IN CHILD"
        return object.__new__(cls, *args, **kwds)

但是当我运行时

B = Child(A) 

我明白了:

NEW'ING CHILD  
HIJACKING  
NEW'ING CHILD  
RETURNING FROM NEW IN CHILD  
INITILIZING PARENT  
Traceback (most recent call last):  
  File "classes.py", line 52, in <module>  
    B = Child(A)  
TypeError: __init__() takes exactly 4 arguments (2 given) 

看起来 hack 就像我预期的那样工作,但编译器最后会抛出一个 TypeError。我想知道是否可以重载 TypeError 以使其忽略 B = Child(A) 习语,但我不知道该怎么做。无论如何,请您给我您从实例继承的解决方案吗?

谢谢!

【问题讨论】:

  • 仅供参考,“初始化”中的拼写为“a”。尝试对此进行编辑,但 StackOverflow 不允许编辑少于 6 个字母。

标签: python inheritance instance overloading


【解决方案1】:

一旦__new__Child 类中返回Child 的实例,Child.__init__ 将在该实例上被调用(使用相同的参数__new__)——显然它只是继承Parent.__init__ ,仅使用一个 arg 调用(另一个 ParentA)并不适合。

如果没有其他方法可以创建Child,您可以定义一个Child.__init__,它接受一个arg(它会忽略)或三个(在这种情况下它调用Parent.__init__)。但是放弃__new__ 并拥有Child.__init__ 中的所有逻辑更简单,只需适当地调用Parent.__init__

用一个代码示例来具体说明:

class Parent(object):

    def __init__(self, x, y, z):
        print "INITIALIZING PARENT"
        self.x = x
        self.y = y
        self.z = z

    def __str__(self):
        return "%s(%r, %r, %r)" % (self.__class__.__name__,
            self.x, self.y, self.z)


class Child(Parent):

    _sentinel = object()

    def __init__(self, x, y=_sentinel, z=_sentinel):
        print "INITIALIZING CHILD"
        if y is self._sentinel and z is self._sentinel:
            print "HIJACKING"
            z = x.z; y = x.y; x = x.x
        Parent.__init__(self, x, y, z)
        print "CHILD IS DONE!"

p0 = Parent(1, 2, 3)
print p0
c1 = Child(p0)
print c1
c2 = Child(4, 5, 6)
print c2

【讨论】:

    【解决方案2】:

    好的,所以直到我的解决方案已经完成一半时,我才意识到您对参数的静态副本感到满意。但我决定不浪费它,所以无论如何它都在这里。与其他解决方案的不同之处在于它实际上会从父级获取属性,即使它们已更新。

    _marker = object()
    
    class Parent(object):
    
        def __init__(self, x, y, z):
            self.x = x
            self.y = y
            self.z = z
    
    class Child(Parent):
    
        _inherited = ['x', 'y', 'z']
    
        def __init__(self, parent):
            self._parent = parent
            self.a = "not got from dad"
    
        def __getattr__(self, name, default=_marker):
            if name in self._inherited:
                # Get it from papa:
                try:
                    return getattr(self._parent, name)
                except AttributeError:
                    if default is _marker:
                        raise
                    return default
    
            if name not in self.__dict__:
                raise AttributeError(name)
            return self.__dict__[name]
    

    现在如果我们这样做:

    >>> A = Parent('gotten', 'from', 'dad')
    >>> B = Child(A)
    >>> print "a, b and c is", B.x, B.y, B.z
    a, b and c is gotten from dad
    
    >>> print "But x is", B.a
    But x is not got from dad
    
    >>> A.x = "updated!"
    >>> print "And the child also gets", B.x
    And the child also gets updated!
    
    >>> print B.doesnotexist
    Traceback (most recent call last):
      File "acq.py", line 44, in <module>
        print B.doesnotexist
      File "acq.py", line 32, in __getattr__
        raise AttributeError(name)
    AttributeError: doesnotexist
    

    如需更通用的版本,请查看http://pypi.python.org/pypi/Acquisition 包。事实上,在某些情况下,这是一种非常需要的解决方案。

    【讨论】:

      【解决方案3】:

      我发现这种(封装)是最干净的方式:

      class Child(object):
          def __init__(self):
              self.obj = Parent()
      
          def __getattr__(self, attr):
              return getattr(self.obj, attr)
      

      这样你可以使用Parent的所有方法和你自己的方法,而不会遇到继承问题。

      【讨论】:

      • 在这里尝试了所有选项后,这绝对是我正在寻找的解决方案。最干净,最简单,永远不必知道变量名。强烈推荐。
      【解决方案4】:

      您没有为 Child 定义构造函数 (init),因此调用 Parent 构造函数,需要 4 个参数,而仅传入 2 个参数(从 new)。这是实现您想要的一种方法:

      class Child(Parent):
          def __init__(self, *args, **kwargs):
              if len(args) == 1 and isinstance(args[0], Parent):
                  Parent.__init__(self, args[0].x, args[0].y, args[0].z)
      
              else:
                  # do something else
      

      【讨论】:

      • +1:通过复制父级的属性简单地创建一个子级。
      【解决方案5】:

      我知道这是一个非常古老的话题,但我最近遇到了和 Alexandra 一样的挑战,这是我能找到的最相关的话题。我有一个具有很多很多属性的父类,我想通过保留它的所有方法和属性、添加一些方法和属性以及修改/覆盖其他的来基本上“修补”它的一个实例。一个简单的子类是行不通的,因为属性将在运行时由用户填写,我不能只从父类继承默认值。经过大量修改后,我找到了一种非常干净(虽然相当老套)的方法,即使用__new__。这是一个例子:

      class Parent(object):
          def __init__(self):
              # whatever you want here
              self.x = 42
              self.y = 5
          def f(self):
              print "Parent class, x,y =", self.x, self.y
      
      class Child(Parent):
          def __new__(cls, parentInst):
              parentInst.__class__ = Child
              return parentInst
          def __init__(self, parentInst):
              # You don't call the Parent's init method here
              self.y = 10
          def f(self):
              print "Child class, x,y =", self.x, self.y
      
      c = Parent()
      c.f()  # Parent class, x,y = 42 5
      c.x = 13
      c.f()  # Parent class, x,y = 13 5
      c = Child(c)
      c.f()  # Child class, x,y = 13 10
      

      唯一特殊的部分是在 Child 的构造函数中更改 Parent 类的 __class__ 属性。因此,Child 的__init__ 方法将照常调用,据我所知,Child 类的功能应该与任何其他继承类完全相同。

      【讨论】:

      • 我也遇到过类似的情况:stackoverflow.com/questions/14177788/…
      • 这种作品。但是您只能拥有一个基于同一个 Parent 的 Child 类的实例。如果有多个具有相同父级的子级,则子级都将修改彼此的变量。
      【解决方案6】:

      谢谢,伙计们,这很快!我首先阅读了 Alex 的评论,然后将 Child 的 __init__ 改写为

      def __init__(self, *args, **kwds):
          if len(args) == 1 and str(type(args[0])) == "<class '__main__.Parent'>":
              new_args = [args[0].x, args[0].y, args[0].z]
              super(Child, self).__init__(*new_args, **kwds)
          else:
              super(Child, self).__init__(*args, **kwds)
      

      这与 abhinavg 的建议非常相似(正如我刚刚发现的那样)。它有效。只有他和ars的行

      if len(args) == 1 and isinstance(args[0], Parent):
      

      比我的干净。

      再次感谢!!

      【讨论】:

        【解决方案7】:

        我知道这是一个旧线程,但我希望未来的读者仍然会从我找到的简单解决方案中受益。

        获取实例的 init 值的一种非常简单的方法是使用 vars() 函数。它返回对象的 dict 属性(返回类/实例属性)。我发现这种方法比使用 new 更具可读性,并且它可以处理任意数量的属性。

        class Parent(object): 
            def __init__(self, x, y, z, **kwargs):
                # kwargs is not necessary but
                # it handles if too many variables are given
                print("Accessing Parent")
                self.x = x
                self.y = y
                self.z = z
        
        class Child(Parent):
            def __init__(self, instance):
                print("Child")
                instance_attrs = vars(instance)
                super().__init__(**instance_attrs)
                # Parent.__init__(self, **instance_attrs)
        A = Parent(x, y, z)
        # Accessing Parent
        print(vars(A))
        # {"x": x, "y": y, "z": z}
        B = Child(A)
        # Child
        # Accessing Parent
        print(A.x, B.x) 
        # x x
        

        【讨论】:

          【解决方案8】:

          我希望不要偏离主题,因为我很具体,但这是我发现的唯一相关问题。

          如果你想通过组合继承父对象的属性,并且不复制任何值,你可以这样做:

          class Agro:
              def __init__(self, parent, prop1=None, prop2=None):
                  self.parent = parent
                  self._prop1 = prop1
                  self._prop2 = prop2
          
              @property
              def prop1(self):
                  try:
                      if self._prop1 is None:
                          return self.parent.prop1
                      else:
                          return self._prop1
                  except AttributeError as e:
                      return None
          
              @property
              def prop2(self):
                  try:
                      if self._prop2 is None:
                          return self.parent.prop2
                      else:
                          return self._prop2
                  except AttributeError as e:
                      return None
          

          然后你实例化:

          ag = Agro(None, prop1="nada")  # prop1: nada, prop2: None
          ag2 = Agro(ag, prop1="nada2")  # prop1: nada2, prop2: None
          ag3 = Agro(ag2, prop2="youhou")  # prop1: nada2, prop2: youhou
          ag4 = Agro(ag3)  # prop1: nada2, prop2: youhou
          

          有趣的是,当您查看实例中实际保存的内容时,__dict__ 属性仅保存一次。因此,您可以动态修改属性值:

          ag3._prop2 = "yaya"
          print(ag4.prop2)  # yaya
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-07-20
            • 1970-01-01
            • 2015-07-27
            • 1970-01-01
            • 2019-01-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多