Именованные кортежи namedtuple модуля collections

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Смотреть материал на YouTube | RuTube

Начиная с этого занятия, мы с вами будем рассматривать полезные функции и классы модуля collections стандартной библиотеки. И начнем с функции namedtuple, которая позволяет создавать именованные кортежи.

До сих пор в программах мы использовали обычные кортежи, которые можно задавать разными способами, например, так:

t1 = (1, 2, 3)
t2 = 5, 7
t3 = tuple("abc")

Их особенность в том, что они относятся к неизменяемым, упорядоченным типам данных с доступ к отдельным элементам по их индексам. Например:

a = t1[0] # 1
b = t3[-1] # 'c'

Но нельзя менять значения уже созданного кортежа:

t2[1] = 10 # ошибка

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

  • name – имя;
  • lastname – фамилия;
  • age – возраст;
  • job – профессия.

И, затем, обращаться к данным не только по индексу, но еще и по имени поля. И так для всех полей. Как раз такой функционал предоставляют именованные кортежи namedtuple.

Создание именованных кортежей

Создание именованных кортежей происходит в два этапа. На первом этапе с помощью функции namedtuple создается класс кортежа с определенным набором полей. Например:

Person = namedtuple('Person', 'name lastname age job')

Здесь первый аргумент 'Person' – это имя создаваемого класса, а второй 'name lastname age job' – набор полей, записанных через пробел. Поля в созданном классе кортежа Person будут идти в указанном порядке.

После этого мы можем создавать множество кортежей типа Person, как экземпляры этого класса. Например:

p1 = Person('Sergey', 'Balakirev', 45, 'Teacher')
p2 = Person('Fedor', 'Dostoevsky', 51, 'Writer')

И получать доступ к элементам либо через индексы:

p1[0] # 'Sergey'
p1[1] # 'Balakirev'

Либо через атрибуты (имена):

p1.age # 45
p2.job # 'Writer'

Причем менять значения в уже созданных кортежах не получится. Команда:

p2.name = 'Alex' # ошибка

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

Итак, раз именованные кортежи – это итерируемая коллекция данных, значит, их элементы можно перебирать с помощью цикла for. Сделаем это:

for x in p1:
    print(x, end="; ")

Увидим строчку:

Sergey; Balakirev; 45; Teacher;

Или же, можно воспользоваться оператором распаковки коллекций, которая работает по аналогии с обычными кортежами:

print(*p1) # Sergey Balakirev 45 Teacher

Давайте для примера создадим еще один класс типа namedtuple. Пусть он будет такой:

BMW = namedtuple('Car', ['model', 'dist', 'weight', 'price'])

Обратите внимание на две важные особенности. Первая – поля можно указывать в виде списка из строк (и вообще любого итерируемого объекта, возвращающего строки). Вторая – имя класса 'Car' и переменная BMW, которая на него ссылается, могут иметь разные имена. Для языка Python не важно какая переменная ссылается на тот или иной класс. Это эквивалентно конструкции:

class Car2:
    pass
 
 
BMW2 = Car2

При этом тип данных, с которым связана переменная BMW, будет класс Car:

print(BMW) # <class '__main__.Car'>

После того, как класс создан, мы можем формировать его именованные кортежи. Например:

bmw1 = BMW('X3', 100, 1.2, 3_200_000)
bmw2 = BMW('X5', 6201, 1.5, 5_100_000)

Особенности создания именованных кортежей

Вообще фабричная функция namedtuple создает новые классы на базе стандартного класса tuple, лишь немного меняя его функционал. В этом легко убедиться, выполнив команду:

issubclass(Person, tuple) # True

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

A = namedtuple('ClassA', 'name 1a &b _cd') # 1a, &b, _d - недопустимые имена
B = namedtuple('ClassB', 'arg arg old a2') # дважды одно и то же имя arg
C = namedtuple('ClassC', 'arg class for) # использование ключевых слов class, for

Поэтому нужно либо верно задавать наборы полей, либо использовать дополнительный параметр rename функции namedtuple со значением True:

C = namedtuple('ClassC', 'arg class for', rename=True)

В этом случае все неверные поля будут заменены на порядковые с символом подчеркивания:

print(C._fields) # ('arg', '_1', '_2')

Параметр rename=True полезно использовать, когда имена полей формируются автоматически в процессе работы программы, например, на основе полей из БД. В этом случае сложно заранее проконтролировать их корректность и на помощь приходит автоматическая замена имен.

Операции с именованными кортежами

С именованными кортежами можно выполнять все те же самые операции, что и с обычными. Например, мы можем сложить два кортежа:

res = p1 + p2

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

('Sergey', 'Balakirev', 45, 'Teacher', 'Fedor', 'Dostoevsky', 51, 'Writer')

Обратите внимание, что при объединении именованных кортежей возвращается обычный кортеж tuple. И это логично, так как нельзя объединить два namedtuple с одними и теми же именами полей. Однако легко сформировать обычный кортеж, что и делается.

Также мы можем к именованному кортежу добавлять обычный кортеж:

res =p1 + (2, 4) # ('Sergey', 'Balakirev', 45, 'Teacher', 2, 4)

Результатом будет также обычный кортеж с соответствующим набором данных.

Так как классы именованных кортежей унаследованы от класса tuple, то для них доступны все те же самые методы, что и обычных кортежей. Например:

p1.index(45) # возвращает индекс элемента со значением 45
p2.count('Fedor') # возвращает число элементов со значением 'Fedor'

Помимо этого у именованных кортежей имеется весьма полезный набор методов. Например, метод:

p1._asdict()

возвращает словарь, у которого ключи – это названия атрибутов, а значения – соответствующие значения атрибутов:

{'name': 'Sergey', 'lastname': 'Balakirev', 'age': 45, 'job': 'Teacher'}

Я напомню, что начиная с версии Python 3.7 словари стали упорядоченными. То есть, здесь гарантируется порядок следования ключей в соответствии с порядком атрибутов именованного кортежа.

Следующий метод _replace() позволяет создать новый именованный кортеж с заменой значений отдельных полей:

res_p = p1._replace(age=32) # Person(name='Sergey', lastname='Balakirev', age=32, job='Teacher')

Новый кортеж res_p будет содержать все те же значения, что и кортеж p1, кроме поля age. При этом кортеж p1 остается без изменений.

Наконец метод _make() позволяет также создавать копию кортежа, но с полным указанием набора данных для всех полей:

res_p2 = p2._make(['Stepan', 'Tur', 54, 'Driver']) # Person(name='Stepan', lastname='Tur', age=54, job='Driver')

Исходный кортеж p2 остается без изменений.

Вот что из себя представляют именованные кортежи. Они полезны в случаях, когда нужно создать легкий, быстрый и неизменяемый объект для группировки нескольких связанных данных. Особенно полезно применять их в ситуациях, когда стандартные классы выглядят излишними или громоздкими.

Именованные кортежи особенно хорошо подходят для простых структур данных, таких как координаты точек, конфигурации настроек или промежуточные результаты вычислений.

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Видео по теме