【问题标题】:What is the difference between class syntax and type()?类语法和 type() 有什么区别?
【发布时间】:2020-03-04 07:36:30
【问题描述】:

我很清楚可以在 python 中使用type 动态声明类,并在这里和那里使用它。但是我仍然不清楚这两个功能之间有什么区别。

def class_factory():
    def init(self, attr):
        self.attr = attr
    class_ = type('DynamicClass', (), {'__init__' : init})
    return class_

和

def class_factory():
    class DynamicClass:
        def __init__(self, attr):
            self.attr = attr
    return DynamicClass

我已经看到很多代码库(如 django)中使用的第一个模式,但到目前为止从未见过第二个模式。我仍然发现第二个在语法上更清晰。

我正在努力寻找正确的解释来解释为什么人们使用first pattern 来声明动态类而不是second。我对这两个函数都进行了试验,但在从这两个函数获得的类之间没有发现任何显着差异。我希望从它们不同的任何方面(性能方面或任何其他方面)清楚地解释上述两种模式/语法之间的差异。

谢谢,

【问题讨论】:

  • 我之前已经使用过第二种模式,但很少使用。正如其他一些答案所述,直接使用type() 提供了最大的灵活性。例如,如果您希望能够计算必须使用的类的 name type();类似地,如果类体是以某种方式计算的(甚至是它的基类)。

标签: python class dynamic dynamic-class-creation dynamic-class


【解决方案1】:

你选择了一个正确的例子,你如何用你的第二个例子动态地创建一个类?你不能正确地做到这一点,这就是为什么首选第一种情况。看看这个基于你的玩具示例。

def class_factory(many_args=False):
    def init1(self, attr):
        self.attr = attr

    def init2(self, *attr):
        self.attr = attr

    init = init1
    if many_args:
        init = init2

    class_ = type('DynamicClass', (), {'__init__': init})
    return class_

many = class_factory(True)(1, 2, 3, 4)
one = class_factory()(1)

结果:

>>> print(many.attr, one.attr)
(1, 2, 3, 4) 1

在您的第二个示例中,很难组合具有不同属性和方法的类。

【讨论】:

  • 您可以在class 语句中放置if 语句,以根据many_args 的值选择使用__init__ 的哪个定义。
  • 好吧,同样,仍然可以分配DynamicClass.__init__ = init2 if many_args else init1。
【解决方案2】:

class 语句是用于调用元类的声明性语法。

class Foo(Bar, metaclass=SomeMeta):  # If not specified, the metaclass is type
    ...

等价于

Foo = SomeMeta('Foo', (Bar,) {...})

其中{...} 是从class 语句的主体构造的一些映射。仅作为一个简单的示例,无需详细说明:

class Foo(Bar, metaclass=SomeMeta):
    some_attr = "foo string"

    def __init__(self, x=9):
        self.y = x = 3

将定义一个函数名__init__,然后将{'__init__': __init__, 'some_attr': 'foo string'} 传递给SomeMeta。名称__init__ 仅存在于由class 语句创建的临时命名空间中,不会影响可能存在于语句之外的任何名称__init__。

要回答您的问题,有时自己构建元类的第三个参数比让class 语句为您做更灵活或更简单。第二个论点也是如此。 first 参数由class 语句的语法固定,但如果您自己调用元类,则可以在运行时生成。


请注意,这意味着您可以真的滥用class 声明。在我看来,来自enum 模块的EnumMeta 扩展了你应该用它做什么。这是一个真正滥用的例子。元类实际上不必是类型;它必须是可调用的,并且需要接受三个位置参数。它也可以接受额外的关键字参数。然后它可以使用这些参数做任何它想做的事情,并返回它想要的任何东西。

def FooMeta(name, bases, dct, msg):
    print(f'{msg} from {name}')
    return 3

class Foo(metaclass=FooMeta, msg="hi"):
    pass

Foo 甚至不是一个类;它绑定到3。

【讨论】:

  • 所以基本上第一个语法通过让我们构造元类的第一个和第三个参数给我们更多的控制。是否还有其他差异(性能方面)?
  • 并非如此。如果代码中的瓶颈变成了使用class 语句和调用type 之间的区别,那么恭喜你编写了一些非常快的代码。
猜你喜欢
  • 2015-07-17
  • 2015-04-15
  • 1970-01-01
  • 1970-01-01
  • 2020-11-14
  • 1970-01-01
  • 1970-01-01
  • 2010-12-12
相关资源
最近更新 更多