多个 Python 类继承



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

如何从以子项输入为条件的类继承方法?

我已经尝试了下面的代码,但没有取得多大成功。

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有关。

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

如果需要根据构造函数中的值更改类继承,请创建两个具有不同结构的单独类。然后提供不同的可调用对象作为 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')不同的类的实例,但它们都实现相同的接口,因此这对调用者无关紧要。

如果你必须有一个通用的"C"类来isinstance()issubclass()测试中使用,你可以混合一个,并使用__new__方法重写返回的子类:

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__方法返回类(或其子类)的实例,则将自动在该新实例对象上调用__init__。这就是为什么C.__new__()返回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()实现中添加更多路由,然后根据self.path调用A.something(self)B.something(self)。当您必须为每种方法执行此操作时,这很快就会变得很麻烦,但是装饰器可以在那里提供帮助:

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 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

然而,这变得非常不蟒蛇和丑陋。

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

请记住,实现继承实际上是一种静态的、在某种程度上受到限制的组合/委托,所以一旦你想要更动态的东西,正确的设计就是避免继承,而是进行完整的组合/委托,规范的例子是状态和策略模式。应用于您的示例,这可能如下所示:

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()

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

>>> 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'
>>> 

但这里的 C 类不是从 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 类中的parent属性,但可读性较差......

相关内容

  • 没有找到相关文章

最新更新