Какая разница между namedtuple и NamedTuple?

В документации модуля typing указано, что два фрагмента кода ниже эквивалентны.

from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    id: int

а также

from collections import namedtuple

Employee = namedtuple('Employee', ['name', 'id'])

Являются ли они то же самое или, если нет, каковы различия между этими двумя реализациями?

Ответы

Ответ 1

Тип, сгенерированный подклассом typing.NamedTuple, эквивалентен типу collections.namedtuple, но с __annotations__ _field_types __annotations__, _field_types и _field_defaults. Сгенерированный код будет вести себя одинаково для всех практических целей, поскольку в настоящее время в Python ничего не действует для этих атрибутов, связанных с типизацией (хотя ваша IDE может их использовать).

Как разработчик, использование модуля typing для именованных кортежей позволяет создать более естественный декларативный интерфейс:

  • Вы можете легко указать значения по умолчанию для полей (edit: в Python 3.7 collections.namedtuple получил новое ключевое слово defaults так что это больше не является преимуществом)
  • Вам не нужно повторять имя типа дважды ("Сотрудник")
  • Вы можете настроить тип напрямую (например, добавив строку документации или некоторые методы)

Как и прежде, ваш класс будет подклассом tuple, а экземпляры, как обычно, будут экземплярами tuple. Интересно, что ваш класс не будет подклассом NamedTuple:

>>> class Employee(NamedTuple):
...     name: str
...     id: int
...     
>>> issubclass(Employee, NamedTuple)
False
>>> isinstance(Employee(name='guido', id=1), NamedTuple)
False

Если вы хотите знать почему, читайте дальше для получения дополнительной информации о текущих деталях реализации. typing.NamedTuple - это класс, он использует метаклассы и пользовательский __new__ для обработки аннотаций, а затем делегирует collections.namedtuple, во всяком случае, для создания и возврата типа. Как вы могли догадаться из соглашения о нижнем регистре имен, collections.namedtuple - это не тип/класс, а фабричная функция. Он работает, создавая строку исходного кода Python, а затем вызывая exec для этой строки. Генерируются конструктор вынул из пространства имен и включен в 3-аргументе вызове метакласса type, чтобы построить и вернуть свой класс. Это объясняет странный разрыв наследования, который мы видели выше. NamedTuple использует метакласс, чтобы использовать другой метакласс для создания экземпляра класса.