В чем разница между синтаксисом класса и типом ()? - PullRequest
1 голос
/ 07 ноября 2019

Мне хорошо известен тот факт, что классы могут быть объявлены динамически в 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 один. Я экспериментировал с обеими функциями и не смог найти заметных различий между классами, полученными из обеих функций. Я надеюсь на четкое объяснение различий между двумя вышеупомянутыми шаблонами / синтаксисами в любом аспекте (с точки зрения производительности или любого другого), в котором они различаются.

Спасибо,

Ответы [ 2 ]

4 голосов
/ 07 ноября 2019

Оператор 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. EnumMeta из модуля enum, на мой взгляд, растягивает то, что вы должны делать с одним. Вот действительно оскорбительный пример. На самом деле метакласс не должен быть типом;он просто должен вызываться и должен принимать три позиционных аргумента. Он также может принимать дополнительные ключевые аргументы. Затем он может делать с этими аргументами все, что захочет, и возвращать все, что захочет.

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

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

Foo даже не класс;это связано с 3.

0 голосов
/ 07 ноября 2019

Вы выбрали правильный пример, как вы динамически создаете класс со своим вторым примером? Вы не можете правильно сделать это, поэтому первый случай предпочтительнее. Посмотрите на этот игрушечный пример, основанный на вашем.

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

В вашем втором примере сложно составить класс с различными атрибутами и методами.

...