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


92

例如,我有一个基类如下:

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

有没有一种不错的python方式通过函数调用动态创建这些类,该函数将新类放入我的当前作用域,例如:

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

就像会有评论和疑问一样,为什么我需要这样做:派生类都具有完全相同的内部结构,不同之处在于,构造函数采用了许多以前未定义的参数。因此,举例来说,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?为什么不只在中声明所有3个参数,BaseClass.__init__()而是将它们默认为Nonedef __init__(self, a=None, b=None, C=None)
acattle 2013年

我无法在基类中声明任何内容,因为我不知道可能使用的所有参数。我可能有30个不同的小节,每个小节有5个不同的参数,因此在构造函数中声明150个参数不是解决方案。
亚历克斯

Answers:


141

这一段代码使您可以使用动态名称和参数名称创建新的类。参数验证__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

我看你是要求在命名范围中插入动态名称的-现在,在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;博士:这个答案已经很流行了,但它仍然非常适合问题主体。关于如何 在Python中“从基类动态创建派生类”的一般答案 是一个简单的调用,即type传递新的类名,带有基类的元组和__dict__新类的主体,如下所示:

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

更新
需要此功能的任何人都还应该检查莳萝项目-它声称能够像腌制普通对象一样对类进行腌制和腌制,并且在我的一些测试中得到了证实。


2
如果我没记错的话,BaseClass.__init__()最好是更通用的super(self.__class__).__init__(),当新类被子类化时,它会表现得更好。(参考:rhettinger.wordpress.com/2011/05/26/super-considered-super
Eric O Lebigot

@EOL:这将用于静态声明的类-但是由于您没有将实际的类名硬编码为Super的第一个参数,因此这需要很多时间。尝试用super上面的方法替换它,并创建一个动态创建的类的子类来理解它;并且,在这种情况下,您可以将基类作为要调用的常规对象 __init__
jsbueno

现在,我有一些时间查看建议的解决方案,但这并不是我想要的。首先,它看起来像__init__BaseClass是用一个参数调用,但实际上BaseClass.__init__始终把关键字参数任意列表。其次,上述解决方案将所有允许的参数名称设置为属性,这不是我想要的。ANY参数必须转到BaseClass,但是在创建派生类时我知道哪一个。我可能会更新问题或提出更精确的问题以使其更清楚。
亚历克斯

@jsbueno:是的,使用super()我刚才提到的gives TypeError: must be type, not SubSubClass。如果我理解正确的话,这来自于第一个参数self__init__(),这是SubSubClass其中一个type对象预期:这似乎是相关的事实super(self.__class__)是未绑定的超级对象。它的__init__()方法是什么?我不确定哪种方法可能需要type的第一个参数type。你能解释一下吗?(旁注:我的super()方法在这里确实没有意义,因为__init__()它具有可变的签名。)
Eric O Lebigot 2013年

1
@EOL:主要问题实际上是如果您创建因式分解类的另一个子类:self .__ class__将引用该子类,而不是调用“ super”的类-您将获得无限递归。
jsbueno

89

type() 是创建类(尤其是子类)的函数:

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

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理解此示例时,我看到了TypeErrorthat __init__() takes exactly 2 arguments (1 given)。我发现添加一些东西(任何东西?)来填补空白就足够了。例如,obj = SubClass('foo')运行无错误。
DaveL17

这是正常的,因为SubClass是子类BaseClass中的问题,BaseClass需要一个参数(classtype,这是'foo'在你的例子)。
Eric O Lebigot

-2

要创建具有动态属性值的类,请签出以下代码。注意 这是python编程语言中的代码片段

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*
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.