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

127 投票
4 回答
118843 浏览
提问于 2025-04-17 18:07

比如我有一个基础类,如下所示:

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)

我已经让这个工作了,但更希望能以另一种方式,也就是动态创建类对象。

4 个回答

0

在我的情况下:

inst3 = globals()["SpecialClass"](b=False, c = "test")
120

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
184

这段代码可以让你创建新的类,并且这些类的名字和参数名都是动态的。__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属性中设置类名。)

更新,简而言之:这个回答变得很受欢迎,但它非常针对特定问题。关于如何在Python中“动态创建派生类”的通用答案是简单地调用type,传入新的类名、一个包含基类的元组,以及新类的__dict__内容——像这样:

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

更新
任何需要这个的人也应该看看dill项目——它声称能够像pickle对普通对象那样对类进行序列化和反序列化,并且在我的一些测试中确实能做到这一点。

撰写回答