多重 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
继承自 A
和 B
,由于继承顺序,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()
我正在尝试了解 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
继承自 A
和 B
,由于继承顺序,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()