如何从基类动态创建派生类

Posted

技术标签:

【中文标题】如何从基类动态创建派生类【英文标题】:How can I dynamically create derived classes from a base class 【发布时间】:2013-02-21 05:28:10 【问题描述】:

例如我有一个基类如下:

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

我从这个类派生了几个其他类,例如

class TestClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Test')

class SpecialClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Special')

有没有一种不错的 Pythonic 方法可以通过将新类放入我当前范围的函数调用动态创建这些类,例如:

foo(BaseClass, "My")
a = MyClass()
...

因为会有 cmets 和我为什么需要这个问题:派生类都具有完全相同的内部结构,但不同之处在于构造函数采用了许多以前未定义的参数。因此,例如,MyClass 采用关键字 a,而类 TestClass 的构造函数采用 bc

inst1 = MyClass(a=4)
inst2 = MyClass(a=5)
inst3 = TestClass(b=False, c = "test")

但他们不应该使用类的类型作为输入参数,比如

inst1 = BaseClass(classtype = "My", a=4)

我得到了这个工作,但更喜欢另一种方式,即动态创建的类对象。

【问题讨论】:

只是为了确定,您希望实例的类型根据提供的参数而改变?就像我给a 一样,它总是MyClassTestClass 永远不会接受a?为什么不在BaseClass.__init__() 中声明所有3 个参数,而是将它们全部默认为Nonedef __init__(self, a=None, b=None, C=None)? 我不能在基类中声明任何东西,因为我不知道我可能使用的所有参数。我可能有 30 个不同的类,每个类有 5 个不同的参数,因此在构造函数中声明 150 个参数不是解决方案。 【参考方案1】:

这段代码允许您创建具有动态的新类 名称和参数名称。 __init__中的参数验证就是不允许 未知参数,如果您需要其他验证,例如 类型,或者它们是强制性的,只需添加逻辑 那里:

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

def ClassFactory(name, argnames, BaseClass=BaseClass):
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            # here, the argnames variable is the one passed to the
            # ClassFactory call
            if key not in argnames:
                raise TypeError("Argument %s not valid for %s" 
                    % (key, self.__class__.__name__))
            setattr(self, key, value)
        BaseClass.__init__(self, name[:-len("Class")])
    newclass = type(name, (BaseClass,),"__init__": __init__)
    return newclass

这就是这样工作的,例如:

