我正在尝试了解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
继承自A
和B
,由于继承顺序的原因,所有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
属性,但可读性较差......