Подтвердить что ты не робот

Почему модуль namedtuple не использует метакласс для создания объектов класса nt?

Я потратил некоторое время на изучение модуля collections.namedtuple несколько недель назад. Модуль использует функцию factory, которая заполняет динамические данные (имя нового класса namedtuple и имена атрибутов класса) в очень большую строку. Затем exec выполняется со строкой (которая представляет код) в качестве аргумента, и возвращается новый класс.

Кто-нибудь знает, почему это было сделано таким образом, когда есть конкретный инструмент для такого рода вещей, который легко доступен, т.е. метакласс? Я не пытался сделать это сам, но похоже, что все, что происходит в модуле namedtuple, могло быть легко выполнено с использованием метакласса namedtuple, например:

class namedtuple(type):

и т.д.

4b9b3361

Ответ 1

В проблеме 3974 есть несколько советов. Автор предложил новый способ создания названных кортежей, который был отклонен со следующими комментариями:

Кажется, преимущество исходной версии в том, что она быстрее, благодаря критическим методам жесткого кодирования. - Антуан Питроу

Нет ничего нечестивого в использовании exec. Более ранние версии использовали другие и они оказались излишне сложными и неожиданными проблемы. Это ключевая функция для названных кортежей, что они точно эквивалентно рукописному классу. - Раймонд Хеттингер

Кроме того, вот часть описания оригинального рецепта namedtuple:

... рецепт превратился в его текущий стиль exec, где мы получаем все быстродействующего встроенного аргумента Python для проверки. Новый стиль здания и исполнение шаблона сделали как __new__, так и __repr__ функционирует быстрее и чище, чем в предыдущих версиях этого рецепта.

Если вы ищете альтернативные варианты:

Ответ 2

В качестве побочного элемента: Другим возражением, которое я вижу чаще всего при использовании exec, является то, что некоторые местоположения (читые компании) отключили его по соображениям безопасности.

Помимо расширенных Enum и NamedConstant, библиотека aenum * также имеет NamedTuple, который является metaclass.


* aenum написан автором Enum и enum34 backport.

Ответ 3

Вот другой подход.

""" Subclass of tuple with named fields """
from operator import itemgetter
from inspect import signature

class MetaTuple(type):
    """ metaclass for NamedTuple """

    def __new__(mcs, name, bases, namespace):
        cls = type.__new__(mcs, name, bases, namespace)
        names = signature(cls._signature).parameters.keys()
        for i, key in enumerate(names):
            setattr(cls, key, property(itemgetter(i)))
        return cls

class NamedTuple(tuple, metaclass=MetaTuple):
    """ Subclass of tuple with named fields """

    @staticmethod
    def _signature():
        " Override in subclass "

    def __new__(cls, *args):
        new = super().__new__(cls, *args)
        if len(new) == len(signature(cls._signature).parameters):
            return new
        return new._signature(*new)

if __name__ == '__main__':
    class Point(NamedTuple):
        " Simple test "
        @staticmethod
        def _signature(x, y, z): # pylint: disable=arguments-differ
            " Three coordinates "
    print(Point((1, 2, 4)))

Если у этого подхода есть какое-либо достоинство, то это простота. Это было бы еще проще без NamedTuple.__new__, который служит только для обеспечения принудительного подсчета количества элементов. Без этого он, к счастью, допускает дополнительные анонимные элементы после именованных, и основной эффект пропуска элементов - это IndexError для пропущенных элементов при доступе к ним по имени (с небольшой работой, которая может быть переведена в AttributeError). Сообщение об ошибке для неправильного количества элементов немного странно, но оно дает понять, в чем дело. Я не ожидал, что это будет работать с Python 2.

Есть место для дальнейших осложнений, таких как метод __repr__. Я понятия не имею, как производительность сравнивается с другими реализациями (может помочь кэширование длины подписи), но я предпочитаю соглашение о вызовах по сравнению с собственной реализацией namedtuple.

Ответ 4

Есть еще одна причина, по которой ни один из других ответов не встречается *.

Класс может иметь только 1 метакласс. Одна из причин этого заключается в том, что метакласс действует как фабрика, которая создает класс. Невозможно смешать фабрики вместе. Вы должны создать либо "комбинаторную фабрику", которая знает, как вызывать несколько фабрик в правильном порядке, либо "дочернюю фабрику", которая знает о "родительской фабрике" и использует ее правильно.

Если namedtuple использует свой собственный метакласс, наследование с использованием любого другого метакласса будет namedtuple:

>>> class M1(type): ...
...
>>> class M2(type): ...
...
>>> class C1(metaclass=M1): ...
...
>>> class C2(metaclass=M2): ...
...
>>> class C(C1, C2): ...
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Вместо этого, если вы хотите иметь свой собственный метакласс и наследовать от класса namedtuple, вам придется использовать какой-то так называемый метакласс namedtuple_meta чтобы сделать это:

from namedtuple import namedtuple_meta  # pretending this exists

class MyMeta(type): ...

class MyMetaWithNT(namedtuple_meta, MyMeta): ...

class C(metaclass=MyMetaWithNT): ...

... или просто наследовать пользовательский метакласс напрямую от namedtuple_meta:

class MyMeta(namedtuple_meta): ...

class C(metaclass=MyMeta): ...

Поначалу это выглядит легко, но написание собственного метакласса, который хорошо сочетается с некоторым (сложным) метаклассом nt, может стать проблемой очень быстро. Это ограничение, вероятно, возникнет не часто, но достаточно часто, что будет препятствовать использованию namedtuple. Так что, безусловно, преимущество заключается в том, чтобы все классы namedtuple были type type, и это устраняет сложность пользовательского метакласса.


Комментарий Раймонда Хеттингера намекает на это:

Для именованных кортежей ключевым моментом является то, что они в точности эквивалентны рукописному классу.