Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 15 из 20 Начальный 15 мин 100 XP

namedtuple в Python: кортеж с именами полей

namedtuple даёт кортежу имена полей: p.x вместо p[0], читаемый repr и всё кортежное на месте — индексы, распаковка, сравнение. Шесть строк кода, и записи перестают быть наборами индексов.

Редакция Питоники

В предыдущем уроке функция вернула пару значений, и распаковка дала каждой величине имя. Но как только пара начинает путешествовать по программе — лежать в списке, передаваться в другую функцию, — имена остаются только в голове автора. Запись point[0] не говорит, координата это или имя пользователя. namedtuple из стандартного модуля collections закрывает именно эту дыру: кортеж, у которого у каждого поля есть имя. Внутри — всё тот же tuple из урока 11, снаружи — читаемые поля.

Проблема: индексы без имён

Обычный кортеж не помнит, что он значит. Две записи — точка на плоскости и гость с числом визитов — выглядят снаружи одинаково:

два кортежа, ноль подсказок
point = (3, 4)
guest = ('Анна', 3)

print(point[0] + point[1])
print(guest[0], guest[1])
Вывод
7
Анна 3

Код работает, но каждый, кто его читает — включая вас через месяц, — вынужден помнить: нулевой индекс у guest это имя, первый — число визитов. Перепутать места при создании записи Python не заметит: guest = (3, 'Анна') — тоже кортеж, только смыслы поменяны местами. Чем сложнее запись, тем дороже эта памятка.

namedtuple: имя для каждого поля

namedtuple — это фабрика классов: она принимает имя типа и список полей, а возвращает новый класс кортежа. Один раз описали поля — дальше создаёте записи как объекты:

namedtuple с двумя полями
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])

p = Point(3, 4)
print(p.x, p.y)
print(p)
print(type(p).__name__)
Вывод
3 4
Point(x=3, y=4)
Point

Три строки дали то, чего у обычного кортежа не было. Доступ по имени: p.x вместо p[0], и перепутать поля при чтении невозможно. Печать кортежа показывает и поля, и значения: Point(x=3, y=4) — запись сама рассказывает, из чего состоит. А вызов type показывает, что namedtuple действительно создал новый класс с именем Point: переменная Point — это класс, p — его экземпляр.

Это всё ещё кортеж

Главная сила namedtuple — он не заменяет кортеж, а наследует его. namedtuple остаётся кортежем со всеми правами: доступ по индексу, длина, распаковка и сравнение с обычным tuple работают без оговорок.

кортежные права в действии
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)

print(p[0], p[1])
print(len(p))
x, y = p
print('распаковка:', x, y)
print(p == (3, 4))
Вывод
3 4
2
распаковка: 3 4
True

Равенство с обычным кортежем сработало: namedtuple сравнивается по значениям, и (3, 4) ему родной. Значит, namedtuple можно передавать туда, где ждут кортеж: в функции из урока 14, в ключи словаря, в множества — он хешируем, как любой кортеж из неизменяемых значений. Старый код с индексами продолжит работать, новый — читает по именам.

Поля строкой и значения по умолчанию

Список полей — не единственная запись: строка с именами через пробел короче и встречается в чужом коде чаще. А параметр defaults задаёт значения по умолчанию для полей справа, и тогда часть аргументов можно не указывать:

поля строкой и defaults
from collections import namedtuple

Visitor = namedtuple('Visitor', 'name days')
Returning = namedtuple('Returning', ['name', 'days'], defaults=[1])

v = Visitor('Анна', 3)
r = Returning('Борис')

print(v.name, '-', v.days)
print(r.name, '-', r.days)
Вывод
Анна - 3
Борис - 1

defaults раскладываются на поля справа: у Returning единица досталась полю days, а обязательное имя осталось обязательным. Приём удобен для записей, где чаще всего повторяется одно и то же значение — визит по умолчанию одноразовый, и только постоянные гости требуют второго числа.

Список записей: где namedtuple раскрывается

Один namedtuple — приятность, список namedtuple — рабочий инструмент. Журнал гостей превращается в таблицу, у которой вместо индексов колонки:

журнал гостей как список записей
from collections import namedtuple

Visitor = namedtuple('Visitor', 'name days')
visitors = [
    Visitor('Анна', 3),
    Visitor('Борис', 1),
    Visitor('Вера', 2),
]

total = 0
for v in visitors:
    print(v.name, '-', v.days)
    total += v.days

print('сумма визитов:', total)
print('самый частый гость:', max(visitors, key=lambda v: v.days).name)
Вывод
Анна - 3
Борис - 1
Вера - 2
сумма визитов: 6
самый частый гость: Анна

Строка v.name читается без заглядывания в памятку, а max с key=lambda v: v.days находит запись с наибольшим полем и забирает из неё имя. С обычными кортежами то же самое выглядело бы как v[0] и key=lambda v: v[1] — работает, но каждый раз требует расшифровки.

_replace возвращает новую запись
from collections import namedtuple

Point = namedtuple('Point', 'x y')
p = Point(3, 4)
moved = p._replace(y=10)

print(p)
print(moved)
Вывод
Point(x=3, y=4)
Point(x=3, y=10)

Исходная точка не сдвинулась: _replace построил новый кортеж, и это ровно то поведение, которое у кортежей по умолчанию. Если полям нужно меняться часто и по месту — namedtuple не тот инструмент: берите словарь или класс из курса ООП.

