Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs
Начиная с этого
занятия, мы с вами будем рассматривать полезные функции и классы модуля collections стандартной
библиотеки. И начнем с функции namedtuple, которая позволяет создавать
именованные кортежи.
До сих пор в
программах мы использовали обычные кортежи, которые можно задавать разными
способами, например, так:
t1 = (1, 2, 3)
t2 = 5, 7
t3 = tuple("abc")
Их особенность в
том, что они относятся к неизменяемым, упорядоченным типам данных с доступ к
отдельным элементам по их индексам. Например:
a = t1[0] # 1
b = t3[-1] # 'c'
Но нельзя менять
значения уже созданного кортежа:
В практике
программирования такие кортежи играют свою значимую роль. Однако в некоторых
случаях хотелось бы иметь более широкий функционал, в частности, доступ к отдельным
элементам не по числовым индексам, а по осмысленным именам. Например, кортеж
хранящий информацию о пользователе с набором полей:
- 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 полезно
использовать, когда имена полей формируются автоматически в процессе работы
программы, например, на основе полей из БД. В этом случае сложно заранее
проконтролировать их корректность и на помощь приходит автоматическая замена
имен.
Операции с именованными кортежами
С именованными
кортежами можно выполнять все те же самые операции, что и с обычными. Например,
мы можем сложить два кортежа:
и получить
новый, но уже обычный кортеж с объединенным набором данных:
('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'
Помимо этого у
именованных кортежей имеется весьма полезный набор методов. Например, метод:
возвращает словарь,
у которого ключи – это названия атрибутов, а значения – соответствующие
значения атрибутов:
{'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