На этом шаге мы рассмотрим инструменты для решения этой задачи.
У вас есть код, который осуществляет доступ к элементам в списке или кортеже по позиции. Однако такой подход часто делает программу нечитабельной. Также
вы можете захотеть уменьшить зависимость от позиции в структуре данных путем перехода к принципу доступа к элементам по имени.
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__().
На следующем шаге мы рассмотрим одновременное преобразование и сокращение (свертку) данных.