Кастомные коллекции
Опубликовано 24 August 2015 в Python
Хорошо написанный код на Python должен работать с последовательностями однообразно. При этом не важно встроенные это последовательности или нет. Тем более, что свою последовательность написать в питоне очень просто. Нужно всего лишь переопределить __len__, __getitem__ что бы работа с вашей последовательностью в большинстве случаев не отличалась от работы со встроенными последовательностями.
В качестве иллюстрации возьмем такой пример. У нас есть класс ShoppingBasket. В нем лежат какие-то товары. Пусть нам нужно пройтись по всем товарам, научиться получать товары по ключу и добавлять товары.
Первый вариант
Положимся на встроенные коллекции. Для хранения элементов используем список.
# -*- coding: utf-8 -*-
from collections import namedtuple
BasketItem = namedtuple('BasketItem', 'name, price, quantity')
class ShoppingBasket(object):
def __init__(self):
self.items = []
if __name__ == '__main__':
basket = ShoppingBasket()
basket.items = [
BasketItem("Apple", 1.12, 2),
BasketItem("Orange", 1.3, 4),
]
# печатаем содержимое
for item in basket.items:
print(item)
# только яблоки
print("Apples:", basket.items[0])
# всего элементов в корзине
print("Items:", len(basket.items))
Код работает. Но у него большая проблема: класс ShoppingBasket открывает наружу то как он хранит товары в корзине. Этот код нас жестко привязывает к списку. Если по каким-то причинам мы захотим поменять модель хранения товаров в корзине (привязать к БД, на пример), то придется править код во множестве мест, где имеется работа с корзиной. Нам повезет, если код не является частью публичного API.
Второй вариант
Попробуем спрятать зависимость от дикта внутри корзины.
# -*- coding: utf-8 -*-
from collections import namedtuple
BasketItem = namedtuple('BasketItem', 'name, price, quantity')
class ShoppingBasket(object):
def __init__(self):
self.items = []
def append(self, value):
self.items.append(value)
def __getitem__(self, key):
return self.items[key]
def __len__(self):
return len(self.items)
if __name__ == '__main__':
basket = ShoppingBasket()
basket.append(BasketItem("Apple", 1.12, 2))
basket.append(BasketItem("Orange", 1.3, 4))
basket.append(BasketItem("Bananas", 0.8, 1))
# печатаем содержимое
for item in basket:
print(item)
# только яблоки
print("Apples:", basket[0])
# слайсы так же работают
print(basket[:2])
# всего элементов в корзине
print("Items:", len(basket))
Код внутри __main__ стал более опрятным. Зависимость от способа хранения элементов убрана в класс корзины. Переделать этот вариант корзины на хранение элементов в БД с сохранением интерфейса не составит проблем.
Не ленитесь. Если ваш класс по сути коллекция, прячьте то как он хранит свои элементы, но делайте привычные интерфейсы. Так вы облегчите жизнь себе и пользователям вашего кода.
Возник вопрос? Мне всегда можно написать в Twitter: avkorablev