>>> SpecialClass = ClassFactory("SpecialClass", "a b c".split())
>>> s = SpecialClass(a=2)
>>> s.a
2
>>> s2 = SpecialClass(d=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __init__
TypeError: Argument d not valid for SpecialClass

我看到您要求在命名范围内插入动态名称 -- 现在,that 在 Python 中不被认为是一个好习惯——你要么有 变量名称,在编码时已知,或数据 - 以及在运行时学习的名称 比“变量”更多的是“数据” -

因此,您可以将您的类添加到字典中并从那里使用它们:

name = "SpecialClass"
classes = 
classes[name] = ClassFactory(name, params)
instance = classes[name](...)

如果您的设计绝对需要将名称纳入范围, 做同样的事情,但使用globals()返回的字典 调用而不是任意字典:

name = "SpecialClass"
globals()[name] = ClassFactory(name, params)
instance = SpecialClass(...)

(类工厂函数确实可以在调用者的全局范围内动态插入名称 - 但这是更糟糕的做法,并且在 Python 实现之间不兼容。这样做的方法是通过sys._getframe(1) 获取调用者的执行帧,并在其f_globals 属性中设置帧全局字典中的类名。

更新,tl;dr:这个答案已经很流行,但它仍然非常具体到问题主体。关于如何的一般答案 “从基类动态创建派生类” 在 Python 中是对 type 的简单调用,传递新类名、带有基类的元组和新类的 __dict__ 主体 - 像这样:

>>> new_class = type("NewClassName", (BaseClass,), "new_method": lambda self: ...)

更新 任何需要这个的人也应该检查 dill 项目 - 它声称能够像 pickle 对普通对象所做的那样腌制和取消腌制类,并且在我的一些测试中已经做到了。

【讨论】:

如果我没记错的话,BaseClass.__init__() 作为更通用的super(self.__class__).__init__() 会更好,当新类被子类化时,它会更好地发挥作用。 (参考:rhettinger.wordpress.com/2011/05/26/super-considered-super) @EOL:它适用于静态声明的类 - 但由于您没有将实际类名硬编码为 Super 的第一个参数,因此需要大量的操作。尝试用上面的super 替换它,并创建一个动态创建的类的子类来理解它;另一方面,在这种情况下,您可以将基类作为通用对象,从中调用__init__ 现在我有时间查看建议的解决方案,但这并不是我想要的。首先,看起来BaseClass__init__ 是用一个参数调用的,但实际上BaseClass.__init__ 总是采用任意的关键字参数列表。其次,上面的解决方案将所有允许的参数名称设置为属性,这不是我想要的。任何参数都必须转到BaseClass,但在创建派生类时我知道哪个参数。我可能会更新问题或更准确的问题以使其更清楚。 @jsbueno:对,使用我提到的super() 可以得到TypeError: must be type, not SubSubClass。如果我理解正确,这来自__init__() 的第一个参数self,这是一个SubSubClass,其中需要一个type 对象:这似乎与super(self.__class__) 是一个未绑定的超级对象这一事实有关。它的__init__() 方法是什么?我不确定哪种方法可能需要type 类型的第一个参数。你能解释一下吗? (旁注:我的super() 方法在这里确实没有意义,因为__init__() 有一个变量签名。) @EOL:主要问题实际上是如果您创建分解类的另一个子类: self.__class__ 将引用该子类,而不是调用“super”的类-您将获得无限递归。【参考方案2】:

type() 是创建类,特别是子类的函数,就像问题中一样:

def set_x(self, value):
    self.x = value

# type() takes as argument the new class name, its base
# classes, and its attributes:
SubClass = type('SubClass', (BaseClass,), 'set_x': set_x)
# (More methods can be put in SubClass, including __init__().)

obj = SubClass()
obj.set_x(42)
print obj.x  # Prints 42
print isinstance(obj, BaseClass)  # True

【讨论】:

在尝试使用 Python 2.7 理解这个示例时,我得到了一个 TypeError,上面写着 __init__() takes exactly 2 arguments (1 given)。我发现添加一些东西(任何东西?)来填补空白就足够了。例如,obj = SubClass('foo') 运行时没有错误。 这是正常的,因为SubClass 是问题中BaseClass 的子类,而BaseClass 带有一个参数(classtype,在您的示例中为'foo')。 @EricOLebigot 我可以在这里以某种方式调用 BaseClass 的 init 吗?喜欢超级吗? 是的,仍然使用super(),但是Python解释器不能再发挥它的魔力了,直接解释一个裸露的super(),所以你必须在set_x中使用(或@的任何其他方法987654335@) 更明确的形式super(SubClass, self).__init__()【参考方案3】:

要创建具有动态属性值的类,请查看以下代码。 注意。这是python编程语言中的代码sn-ps

def create_class(attribute_data, **more_data): # define a function with required attributes
    class ClassCreated(optional extensions): # define class with optional inheritance
          attribute1 = adattribute_data # set class attributes with function parameter
          attribute2 = more_data.get("attribute2")

    return ClassCreated # return the created class

# use class

myclass1 = create_class("hello") # *generates a class*

【讨论】:

以上是关于如何从基类动态创建派生类的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 C++ 中的模板编程从基类创建派生类?

如何通过派生类函数从基类更改向量

如何从基类中获取派生类名

如何从现有的基类对象创建派生类对象?

从基类指针访问两个具有不同类型的派生成员变量。

如何从基类调用派生类方法?