多重 python class 继承

multiple python class inheritance

我正在尝试了解 python 的 class 继承方法,但在弄清楚如何执行以下操作时遇到了一些麻烦:

如何从子输入的class条件继承方法?

我尝试了下面的代码但没有成功。

class A(object):
    def __init__(self, path):
        self.path = path

    def something(self):
        print("Function %s" % self.path)   


class B(object):
    def __init__(self, path):
        self.path = path
        self.c = 'something'

    def something(self):
        print('%s function with %s' % (self.path, self.c))


class C(A, B):
    def __init__(self, path):
        # super(C, self).__init__(path)

        if path=='A':
            A.__init__(self, path)
        if path=='B':
            B.__init__(self, path)
        print('class: %s' % self.path)


if __name__ == '__main__':
    C('A')
    out = C('B')
    out.something()

我得到以下输出:

class: A
class: B
Function B

虽然我想看:

class: A
class: B
B function with something

我想使用 A.something()(而不是 B.something())的原因与 python 的 MRO 有关。

在任一父 class 上调用 __init__ 不会更改您的 class 的继承结构,不会。在创建实例时,除了 C.__init__ 之外,您只更改初始化方法 运行 。 C 继承自 AB,由于继承顺序,B 的所有方法都被 A 上的方法覆盖。

如果您需要根据构造函数中的值更改 class 继承,请创建 两个具有不同结构的独立 classes。然后提供一个与 API 不同的可调用对象来创建一个实例:

class CA(A):
    # just inherit __init__, no need to override

class CB(B):
    # just inherit __init__, no need to override

def C(path):
    # create an instance of a class based on the value of path
    class_map = {'A': CA, 'B': CB}
    return class_map[path](path)

您的 API 的用户仍有名字 C() 可以呼叫; C('A') 生成与 C('B') 不同的 class 的实例,但它们都实现相同的接口,因此这对调用者来说无关紧要。

如果你有一个共同的'C'class用于isinstance()issubclass()测试,你可以混合一个,并使用 __new__ method 覆盖返回的 subclass:

class C:
    def __new__(cls, path):
        if cls is not C:
            # for inherited classes, not C itself
            return super().__new__(cls)
        class_map = {'A': CA, 'B': CB}
        cls = class_map[path]
        # this is a subclass of C, so __init__ will be called on it
        return cls.__new__(cls, path)

class CA(C, A):
    # just inherit __init__, no need to override
    pass

class CB(C, B):
    # just inherit __init__, no need to override
    pass
调用

__new__构造新的实例对象;如果 __new__ 方法 returns 是 class 的实例(或其子class),那么 __init__ 将自动在该新实例对象上调用。这就是为什么 C.__new__() returns CA.__new__()CB.__new__() 的结果; __init__ 会为您打电话。

后者的演示:

>>> C('A').something()
Function A
>>> C('B').something()
B function with something
>>> isinstance(C('A'), C)
True
>>> isinstance(C('B'), C)
True
>>> isinstance(C('A'), A)
True
>>> isinstance(C('A'), B)
False

如果这些选项都不适用于您的特定用例,则您必须在 C 上的新 somemethod() 实现中添加更多路由,然后调用 A.something(self)B.something(self) 基于 self.path。当您必须为 每个方法 执行此操作时,这很快就会变得很麻烦,但是装饰器可以提供帮助:

from functools import wraps

def pathrouted(f):
    @wraps
    def wrapped(self, *args, **kwargs):
        # call the wrapped version first, ignore return value, in case this
        # sets self.path or has other side effects
        f(self, *args, **kwargs)
        # then pick the class from the MRO as named by path, and call the
        # original version
        cls = next(c for c in type(self).__mro__ if c.__name__ == self.path)
        return getattr(cls, f.__name__)(self, *args, **kwargs)
    return wrapped

然后在 class:

的空方法上使用它
class C(A, B):
    @pathrouted
    def __init__(self, path):
        self.path = path
        # either A.__init__ or B.__init__ will be called next

    @pathrouted
    def something(self):
        pass  # doesn't matter, A.something or B.something is called too

然而,这变得非常不符合 Python 标准且丑陋。

Martijn 的回答解释了如何选择从两个 classes 之一继承的对象。 Python 还允许轻松地将方法转发到不同的方法 class:

>>> class C:
    parents = { 'A': A, 'B': B }
    def __init__(self, path):
        self.parent = C.parents[path]
        self.parent.__init__(self, path)                 # forward object initialization
    def something(self):
        self.parent.something(self)                      # forward something method


>>> ca = C('A')
>>> cb = C('B')
>>> ca.something()
Function A
>>> cb.something()
B function with something
>>> ca.path
'A'
>>> cb.path
'B'
>>> cb.c
'something'
>>> ca.c
Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    ca.c
AttributeError: 'C' object has no attribute 'c'
>>> 

但是这里classC并没有继承自A或者B:

>>> C.__mro__
(<class '__main__.C'>, <class 'object'>)

下面是我使用猴子补丁的原始解决方案:

>>> class C:
    parents = { 'A': A, 'B': B }
    def __init__(self, path):
        parent = C.parents[path]
        parent.__init__(self, path)                      # forward object initialization
        self.something = lambda : parent.something(self) # "borrow" something method

它避免了 C class 中的 parent 属性,但可读性较差...

虽然 Martijn 的回答(像往常一样)接近完美,但我想指出的是,从设计 POV 来看,继承在这里是错误的工具。

请记住,实现继承实际上是一种静态的,并且在某种程度上受到限制 composition/delegation,所以一旦您想要更动态的东西,正确的设计就是避免继承并寻求完整的 composition/delegation,典型的例子是状态和策略模式。应用于您的示例,这可能类似于:

class C(object):
    def __init__(self, strategy):
        self.strategy = strategy

    def something(self):
        return self.strategy.something(self)

class AStrategy(object):
    def something(self, owner):
        print("Function A")

class BStrategy(object):
    def __init__(self):
        self.c = "something"

    def something(self, owner):
        print("B function with %s" % self.c)


if __name__ == '__main__':
    a = C(AStrategy())
    a.something()
    b = C(BStrategy())
    b.something()

然后如果你需要允许用户通过名称(作为字符串)指定策略,你可以将工厂模式添加到解决方案中

STRATEGIES = {
    "A": AStrategy,
    "B": BStrategy, 
    }

def cfactory(strategy_name):
  try:
      strategy_class = STRATEGIES[strategy_name]
  except KeyError:
      raise ValueError("'%s' is not a valid strategy" % strategy_name)
  return C(strategy_class())

if __name__ == '__main__':
    a = cfactory("A")
    a.something()
    b = cfactory("B")
    b.something()