плохие имена полей
from collections import namedtuple

try:
    Bad = namedtuple('Bad', 'name _days')
except ValueError as e:
    print('ValueError:', e)
Вывод
ValueError: Field names cannot start with an underscore: '_days'

_asdict: мост к словарю

Когда запись нужно превратить в словарь — отправить в JSON, передать функции, ждущей ключи, — есть готовый метод:

запись превращается в словарь
from collections import namedtuple

Visitor = namedtuple('Visitor', 'name days')
v = Visitor('Анна', 3)

d = v._asdict()
print(d)
print(d['days'])
Вывод
{'name': 'Анна', 'days': 3}
3

Порядок полей в словаре — тот же, что при объявлении namedtuple: словарь Python помнит порядок вставки, и колонки не перемешаются. Обратного хода нет: namedtuple из словаря собирают явно, перечисляя значения по ключам.

tuplenamedtupledict
Доступt[0] — по индексуp.x и p[0]d['x'] — по ключу
Печать(3, 4)Point(x=3, y=4){'x': 3, 'y': 4}
ИзменяемостьНетНетДа
Ключ словаряДаДаНет
Когда братьПара «в уме»Записи с полямиРастущий набор

Правило выбора простое: если данные — фиксированный набор полей, который читают чаще, чем меняют, — namedtuple даёт имена, не теряя кортежных прав. Если состав полей растёт и меняется — словарь.

Что дальше

namedtuple снял последую проблему кортежей — безымянность. Дальше две практические темы: дубликаты одной строкой — как множество и словарь чистят список от повторов, и общие механизмы множеств и словарей — почему namedtuple годится в ключ, а список нет.

Пять строк на фабрику класса — и записи в программе читаются как записи: у каждого поля имя, у каждого кортежа честный repr с перечислением полей. Старый кортежный код продолжает работать, потому что namedtuple — это кортеж.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

from collections import namedtuple

Book = namedtuple('Book', 'title pages')
b = Book('Муму', 32)

print(b.title, b.pages)
print(b[1])
print(len(b))
from collections import namedtuple

Item = namedtuple('Item', ['name', 'qty'], defaults=[0])
a = Item('гвоздь')
b = Item('молоток', 2)

print(a.qty, b.qty)
print(a == Item('гвоздь'))
from collections import namedtuple

P = namedtuple('P', 'x y')
pts = [P(1, 2), P(3, 4), P(1, 2)]
print(len(set(pts)))
print(P(1, 2) in set(pts))
Проверь себя
0 / 7

1. Что возвращает вызов namedtuple('Point', ['x', 'y'])?

2. Что напечатает print(p) для p = Point(3, 4)?

3. Сравнение p == (3, 4) для namedtuple Point(x=3, y=4) даст...

4. Что произойдёт при попытке p.y = 10?

5. Куда лягут defaults=[1] у namedtuple('Returning', ['name', 'days'], defaults=[1])?

6. Почему список namedtuple можно положить в множество, а список словарей нельзя?

7. Что делает метод _asdict()?

Карточки терминов
Запомнено: 0 / 8
Практика

Создайте namedtuple City с полями name и population. Сделайте две записи: Москва с 13 и Тула с 4 (числа в миллионах, для краткости) и напечатайте каждую в формате «Москва: 13 млн». Затем напечатайте сумму населения обоих городов.

practice.py
Вопросы и ответы по уроку

Что такое namedtuple в Python простыми словами?

Это кортеж, у которого каждое поле имеет имя. namedtuple('Point', ['x', 'y']) создаёт класс, записи которого читаются и по имени — p.x, — и по индексу — p[0]. Печать показывает поля: Point(x=3, y=4). Внутри это обычный кортеж со всеми его правами.

Урок 11: tuple — неизменяемый кортеж

Чем namedtuple отличается от словаря?

У namedtuple фиксированный набор полей, заданный при создании класса, и нет изменений после создания; у словаря поля добавляются на ходу и значения можно перезаписывать. namedtuple хешируем и годится в множество и ключ словаря, словарь — нет. Мост между ними — метод _asdict().

Можно ли изменить поле namedtuple?

Нет: namedtuple остаётся неизменяемым кортежем, присваивание p.y = 10 падает с AttributeError. Метод _replace(y=10) возвращает новый кортеж с изменённым полем, не трогая старый. Если поля меняются часто — нужен словарь или обычный класс.

Когда брать namedtuple, а когда обычный tuple?

Обычный tuple — для коротких пар, где смысл очевиден из контекста: координаты в распаковке, возврат двух значений из функции. namedtuple — для записей, которые путешествуют по программе и читаются в разных местах: журнал гостей, строки таблицы, точки со смыслом полей.

Урок 14: возврат нескольких значений

Почему namedtuple можно класть в множество и в ключ словаря?

Потому что это кортеж из ваших значений: если все поля неизменяемы — строки и числа, — хеш считается и не меняется. Список и словарь такого экзамена не сдают: у них нет хеша, и Python отвечает TypeError: unhashable type.

Урок 17: множества и словари — общий механизм

Понравился урок? Сошлитесь на него

«namedtuple остаётся кортежем со всеми правами: доступ по индексу, длина, распаковка и сравнение с обычным tuple работают без оговорок.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.