import collections collections.UserList([list])
list
- список list
используемый как источник данных для пользовательского класса. Класс UserList()
модуля collections
действует как обертка для объектов списка list
. Это полезный базовый класс для собственных классов, подобных спискам, которые могут наследоваться от них и переопределять существующие методы или добавлять новые. Таким образом, в списки можно добавлять новые модели поведения.
Содержимое экземпляра хранится в обычном списке, который доступен через атрибут данных экземпляров UserList.data
. Содержимое экземпляра изначально установлено как копия списка, по умолчанию пустой список []
. Список может быть любым итерируемым объектом, например, реальным списком list
или объектом UserList
.
Требования к подклассам
collections.UserList
:Ожидается, что подклассы
UserList
будут предлагать конструктор, который можно вызывать либо без аргументов, либо с одним аргументом. Операции списка, которые возвращают новую последовательность, пытаются создать экземпляр фактического класса реализации. Для этого предполагается, что конструктор может быть вызван с одним аргументомlist
, который является объектом последовательности, используемым в качестве источника данных.Если производный класс не будет соответствовать этому требованию, все специальные методы, поддерживаемые этим классом, должны быть переопределены.
Потребность в этом классе была частично вытеснена возможностью создавать подклассы непосредственно из типа list
, однако с этим классом может быть проще работать, поскольку базовый список доступен как атрибут UserList.data
.
UserList
.Предположим, что нужен список, который автоматически сохраняет все свои элементы в виде строк. Предполагая, что пользовательский класс списка будет хранить числа только в виде строк.
Так как пользовательский класс будет хранить элементы в виде строк, то необходимо переопределить все методы, которые добавляют или изменяют элементы в базовом списке list
. Эти методы включают следующее:
__init__
- инициализирует все новые экземпляры класса.__setitem__()
- позволяет присвоить новое значение существующему элементу, используя индекс элемента, например, list[index] = item
.insert()
- позволяет вам вставить новый элемент в заданную позицию в базовом списке, используя индекс элемента.append()
- добавляет один новый элемент в конец базового списка.extend()
- добавляет ряд элементов в конец списка.from collections import UserList class StringList(UserList): def __init__(self, iterable): super().__init__(str(item) for item in iterable) def __setitem__(self, index, item): self.data[index] = str(item) def insert(self, index, item): self.data.insert(index, str(item)) def append(self, item): self.data.append(str(item)) def extend(self, other): if isinstance(other, type(self)): self.data.extend(other) else: self.data.extend(str(item) for item in other)
В этом примере доступ к атрибуту UserList.data
позволяет создать класс более простым способом, используя делегирование, т.е. список в UserList.data
позаботится об обработке всех запросов.
Здесь почти не используется расширенные инструменты, такие как super()
. Функция super()
используется один раз в инициализаторе класса __init__
, для предотвращения проблемы в дальнейших сценариях наследования. В остальных методах используется атрибут UserList.data
, который содержит обычный список Python.
>>> lst = StringList([1, 2, 2, 4, 5]) >>> lst # ['1', '2', '2', '4', '5'] >>> lst.append(6) >>> lst # ['1', '2', '2', '4', '5', '6'] >>> lst.insert(0, 0) >>> lst # ['0', '1', '2', '2', '4', '5', '6'] >>> lst.extend([7, 8, 9]) >>> lst # ['0', '1', '2', '2', '4', '5', '6', '7', '8', '9'] >>> lst[3] = 3 >>> lst # ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
Когда происходит добавление lst.append()
, вставка lst.insert()
, присваивание lst[index] = item
или расширение lst.extend()
экземпляра класса новыми значениями, то методы, поддерживающие каждую операцию, позаботятся о процессе преобразования передаваемых элементов в строку.
Другие методы, которые пользовательский класс StringList
унаследовал от типа list
, будут прекрасно работать, т.к. они не добавляют и не обновляют элементы.
Примечание. Если есть необходимость, чтобы класс
StringList
поддерживал конкатенацию с помощью оператора плюс+
, то потребуется реализовать другие специальные методы, такие как__add__()
,__radd__()
и__iadd__()
.
list
.При наследовании от встроенного типа list
, необходимо менять внутреннюю реализацию всех методов родительского класса, которые добавляют или изменяют элементы, при помощи встроенной функции super()
.
class StringList(list): def __init__(self, iterable): super().__init__(str(item) for item in iterable) def __setitem__(self, index, item): super().__setitem__(index, str(item)) def insert(self, index, item): super().insert(index, str(item)) def append(self, item): super().append(str(item)) def extend(self, other): if isinstance(other, type(self)): super().extend(other) else: super().extend(str(item) for item in other)
На практике часто требуется список, который принимает только числовые данные. То есть, в списке должны храниться только целые числа, вещественные числа и комплексные числа. Если попытаться сохранить значение любого другого типа данных, например строку, то пользовательский класс должен вызвать ошибку TypeError
.
Реализация класса NumberList
с желаемой функциональностью:
class NumberList(list): def __init__(self, iterable): super().__init__(self._validate_number(item) for item in iterable) def __setitem__(self, index, item): super().__setitem__(index, self._validate_number(item)) def insert(self, index, item): super().insert(index, self._validate_number(item)) def append(self, item): super().append(self._validate_number(item)) def extend(self, other): if isinstance(other, type(self)): super().extend(other) else: super().extend(self._validate_number(item) for item in other) def _validate_number(self, value): if isinstance(value, (int, float, complex)): return value raise TypeError( f"numeric value expected, got {type(value).__name__}" )
В этом примере класс NumberList
наследуется непосредственно от встроенного типа list
. Это означает, что класс разделяет все основные функции с типом list
. Может перебирать экземпляры NumberList
, получать доступ и обновлять его элементы, используя их индексы, вызывать общие методы списка и многое другое.
Теперь, чтобы гарантировать, что каждый входной элемент является числом, необходимо его проверить во всех методах, поддерживающих операции добавления новых элементов или обновления существующих элементов.
Для проверки входных данных используется вспомогательный метод NumberList._validate_number()
. Этот метод использует встроенную функцию isinstance()
, для проверки принадлежности текущего входного значения к экземплярам int
, float
или complex
.
Альтернативная реализация NumberList
с использованием collections.UserList
может выглядеть примерно так:
from collections import UserList class NumberList(UserList): def __init__(self, iterable): super().__init__(self._validate_number(item) for item in iterable) def __setitem__(self, index, item): self.data[index] = self._validate_number(item) def insert(self, index, item): self.data.insert(index, self._validate_number(item)) def append(self, item): self.data.append(self._validate_number(item)) def extend(self, other): if isinstance(other, type(self)): self.data.extend(other) else: self.data.extend(self._validate_number(item) for item in other) def _validate_number(self, value): if isinstance(value, (int, float, complex)): return value raise TypeError( f"numeric value expected, got {type(value).__name__}" )
В этом примере вместо постоянного использования super()
для доступа к методам и атрибутам родительского класса, напрямую используется атрибут UserList.data
. Возможно, в некоторой степени использование UserList.data
упрощает код по сравнению с использованием super()
и других продвинутых инструментов, таких как специальные методы.
Обратите внимание, что функция super()
используется только в методе __init__()
. Это лучшая практика при работе с наследованием в Python. Это позволяет правильно инициализировать атрибуты в родительском классе, не нарушая работу.
Допустим, что нужен список со всеми стандартными функциями, который также должен предоставлять некоторые дополнительные функции:
join()
: объединяет все элементы списка в одну строку.map(action)
: дает новые элементы, которые являются результатом применения внешней функции action()
к каждому элементу в базовом списке.filter(predicate)
: возвращает все элементы, которые возвращают True
при вызове для них внешней функции predicate()
.for_each(func)
: вызывает внешнюю функцию func()
для каждого элемента в базовом списке, чтобы создать побочный эффект.Реализация:
class CustomList(list): def join(self, separator=" "): return separator.join(str(item) for item in self) def map(self, action): return type(self)(action(item) for item in self) def filter(self, predicate): return type(self)(item for item in self if predicate(item)) def for_each(self, func): for item in self: func(item) >>> words = CustomList( ... [ ... "Hello,", ... "Pythonista!", ... "Welcome", ... "to", ... "Python!" ... ] ... ) >>> words.join() # 'Hello, Pythonista! Welcome to Python!' >>> words.map(str.upper) # ['HELLO,', 'PYTHONISTA!', 'WELCOME', 'TO', 'PYTHON!'] >>> words.filter(lambda word: word.startswith("Py")) # ['Pythonista!', 'Python!'] >>> words.for_each(print) # Hello, # Pythonista! # Welcome # to # Python!
Можно реализовать CustomList
, наследуя от UserList
, а не от встроенного типа list
. В этом случае не нужно менять внутреннюю реализацию, только базовый класс:
from collections import UserList class CustomList(UserList): def join(self, separator=" "): return separator.join(str(item) for item in self.data) def map(self, action): return type(self)(action(item) for item in self.data) def filter(self, predicate): return type(self)(item for item in self.data if predicate(item)) def for_each(self, func): for item in self.data: func(item)
Обратите внимание, что в этом примере нет необходимости использовать
self.data
напрямую (можно использовать простоself
). Преимущество использованияself.data
в том, что становиться ясно, что работа ведется с подклассомUserList
- это делает код более явным, а программисты читающие его видят больше контекста.