Собственные последовательности в Python: от теории к практике
Последовательности в Python и способы их реализации
Создание неизменяемой последовательности с помощью __getitem__ и __len__
Как реализовать собственную неизменяемую последовательность в Python?
Для создания класса, который ведет себя как последовательность, достаточно определить два метода: __len__ и __getitem__. Python автоматически получит поддержку итерации, оператора in, а также функций len() и индексации. Пример простой последовательности, хранящей данные во внутреннем списке:
class MySeq:
def __init__(self, items):
self._items = list(items)
def __len__(self):
return len(self._items)
def __getitem__(self, index):
return self._items[index]
значения списка числа python (итерация по значениям списка чисел в python)
Теперь объект MySeq можно использовать как обычную последовательность:
seq = MySeq([10, 20, 30])
print(len(seq)) # 3
print(seq[1]) # 20
for item in seq:
print(item)
словарь set python (словарь и set в python)
3 20 10 20 30
Python dict set (словарь и множество в python)
Типичные проблемы и ошибки:
- Если не обрабатывать отрицательные индексы в __getitem__, то seq[-1] вызовет IndexError. Решение: проверять отрицательные индексы и преобразовывать их в положительные.
- Метод __getitem__ должен вызывать IndexError при недопустимом индексе, иначе поведение цикла for станет некорректным.
- По умолчанию не будет работать срез (seq[1:3]) – нужно реализовать обработку объекта типа slice.
Как гарантировать полную реализацию методов последовательности?
Абстрактный базовый класс Sequence из модуля collections.abc предоставляет стандартную реализацию методов __contains__, __iter__, __reversed__, index и count на основе ваших __getitem__ и __len__. Достаточно унаследоваться от Sequence и реализовать эти два метода.
from collections.abc import Sequence
class MySeq(Sequence):
def __init__(self, items):
self._items = list(items)
def __getitem__(self, index):
return self._items[index]
def __len__(self):
return len(self._items)
типы структур python (типы структур данных в python)
Теперь доступны все методы последовательности:
seq = MySeq([1,2,3,2])
print(seq.count(2)) # 2
print(seq.index(3)) # 2
print(3 in seq) # True
кортеж чисел python (кортеж чисел в python)
2 2 True
язык программирования python массивы (массивы (списки) в python)
Проблемы: Sequence является ABC, поэтому при наследовании необходимо правильно указать метакласс (обычно это автоматически). Если забыть реализовать __getitem__ или __len__, Python выдаст ошибку при создании экземпляра. Также Sequence не предоставляет реализацию среза – по-прежнему нужно обрабатывать slice в __getitem__.
Как создать изменяемую последовательность?
Для изменяемой последовательности используется MutableSequence. Помимо __getitem__ и __len__ нужно реализовать __setitem__, __delitem__ и метод insert. Пример простой изменяемой последовательности на основе списка:
from collections.abc import MutableSequence
class MyMutableSeq(MutableSequence):
def __init__(self, items=None):
self._items = list(items) if items else []
def __getitem__(self, index):
return self._items[index]
def __setitem__(self, index, value):
self._items[index] = value
def __delitem__(self, index):
del self._items[index]
def __len__(self):
return len(self._items)
def insert(self, index, value):
self._items.insert(index, value)
массивы данных python 3 (массивы данных в python)
Пример использования:
mseq = MyMutableSeq([1,2,3])
mseq.append(4) # наследуется от MutableSequence
mseq[1] = 99
del mseq[0]
print(list(mseq))
одномерные массивы на языке программирования python (одномерные массивы в python)
[99, 3, 4]
последовательности в python и способы их реализации (последовательности в python и способы их реализации)
Типичные ошибки: Неправильная реализация insert – метод insert в MutableSequence должен вставлять элемент перед указанным индексом, сдвигая остальные. Если метод insert реализован неверно, то такие методы, как append, remove, pop, могут работать некорректно. Также необходимо следить за обработкой отрицательных индексов.
Как расширить функциональность существующей последовательности?
Простой способ – унаследоваться от list или tuple и добавить новые методы или переопределить существующие. Это сохраняет всю производительность встроенного типа. Пример подкласса списка с дополнительным методом среднего арифметического:
class AverageList(list):
def average(self):
if not self:
return 0.0
return sum(self) / len(self)
программы с массивами на python (программы с массивами на python)
Использование:
al = AverageList([10, 20, 30])
print(al.average()) # 20.0
al.append(40)
print(al.average()) # 25.0
Python пар (пары (ключ-значение) в python)
20.0 25.0
Python разница списков (разница между списками и кортежами в python)
Проблемы: При наследовании от list стоит помнить, что некоторые методы возвращают новый список (например, copy, slice). Эти методы вернут обычный list, а не подкласс, если не переопределить их. Можно переопределить __class__ или использовать декораторы. Также наследование от tuple ограничивает изменяемость.
Как создать последовательность для хранения однотипных данных с минимальным расходом памяти?
Модуль array предоставляет тип array.array, который является изменяемой последовательностью, хранящей элементы одного типа (числа, символы). Это эффективнее списка по памяти и скорости для числовых данных.
from array import array
arr = array('i', [1, 2, 3]) # 'i' – знаковое целое
arr.append(4)
print(arr[2]) # 3
for val in arr:
print(val, end=' ')
как сделать массив python (создание массива (списка) в python)
3 1 2 3 4
Проблемы: array поддерживает только ограниченный набор типов (строковые коды). Нельзя хранить разнородные данные. При попытке вставить значение другого типа возникает TypeError. Также array не поддерживает изменение размера через срез (но можно использовать extend и pop).
Расширенные примеры реализации последовательностей
Последовательность с поддержкой срезов и отрицательных индексов
В следующем примере класс SequenceWithSlice обрабатывает объект slice в методе __getitem__ и корректно работает с отрицательными индексами.
class SequenceWithSlice:
def __init__(self, items):
self._items = list(items)
def __len__(self):
return len(self._items)
def __getitem__(self, index):
if isinstance(index, slice):
# обрабатываем срез
start, stop, step = index.indices(len(self._items))
return [self._items[i] for i in range(start, stop, step)]
elif isinstance(index, int):
# поддерживаем отрицательные индексы
if index < 0:
index += len(self._items)
if index < 0 or index >= len(self._items):
raise IndexError('list index out of range')
return self._items[index]
else:
raise TypeError('indices must be integers or slices')
seq = SequenceWithSlice([10, 20, 30, 40, 50])
print(seq[1:4]) # срез
print(seq[-1]) # последний элемент
print(seq[::2]) # каждый второй
[20, 30, 40] 50 [10, 30, 50]
Ленивая последовательность с кэшированием
Реализация последовательности, которая генерирует элементы по требованию (например, из итератора) и кэширует уже полученные значения. Это полезно для дорогостоящих вычислений или бесконечных последовательностей.
class LazySequence:
def __init__(self, iterable):
self._iter = iter(iterable)
self._cache = []
def __len__(self):
# подгружаем все оставшиеся элементы для корректной длины
try:
while True:
self._cache.append(next(self._iter))
except StopIteration:
pass
return len(self._cache)
def __getitem__(self, index):
if isinstance(index, slice):
# для среза сначала подгружаем нужные элементы
start, stop, step = index.indices(10**9) # условно большой лимит
while len(self._cache) <= max(start, stop):
try:
self._cache.append(next(self._iter))
except StopIteration:
break
return [self._cache[i] for i in range(start, stop, step) if i < len(self._cache)]
if isinstance(index, int):
if index < 0:
# для отрицательных индексов загружаем все
self.__len__()
return self._cache[index]
while len(self._cache) <= index:
try:
self._cache.append(next(self._iter))
except StopIteration:
raise IndexError('list index out of range')
return self._cache[index]
raise TypeError
def generate():
for i in range(10):
yield i
lazy = LazySequence(generate())
print(lazy[2]) # 2, загрузились элементы до индекса 2
print(lazy[5]) # 5, загрузились ещё
print(lazy[0]) # 0, уже в кэше
print(lazy[1:4]) # [1, 2, 3]
2 5 0 [1, 2, 3]
Последовательность на основе односвязного списка
Реализация неизменяемой последовательности, где данные хранятся в узлах односвязного списка. Метод __getitem__ проходит по цепочке до нужного индекса.
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
class LinkedListSeq:
def __init__(self, head=None):
self._head = head
self._length = 0
# подсчет длины при инициализации
cur = head
while cur:
self._length += 1
cur = cur.next
def __len__(self):
return self._length
def __getitem__(self, index):
if index < 0:
index += self._length
if index < 0 or index >= self._length:
raise IndexError('list index out of range')
cur = self._head
for _ in range(index):
cur = cur.next
return cur.value
# создание списка: 10 -> 20 -> 30
tail = Node(30)
middle = Node(20, tail)
head = Node(10, middle)
lst = LinkedListSeq(head)
print(len(lst)) # 3
print(lst[1]) # 20
for val in lst:
print(val, end=' ')
3 20 10 20 30