【问题标题】:What is the advantage in using `exec` over `type()` when creating classes at runtime?在运行时创建类时,使用 exec 而不是 type() 有什么优势?
【发布时间】:2011-10-07 00:35:52
【问题描述】:

我想在运行时在 python 中动态创建类。

例如,我想复制下面的代码:

>>> class RefObj(object):
...     def __init__(self, ParentClassName):
...         print "Created RefObj with ties to %s" % ParentClassName
... class Foo1(object):
...     ref_obj = RefObj("Foo1")
... class Foo2(object):
...     ref_obj = RefObj("Foo2")
... 
Created RefObj with ties to Foo1
Created RefObj with ties to Foo2
>>>

...但我希望动态创建 Foo1、Foo2、Foo 类(即:在执行期间而不是在首次编译时)。

实现此目的的一种方法是使用type(),如下所示:

>>> class RefObj(object):
...     def __init__(self, ParentClassName):
...         print "Created RefObj with ties to %s" % ParentClassName
... def make_foo_class(index):
...     name = "Foo%s" % index
...     return type(name, (object, ), dict(ref_obj = RefObj(name)))
... 
>>> Foo1 = make_foo_class(1)
Created RefObj with ties to Foo1
>>> Foo2 = make_foo_class(2)
Created RefObj with ties to Foo2
>>> type(Foo1()), type(Foo2())
(<class 'Foo1'>, <class 'Foo2'>)

我也可以用exec 实现,像这样:

>>> class RefObj(object):
...     def __init__(self, ParentClassName):
...         print "Created RefObj with ties to %s" % ParentClassName
... def make_foo_object(index):
...     class_template = """class Foo%(index)d(object):
...         ref_obj = RefObj("Foo%(index)d")
...         """ % dict(index = index)
...     global RefObj
...     namespace = dict(RefObj = RefObj)
...     exec class_template in namespace
...     return namespace["Foo%d" % index]
... 
>>> Foo1 = make_foo_object(1)
Created RefObj with ties to Foo1
>>> Foo2 = make_foo_object(2)
Created RefObj with ties to Foo2
>>> type(Foo1()), type(Foo2())
(<class 'Foo1'>, <class 'Foo2'>)

exec 的使用并不适合我(因为我预计很多阅读此问题的人不会这样做)但exec 完全是 python 的@ 987654336@ 类is implemented(见this line)。同样非常相关的是类的创建者(Raymond Hettinger)对exechere的这种使用的辩护。在这个辩护中,声明“命名元组的一个关键特征是它们完全等同于手写类”,这可能意味着使用type()还不如用exec...

有区别吗?为什么使用exectype()

我希望答案可能是两种方式都是相同的,只是 namedtuple 实现有很多 namedtuple 变量贯穿其中,并且通过为所有方法动态生成闭包来执行此操作使代码变得笨拙,但我想知道这是否还有其他内容。

关于我对exec 的不适,我确实认识到,如果不受信任的各方无法向其中注入恶意代码,那应该没问题...只是确保这让我感到紧张。

【问题讨论】:

  • blog.ccpgames.com/kristjan/2011/05/28/namedtuple-and-exec 也有关于这个问题的一些很好的讨论,还有另一篇关于它的好博客文章我现在看不到。您没有说明type 在您的情况下存在问题的任何原因,所以我不知道您为什么要打扰exec,因为您知道type 有效。 (显然除了好奇心)。
  • @agf - 很棒的链接,谢谢!我最初对type 没有意见,因为这两种方法都有效。我只是对这些差异感到好奇,并试图了解在namedtuple 中使用exec 的原因。提供的类/函数签名参数非常好,但是...我经常遇到需要使用 decorator 包的装饰器问题。
  • 您能否给出需要动态类创建背后的原因,还是只是出于好奇?
  • 从模板生成代码是一种基本而强大的技术。 ANTLR 和模板语言等工具利用这种技术取得了良好的效果。 namedtuple 的早期版本使用 type 而不是 exec 但这种方法不能很好地扩展——它很难维护,有点不可读,有奇怪的错误,而且不是自我记录。经过一些清理,exec 版本最终变得有些漂亮:hg.python.org/cpython/file/3.2/Lib/collections.py#l235 我承认 exec 让一些开发人员感到恶心,但这是一个心理问题,而不是技术性的。

标签: python namedtuple dynamic-class-creation


【解决方案1】:

我会在这里推荐type 而不是exec

事实上,class 语句只是调用type 的语法糖:类主体在其自己的命名空间内执行,然后传递给元类,如果没有,则默认为type已指定自定义元类。

这种方法不太容易出错,因为不需要在运行时解析代码,甚至可能会更快一些。

【讨论】:

    【解决方案2】:

    为什么不在函数中创建一个类?

    def foo_factory(index):
        name = 'Foo%d' % index
    
        class Foo(object):
            ref_obj = RefObj(name)
    
        Foo.__name__ = name
        return Foo
    

    【讨论】:

    • Foo 确实有一些动态的东西时——你不想每次都创建基本相同的东西。
    • 请展示您将如何以这种方式实现namedtuple 或保留签名的装饰器。说“一点都不灵活”是不正确的。
    • OP 绝不是询问如何 去做,而是为什么这两种不同的方式是不同的。
    • 好的,我删除了“不那么灵活”这句话,但不是因为namedtuple不能这样实现,而是因为根据实际问题,其他构造可能是要走的路。
    • @brandizzi 我只是想展示另一个 OP 可能没有考虑过的选项。
    【解决方案3】:

    使用 type() 而不是 exec 没有任何缺点。我认为雷蒙德的防守有点防守。您必须选择您认为最易读和易于理解的技术。这两种方式都会产生令人困惑的代码。

    你应该尽量避免一开始就创建类的代码,这是最好的。

    【讨论】:

    • @agfL 抱歉,我不确定您指的是什么。
    • exec 对于decorator 库的一个论据是更容易保留装饰函数/方法的签名。我认为 namedtuple 的一个论点是,使用 exec 构建的类与手动编码后的类完全相同,而使用 type 时它们是不同的。
    • 我绝对希望避免动态创建类,但我有一个案例,我需要根据我不能保证在上课前在范围内的参数创建类属性创建。动态类似乎是目前最简单的方法。
    • @Russ 也许您可以在事后简单地设置类属性?
    猜你喜欢
    • 2013-11-25
    • 1970-01-01
    • 2011-09-23
    • 1970-01-01
    • 1970-01-01
    • 2011-07-09
    • 2020-11-06
    • 1970-01-01
    • 2013-09-13
    相关资源
    最近更新 更多