Функция collections.namedtuple позволяет построить класс, который содержит только поля и никаких методов. Экземпляр класса будет работать так же, как и обычный кортеж (tuple), только к элементам экземпляра класса можно будет обратиться через соответсвутющие имена, в отличие от обычного кортежа, где к элементам можно обратиться только через их индексы.
В примере ниже определим кортеж для хранения информации о городе с помощью collections.namedtuple:
>>> from collections import namedtuple
>>> City = namedtuple('City', ['name', 'country', 'population'])
>>> minsk = City('Minsk', 'Belarus', 2648500)
>>> minsk
City(name='Minsk', country='Belarus', population=2648500)
>>> minsk.name
'Minsk'
>>> minsk.country
'Belarus'
>>> minsk[2]
2648500
Для создания именованного кортежа, нужно задать имя класса и список имен полей. Мы задали имена полей с помощью списка ([‘name’, ‘country’, ‘population’]), однако их можно задать и с помощью строки, разделяя поля пробелом или запятой:
>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population')
>>> City = namedtuple('City', 'name, country, population')
>>> minsk = City(name='Minsk', country='Belarus', population=2648500)
>>> minsk
City(name='Minsk', country='Belarus', population=2648500)
Для сравнения, в аналогичной ситуации можно было бы использовать словарь (dict), либо обычный кортеж (tuple):
>>> minsk = {'name': 'Minsk', 'country': 'Belarus', 'population': 2648500}
>>> minsk
{'name': 'Minsk', 'country': 'Belarus', 'population': 2648500}
>>> minsk = ('Minsk', 'Belarus', 2648500)
>>> minsk
('Minsk', 'Belarus', 2648500)
Но проблема словаря в том, что это изменяемый объект и он медленнее, чем кортеж. А проблема кортежа в том, что к его элементам нужно обращаться по индексам, и из-за этого код становится не читабельным.
Namedtuple to the rescue! Именованный кортеж решает эти проблемы. Он неизменяем и работает быстрее, чем словарь, а также, благодаря ему, код становиться читабельным и всем сразу понятно, что в этом кортеже храниться.
Скрытие структуры данных
Вот еще пример, где можно применить именованный кортеж. Представьте, что у вас есть класс Inventory, в который передается список с ценой и количеством каждого продукта. Нужно узнать итоговую стоимость всех продуктов:
class Inventory:
def __init__(self, data):
self.data = data
def total_price(self):
# x[0] - цена; x[1] - количество
return sum(x[0] * x[1] for x in self.data)
Предпологается, что в класс передается такой список кортежей:
data = [(1.45, 2), (1.25, 5), (0.55, 3)]
Работа класса зависит от структуры данных. Если класс обращается в нескольких местах к элементам кортежа по индексу, то возникнут проблемы, когда структура данных изменится. Придется в каждом месте делать изменение, а это не соответсвует принципу DRY (Don’t Repeat Yourself).
Отрефакторим класс выше. Заменим кортеж на именованный кортеж, и сделаем так, чтобы при изменении структуры данных, код класса изменялся только в одном месте:
from collections import namedtuple
Product = namedtuple('Product', 'price quantity')
class Inventory:
def __init__(self, data):
self.products = self.to_products(data)
def to_products(self, data):
return [Product(x[0], x[1]) for x in data]
def total_price(self):
return sum(product.price * product.quantity
for product in self.products)
Теперь, при изменении структуры данных, код в классе изменяется только в методе to_products. Кроме того, метод to_products изолирует сложную структуру списка с помощью namedtuple.
Заключение
Вам решать, когда использовать именованный кортеж, а когда словарь или обычный кортеж. Но если вы уверены, что именованный кортеж сделает ваш код читабельнее и проще к пониманию, то используйте его. Всегда помните:
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
Jason Statham