在Python中从实例继承

19 投票
8 回答
17022 浏览
提问于 2025-04-15 12:40

在Python中,我想直接通过一个父类的实例来创建一个子类的实例。比如说:

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

这是我想到的一个可能有效的办法:

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) 

看起来这个办法按我预想的那样有效,但最后编译器却抛出了一个类型错误(TypeError)。我在想,是否可以重载这个类型错误,让它忽略 B = Child(A) 这种写法,但我不太确定怎么做。无论如何,能不能请你们给我一些关于如何从实例中继承的解决方案呢?

谢谢!

8 个回答

8

一旦类 Child 中的 __new__ 方法返回了一个 Child 的实例,接下来就会调用这个实例的 Child.__init__ 方法(传入的参数和 __new__ 一样)——而且看起来它只是继承了 Parent.__init__,这个方法不太适合只用一个参数(另外一个是 Parent,也就是 A)来调用。

如果没有其他方法可以创建 Child,你可以定义一个 Child.__init__ 方法,让它接受一个参数(这个参数可以忽略)或者三个参数(在这种情况下,它会调用 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
10

好的,我之前没意识到你其实是希望用一个静态的参数副本,直到我已经完成了一半的解决方案。不过我决定不浪费这个,所以还是把它分享出来。这个方案和其他的不同之处在于,它可以从父级那里获取属性,即使这些属性已经被更新过。

_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包。在某些情况下,这个方案真的是非常必要的。

13

我觉得这种(封装)方式是最简洁的:

class Child(object):
    def __init__(self):
        self.obj = Parent()

    def __getattr__(self, attr):
        return getattr(self.obj, attr)

这样你就可以使用父类的所有方法,也可以用你自己的方法,而不会遇到继承带来的问题。

撰写回答