Как работает обращение к элементу списка по индексу?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает обращение к элементу списка по индексу
Обращение к элементу списка по индексу — это фундаментальная операция в Python. Под капотом происходит вызов специального метода __getitem__, который реализует логику индексирования.
1. Базовое индексирование
my_list = ['a', 'b', 'c', 'd', 'e']
# Положительные индексы (слева направо)
print(my_list[0]) # 'a'
print(my_list[2]) # 'c'
print(my_list[4]) # 'e'
# Отрицательные индексы (справа налево)
print(my_list[-1]) # 'e' (последний элемент)
print(my_list[-2]) # 'd' (предпоследний)
print(my_list[-5]) # 'a' (первый элемент)
Внутренняя работа:
Индекс: 0 1 2 3 4
+----+----+----+----+----+
Элемент | a | b | c | d | e |
+----+----+----+----+----+
Отрицательные: -5 -4 -3 -2 -1
2. Волшебный метод __getitem__
Когда ты пишешь list[index], Python вызывает list.__getitem__(index):
# Эти два выражения равны
print(my_list[2])
print(my_list.__getitem__(2))
# Создаём свой класс с индексированием
class CustomList:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
# Обработка положительных и отрицательных индексов
if isinstance(index, int):
# Преобразуем отрицательные индексы
if index < 0:
index = len(self.items) + index
# Проверяем границы
if index < 0 or index >= len(self.items):
raise IndexError("list index out of range")
return self.items[index]
else:
raise TypeError(f"indices must be integers, not {type(index).__name__}")
custom = CustomList(['x', 'y', 'z'])
print(custom[0]) # 'x'
print(custom[-1]) # 'z'
print(custom[1]) # 'y'
3. Срезы (Slicing)
Срезы также используют __getitem__, но с объектом slice:
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Базовый срез
print(my_list[1:4]) # [1, 2, 3] (от индекса 1 до 3 включительно)
print(my_list[2:7:2]) # [2, 4, 6] (с шагом 2)
print(my_list[:5]) # [0, 1, 2, 3, 4] (от начала до 4)
print(my_list[5:]) # [5, 6, 7, 8, 9] (от 5 до конца)
print(my_list[::2]) # [0, 2, 4, 6, 8] (каждый 2-й элемент)
print(my_list[::-1]) # [9, 8, 7, ..., 0] (в обратном порядке)
# Под капотом это вызывает
print(my_list.__getitem__(slice(1, 4, None)))
# Реализация поддержки срезов в custom классе
class CustomList:
def __init__(self, items):
self.items = 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) + index
if index < 0 or index >= len(self.items):
raise IndexError("list index out of range")
return self.items[index]
4. Сложные индексы
Многомерные списки:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# Доступ к элементу (строка 1, столбец 2)
print(matrix[1][2]) # 6
# Внутренно: matrix[1] возвращает [4, 5, 6], затем [2] возвращает 6
NumPy массивы:
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
# Можно использовать кортеж индексов
print(arr[0, 2]) # 3 (первая строка, третий столбец)
print(arr[1, :]) # [4, 5, 6] (вторая строка, все столбцы)
print(arr[:, 1]) # [2, 5] (все строки, второй столбец)
5. Проверка границ и обработка ошибок
my_list = [10, 20, 30]
# IndexError
try:
print(my_list[10]) # Индекс вне диапазона
except IndexError as e:
print(f"Error: {e}") # "list index out of range"
# TypeError
try:
print(my_list["invalid"]) # Неверный тип индекса
except TypeError as e:
print(f"Error: {e}") # "list indices must be integers or slices, not str"
# Безопасный доступ
index = 100
if 0 <= index < len(my_list):
print(my_list[index])
else:
print("Index out of range")
6. Производительность индексирования
# Список (list) — O(1) операция
my_list = list(range(1000000))
start = time.time()
for i in range(1000):
_ = my_list[999999] # Постоянное время
print(f"List access: {time.time() - start}")
# Связный список — O(n) операция
from collections import deque
my_deque = deque(range(1000000))
start = time.time()
for i in range(1000):
_ = my_deque[-1] # Линейное время для доступа по индексу
print(f"Deque access: {time.time() - start}")
# Словарь (dict) — в среднем O(1)
my_dict = {i: i*2 for i in range(1000000)}
start = time.time()
for i in range(1000):
_ = my_dict[999999]
print(f"Dict access: {time.time() - start}")
7. Отрицательные индексы внутри
def safe_get(lst, index):
"""Безопасное получение элемента со всеми проверками"""
n = len(lst)
# Обработка отрицательных индексов
if isinstance(index, int):
if index < 0:
actual_index = n + index
else:
actual_index = index
if 0 <= actual_index < n:
return lst[actual_index]
else:
raise IndexError("Index out of range")
else:
raise TypeError(f"Expected int, got {type(index).__name__}")
my_list = ['a', 'b', 'c']
print(safe_get(my_list, -1)) # 'c'
print(safe_get(my_list, 0)) # 'a'
8. Итераторы и индексирование
# Не все объекты поддерживают индексирование
my_tuple = (1, 2, 3) # Поддерживает
my_set = {1, 2, 3} # Не поддерживает
my_dict = {1: 'a'} # Поддерживает (по ключам)
print(my_tuple[0]) # OK: 1
print(my_dict[1]) # OK: 'a'
print(my_set[0]) # Error: 'set' object is not subscriptable
# Проверка поддержки
from collections.abc import Sequence
print(isinstance(my_list, Sequence)) # True
print(isinstance(my_set, Sequence)) # False
Заключение
Обращение к элементу списка по индексу — это O(1) операция, которая напрямую вычисляет адрес в памяти. Python обрабатывает как положительные, так и отрицательные индексы, проверяет границы и вызывает метод __getitem__ под капотом. Эта простота скрывает мощный механизм, позволяющий реализовать кастомные структуры данных с собственной логикой индексирования.