Шаг 19.
Python: сборник рецептов. Структуры данных и алгоритмы. Отображение имен на последовательность элементов

    На этом шаге мы рассмотрим инструменты для решения этой задачи.

Задача

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

Решение

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

>>> from collections import namedtuple
>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
>>> sub = Subscriber('jonesy@example.com', '2012-10-19')
>>> sub
Subscriber(addr='jonesy@example.com', joined='2012-10-19')
>>> sub.addr
'jonesy@example.com'
>>> sub.joined
'2012-10-19'
>>> 

    Хотя экземпляр namedtuple (именованного кортежа) выглядит так же, как и обычный экземпляр класса, он взаимозаменяем с кортежем и поддерживает все обычные операции кортежей, такие как индексирование и распаковка. Например:

>>> len(sub)
2
>>> addr, joined = sub
>>> addr
'jonesy@example.com'
>>> joined
'2012-10-19'
>>> 

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

    Чтобы проиллюстрировать это, приведем пример кода, использующего обычные кортежи:

>>> def compute_cost(records):
	total = 0.0
	for rec in records:
		total += rec[1] * rec[2]
	return total

>>> 

    Использование позиционного обращения к элементам часто делает код немного менее выразительным и более зависимым от структуры записей. А вот версия с использованием именованного кортежа:

>>> from collections import namedtuple
>>> Stock = namedtuple('Stock', ['name', 'shares', 'price'])
>>> def compute_cost(records):
	total = 0.0
	for rec in records:
		s = Stock(*rec)
		total += s.shares * s.price
	return total

>>> 

    Естественно, вы можете избежать явной конвертации в именованный кортеж Stock, если последовательность records из примера уже содержит такие экземпляры.

Обсуждение

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

>>> s = Stock('ACME', 100, 123.45)
>>> s
Stock(name='ACME', shares=100, price=123.45)
>>> s.shares = 75
Traceback (most recent call last):
  .   .   .   .
AttributeError: can't set attribute

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

>>> s = s._replace(shares=75)
>>> s
Stock(name='ACME', shares=75, price=123.45)
>>> 

    Тонкость использования метода _replace() заключается в том, что он может стать удобным способом наполнить значениями именованный кортеж, у которого есть опциональные или отсутствующие поля. Чтобы сделать это, создайте прототип кортежа, содержащий значения по умолчанию, а затем применяйте _replace() для создания новых экземпляров с замененными значениями. Например:

>>> from collections import namedtuple
>>> Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])
>>> # Создание экземпляра прототипа
>>> stock_prototype = Stock('', 0, 0.0, None, None)
>>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
>>> Функция для преобразования словаря в Stock
>>> def dict_to_stock(s):
	return stock_prototype._replace(**s)

    Вот пример работы этого кода:

>>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
>>> dict_to_stock(a)
Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
>>> b = {'name': 'ACME', 'shares': 100, 'price': 123.45, 'date': '12/17/2012'}
>>> dict_to_stock(b)
Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)
>>> 

    Последнее, но важное замечание: стоит отметить, что если вашей целью является создание эффективной структуры данных, которая позволяет менять различные атрибуты экземпляров, использование именованных кортежей - не лучший вариант. Вместо них стоит определить класс с использованием __slots__().

    На следующем шаге мы рассмотрим одновременное преобразование и сокращение (свертку) данных.




Предыдущий шаг Содержание Следующий шаг