
Положительные и отрицательные индексы
Допустим у нас есть список или кортеж.
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] t = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Без потери общности будем работать только со списком х (с кортежем t – тоже самое).
Легко получить i-тый элемент этого списка по индексу.
Внимание! Индексы в Python считаются с нуля (0), как в С++ и Java.
>>> x[0] 0 >>> x[7] 7 >>> x[11] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range
В последней строке мы вылезли за пределы (у нас в списке последний индекс – 10) и получили исключение IndexError.
Но что будет, если мы обратимся к элементу с отрицательным индексом? В С++ такой операцией вы бы прострелили себе ногу. А в Python? IndexError? Нет!
>>> x[-1] 10 >>> x[-2] 9 >>> x[-10] 1 >>> x[-11] 0
Это совершенно легально. Мы просто получаем элементы не с начала списка, а с конца (-i-тый элемент).
x[-1] – последний элемент.
x[-2] – предпоследний элемент.
Это аналогично конструкции x[len(x)-i]:
>>> x[len(x)-1] 10
Обратите внимание, что начальный (слева) элемент в отрицательной нотации имеет индекс -11.
Срезы
Срезы, они же slices, позволяют вам получить какую-то часть списка или кортежа.
Форма x[start:end] даст элементы от индекса start (включительно) до end (не включая end). Если не указать start – мы начнем с 0-го элемента, если не указать end – то закончим последним элементом (включительно). Соотвественно, x[:] это тоже самое, что и просто x.
>>> x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> x[2:8] [2, 3, 4, 5, 6, 7] >>> x[:8] [0, 1, 2, 3, 4, 5, 6, 7] >>> x[2:] [2, 3, 4, 5, 6, 7, 8, 9, 10] >>> x[:] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Если end <= start, получим пустой список.
>>> x[5:3] []
Аналогично мы можем получать срезы с отчетом от конца списка с помощью отрицательных индексов.
>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a[-4:-2] [7, 8]
В этом случае также start < end, иначе будет пустой список.
Форма x[start:end:step] даст элементы от индекса start (включительно) до end (не включая end), в шагом step. Если step равен 1, то эта форма аналогична предыдущей рассмотренной x[start:end].
>>> x[::2] [0, 2, 4, 6, 8, 10] >>> x[::3] [0, 3, 6, 9] >>> x[2:8:2] [2, 4, 6]
x[::2] – каждый второй элемент, а x[::3] – каждый третий.
Отрицательный шаг вернет нам элементы в обратном порядке:
>>> x[::-1] [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] # как если бы: >>> list(reversed(x)) [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] # в обратном порядке с шагом 2 >>> x[::-2] [10, 8, 6, 4, 2, 0]
Запись в список по срезу
Можно присвоить части списка, отобранной срезом, некоторый другой список, причем размер среза не обязан равняться размеру присваемого списка.
Если размеры равны (в примере два элемента в срезе и два элемента во втором списке) – происходит замена элементов.
>>> a = [1,2,3,4,5] >>> a[1:3] = [22, 33] >>> a [1, 22, 33, 4, 5]
Если они не равны по размеру, то в результате список расширяется или сжимается.
>>> a = [1, 2, 3, 4, 5] # размер среза = 1 элемент, а вставляем два (массив расширился) >>> a[2:3] = [0, 0] >>> a [1, 2, 0, 0, 4, 5] # тут вообще пустой размер среза = вставка подсписка по индексу 1 >>> a[1:1] = [8, 9] >>> a [1, 8, 9, 2, 0, 0, 4, 5] # начиная с элемента 1 и кончая предпоследним элементом мы уберем (присвоив пустой список) >>> a[1:-1] = [] >>> a [1, 5]
Именованные срезы
Можно заранее создавать срезы с какими-то параметрами без привязки к списку или кортежу встроенной функцией slice. А потом применить этот срез к какому-то списку.
>>> a = [0, 1, 2, 3, 4, 5] >>> LASTTHREE = slice(-3, None) >>> LASTTHREE slice(-3, None, None) >>> a[LASTTHREE] [3, 4, 5]
Вместо пустых мест для start, end или step здесь мы пишем None.
В заключение к этому разделу хочу сказать, что срезы списков возвращают списки, срезы кортежей – кортежи.
Индексирование своих объектов
В конце концов, мы можете определить самостоятельно поведение оператор индексации [], определив для своего класса магические методы __getitem__, __setitem__ и __delitem__. Первый вызывается при получении значения по индекса (или индексам), второй – если мы попытаемся нашему объекту что-то присвоить по индексу. А третий – если мы будет пытаться делать del по индексу. Необязательно реализовывать их все. Можно только один, например:
# при чтении по индексу из этого класса, мы получим удвоенных индекс
class MyClass:
def __getitem__(self, key):
return key * 2
myobj = MyClass()
myobj[3] # вернет 6
myobj["privet!"] # приколись, будет: 'privet!privet!'
В качестве ключей можно использовать не только целые числа, но и строки или любые другие значения, в том числе slice и Ellipsis. Как вы будете обрабатывать их – решать вам. Естественно, логика, описанная в предыдущих разделах, здесь будет только в том случае, если вы ее сами так реализуете.
Пример. Экземпляр этого класса возвращаем нам список из целых чисел по индексу в виде срезу. Этакий бесконечный массив целых чисел, который почти не занимает памяти.
class IntegerNumbers: def __getitem__(self, key): if isinstance(key, int): return key elif isinstance(key, slice): return list(range(key.start, key.stop, key.step)) else: raise ValueError ints = IntegerNumbers() print(ints[10]) # 10 print(ints[1:10:2]) # [1, 3, 5, 7, 9] print(ints["wwdwd"]) # так нельзя
Можно иметь несколько индексов. Ниже мы суммируем все значения индексов.
class MultiIndex: def __getitem__(self, keys): # все индексы (если их 2 и больше попадут) в keys с типом tuple return sum(keys) # просуммируем их prod = MultiIndex() print(prod[10, 20]) # напечает 30
Удачи в программировании и жизни!
🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