Ответ 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
использует метакласс, чтобы использовать другой метакласс для создания экземпляра класса.