代码之家  ›  专栏  ›  技术社区  ›  d125q

按名称实例化子类

  •  0
  • d125q  · 技术社区  · 6 年前

    考虑下面的例子:

    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 这将查看它的所有子类(可能还有它们的子类,无限),并使用 **config['parameters'] 根据匹配 kind ,即。, cls.kind == config['kind']

    我的问题是:

    • __subclasses__ 要做到这一点?
    • 如果类定义驻留在不同的模块中会发生什么?
    1 回复  |  直到 6 年前
        1
  •  1
  •   Mikhail Stepanov    6 年前

    我将在示例中使用以下配置字典:

    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__ (可能只是简化而已)。

    可能出现的错误示例(错误代码) 基础

    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是 基础 ,a.py,b.py,c.py,d.py-的定义 ChildA ChildB ChildC , ChildD 子类 提取器函数和main具有以下列表:

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

    -它按计划工作。但是如果我忽略了 ChildX -它引发了一个关键错误-因为该案例中的child没有导入和删除 提取所有子类 无法提取它们-它们不存在。