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 — это фабрика классов: она принимает имя типа и список полей, а возвращает новый класс кортежа. Один раз описали поля — дальше создаёте записи как объекты:
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 задаёт значения по умолчанию для полей справа, и тогда часть аргументов можно не указывать:
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] — работает, но каждый раз требует расшифровки.
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 из словаря собирают явно, перечисляя значения по ключам.
| tuple | namedtuple | dict | |
|---|---|---|---|
| Доступ | 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))
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()?
Создайте namedtuple City с полями name и population. Сделайте две записи: Москва с 13 и Тула с 4 (числа в миллионах, для краткости) и напечатайте каждую в формате «Москва: 13 млн». Затем напечатайте сумму населения обоих городов.
Что такое 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-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Множества · Урок 14
Функция возвращает несколько значений: return с кортежем
Функция возвращает пару значений одной строкой: запятая в return собирает кортеж, распаковка раскладывает его по переменным. min и max за один проход, звёздочка для остатка.
Множества · Урок 11
Tuple в Python: неизменяемый кортеж и доступ по индексу
Кортеж хранит фиксированный набор значений и защищает его от изменений: создали — и состав застыл. Создание, доступ по индексу, IndexError и то, почему запятая главнее скобок.
Множества · Урок 17
Множества и словари Python: hash и общие механизмы
Множество и словарь построены на одном механизме: hash назначает каждому элементу полку, и проверка in становится мгновенной. Ключи словаря — готовое множество, над которым работают & и |.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Словари · Урок 17
Словарь как данные: разбор ответа API
Ответ API — это вложенный словарь: достаём поля по цепочке ключей, читаем безопасно через get с умолчанием, разбираем списки записей и собираем из ответа готовый отчёт.
безопасный доступ словарь pythonизвлечение полей словарь python
Flask · Урок 14
Валидация в Flask API: плохие запросы и коды 400
Четырнадцатый урок курса Flask: API из урока 13 падает на первом же кривом запросе. Добавляем валидацию — честные 400 с понятным текстом ошибки, 404 для отсутствующих записей и тесты на плохие входы через test_client.
flask проверка полей json
Множества · Урок 12
Кортеж против списка в Python: что и когда использовать
У обоих есть индексы, len и in, но список можно менять, а кортеж — нет. Четыре отличия: изменяемость, ключ словаря, память и правило выбора за три секунды.
python кортеж против спискакортеж или список python