考虑以下示例:
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def say(self):
pass
@classmethod
def from_config(cls, config):
kind = config['kind']
# TODO: Look at all subclasses, find one with matching kind
# TODO: and call its constructor with **config['parameters']
class ChildA(Base):
kind = 'A'
def say(self):
return 'Hello, I am A'
class ChildB(Base):
kind = 'B'
def __init__(self, name):
super().__init__()
self._name = name
def say(self):
return 'Hello, I am {}'.format(self._name)
def main():
configA = {
'kind': 'A',
'parameters': {}
}
configB = {
'kind': 'B',
'parameters': {
'name': 'Foo'
}
}
print(ChildA(**configA['parameters']).say())
print(ChildB(**configB['parameters']).say())
if __name__ == '__main__':
main()
我想在Base
中实现一个类方法,该方法查看其所有子类(可能还有它们的子类,无穷大),并根据匹配的{{ 1}},即**config['parameters']
。
我的问题是:
kind
来实现这一目标吗?答案 0 :(得分:1)
在示例中,我将使用以下配置字典:
configA = {
'kind': 'A',
'parameters': {}
}
configB = {
'kind': 'B',
'parameters': {
'name': 'Foo'
}
}
configC = {
'kind': 'C',
'parameters': {
'age': '17'
}
}
configD = { # ChildD inherits from both ChildC, ChildA
'kind': 'D',
'parameters': {
'name': 'Bar',
'age': '17'
}
}
第一个问题的答案是“是”。您可以通过__subclasses__
进行递归迭代(如果存在)。我将extract_all_subclasses
定义为一个函数,因为它可以用于(大约任何一个)类。
def extract_all_subclasses(cls):
output = set()
# I use the set to avoid repeating classes in case of multiple inheritance -
# but lists is possible alternative, just filter it later
subclasses = set(cls.__subclasses__())
output = output.union(subclasses)
for subcls in subclasses:
if subcls.__subclasses__(): # if there's some subclasses
for subcls_inner in subcls.__subclasses__():
output = output.union(extract_all_subclasses(subcls))
else: # just append
output = output.union(subclasses )
return output
在Base
上:
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def say(self):
pass
@classmethod
def from_config(cls, config):
subclasses = extract_all_subclasses(cls) # extracting all the subclasses
subcls = {c.kind: c for c in subclasses}[config['kind']] # get one from dictionary
return subcls(**config['parameters'])
目前没有子类:
extract_all_subclasses(Base)
Out:
set()
但是然后:
class ChildA(Base):
kind = 'A'
def say(self):
return 'Hello, I am A'
class ChildB(Base):
kind = 'B'
def __init__(self, name, *args, **kwargs):
self._name = name
def say(self):
return 'Hello, I am {}'.format(self._name)
class ChildC(ChildB):
kind = 'C'
def __init__(self, age, *args, **kwargs):
self._age = age
def say(self):
return 'Hello, I am Base\'s grandson, my age is {}'.format(self._age)
class ChildD(ChildC, ChildA):
kind = 'D'
def __init__(self, age, name):
self._age = age
self._name = name
def say(self):
return 'Hello, I am Base\'s grandson, my age is {} and my name is {}'.format(self._age, self._name)
extract_all_subclasses(Base)
Out:
{__main__.ChildA, __main__.ChildB, __main__.ChildC, __main__.ChildD}
因此,即使在多重继承的情况下,它也可以正常工作。检查一下:
print(Base.from_config(configA).say())
print(Base.from_config(configB).say())
print(Base.from_config(configC).say())
print(Base.from_config(configD).say())
Out:
Hello, I am A
Hello, I am Foo
Hello, I am Base's grandson, my age is 17
Hello, I am Base's grandson, my age is 17 and my name is Bar
注意!我刚刚删除了对超类的构造函数的调用,因为它会导致错误,如果它期望您不提供的参数,或者提供了它的非预期参数,则可能会导致错误。但这仅在嵌套子类的情况下才是危险的,并且很容易避免。而且我不知道为什么需要它们-如果您重新定义__init__
上的行为(可能只是为了简化)。
可能的错误示例(Base
相同):
class ChildA(Base):
kind = 'A'
def say(self):
return 'Hello, I am A'
class ChildB(Base):
kind = 'B'
def __init__(self, name):
super().__init__()
self._name = name
def say(self):
return 'Hello, I am {}'.format(self._name)
class ChildC(ChildB):
kind = 'C'
def __init__(self, age):
super().__init__()
self._age = age
def say(self):
return 'Hello, I am Base\'s grandson, my age is {}'.format(self._age)
print(Base.from_config(configA).say())
print(Base.from_config(configB).say())
print(Base.from_config(configC).say())
Out:
Hello, I am A
Hello, I am Foo
---------------------------------------------------------------------------
...
<ipython-input-4-6932e3173d3f> in __init__(self, age)
21
22 def __init__(self, age):
---> 23 super().__init__()
24 self._age = age
25
TypeError: __init__() missing 1 required positional argument: 'name'
修改
解决后一个问题,是的,但有限制。您可以照常构造项目,但如果希望它能正常工作,则应将子类导入名称空间。否则它将无法正常工作。
我使用以下结构:
.
├── __init__.py
├── a.py
├── b.py
├── base.py
├── c.py
├── d.py
├── extractor.py
└── main.py
其中base.py是Base
,a.py,b.py,c.py,d.py的定义-ChildA
,ChildB
,{{ 1}},ChildC
,提取器是子类提取器函数,并且main具有以下列表:
ChildD
-它按计划工作。但是,如果我省略了from subclasses.base import Base
from subclasses.a import ChildA
from subclasses.b import ChildB
from subclasses.c import ChildC
from subclasses.d import ChildD
config = [
{
'kind': 'A',
'parameters': {}
},
{
'kind': 'B',
'parameters': {
'name': 'Foo'
}
},
{
'kind': 'C',
'parameters': {
'age': '17'
}
},
{ # ChildD inherits from both ChildC, ChildA
'kind': 'D',
'parameters': {
'name': 'Bar',
'age': '17'
}
}
]
if __name__ == '__main__':
print(
[
Base.from_config(cfg).say() for cfg in config
]
)
的导入-它会引发KeyError-因为在这种情况下,Childs没有导入,而ChildX
无法提取它们-它们不存在。