Метка: telegram

Множества в Python

Множество (англ. «set«) – неупорядоченная коллекция из уникальных (неповторяющихся) элементов. Элементы множества в Python должны быть немутабельны (неизменяемы), хотя само содержимое множества может меняться: можно добавлять и удалять элементы из множества.

О неизменяемых множествах написано в конце этой статьи.

CPython: внутри множества реализованы как хэш-таблицы, в которых есть только ключи без значений и добавлены некоторые оптимизации, которые используют отсутствие значений. Проверка членства выполняется за время O(1), так как поиск элементов в хэш-таблицы тоже выполняется за О(1). Если интересно, как это реализовано на С: вот ссылка.

Создание множества

Сформировать множество можно несколькими способами. Самый простой – перечислить элементы через запятую внутри фигурных скобок {}. Множество может содержать элементы разных типов, главное, чтобы они были неизменяемы. Поэтому кортеж можно поместить в множество, а список – нельзя.

>>> my_set = {1, 2, 3, 4}

>>> my_hetero_set = {"abc", 3.14, (10, 20)}  # можно с кортежем

>>> my_invalid_set = {"abc", 3.14, [10, 20]}  # нельзя со списком
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Можно также воспользоваться встроенной функцией set, чтобы создать множество из другой коллекции: списка, кортежа или словаря. Если это будет словарь – то новое множество будет составлено только из ключей этого словаря. Можно создать множество даже из строки: будет добавлена каждая буква (но только один раз):

>>> my_set2 = set([11, 22, 33])
>>> my_set2
{33, 11, 22}

>>> my_set3 = set((1, 2, 3))
>>> my_set3
{1, 2, 3}

>>> my_set4 = set({"a": 10, "b": 20})
>>> my_set4
{'b', 'a'}

>>> my_set5 = set("hello")
>>> my_set5
{'h', 'l', 'e', 'o'}

Как создать пустое множество? {} – вернет нам пустой словарик, а не множество. Поэтому, нужно использовать set() без аргументов.

>>> is_it_a_set = {}
>>> type(is_it_a_set)
<class 'dict'>

>>> this_is_a_set = set()
>>> type(this_is_a_set)
<class 'set'>

Изменение множеств

Множества можно менять, добавляя или удаляя элементы. Так как они не упорядочены, то индексирование не имеет смысла и не поддерживается: мы не может получать доступ к элементам множества по индексу, как мы это делаем для списков и кортежей.

Добавление одного элемента выполняется методом add(). Нескольких элементов из коллекции или нескольких коллекций – методом update():

>>> my_set = {44, 55}
>>> my_set.add(50)
>>> my_set
{50, 44, 55}

>>> my_set.update([1, 2, 3])
>>> my_set
{1, 2, 3, 44, 50, 55}

>>> my_set.update([2, 3, 6], {1, 50, 60}) 
>>> my_set
{1, 2, 3, 6, 44, 50, 55, 60}

>>> my_set.update("string")
>>> my_set
{1, 2, 3, 6, 'i', 44, 'r', 50, 's', 55, 'n', 'g', 60, 't'}

Естественно, что при добавлении элементов дубликаты игнорируются.

Удаление элементов из множества

Для удаления элемента существуют методы discard() и remove(). Делают они одно и тоже, но если удаляемого элемента нет во множестве, то discard() оставит множество неизменным молча, а remove() – бросит исключение:

>>> my_set = {1, 2, 3, 4, 5, 6}
>>> my_set.discard(2)
>>> my_set
{1, 3, 4, 5, 6}

>>> my_set.remove(4)
>>> my_set
{1, 3, 5, 6}

>>> my_set.discard(10)
>>> my_set
{1, 3, 5, 6}

>>> my_set.remove(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 10

Также есть метод pop(), который берет какой-нибудь (первый попавшийся) элемент множества, удаляет его и возвращает как результат:

>>> my_set = {3, 4, 5, 6, 1, 2}
>>> my_set
{1, 2, 3, 4, 5, 6}
>>> my_set.pop()
1
>>> my_set
{2, 3, 4, 5, 6}

Наконец, очистить множество (т.е. удалить все его элементы) можно методом clear():

>>> my_set = {1, 2, 3}
>>> my_set.clear()
>>> my_set
set()

Проверка членства

Узнать есть ли элемент в множестве очень легко оператором in (или not in, если хотим убедиться в отсутствии элемента):

>>> s = {"banana", "apple"}
>>> "banana" in s
True
>>> "tomato" not in s
True

Таким образом проверяется членства одного элемента, если нужно узнать является ли одно множество подмножеством другого, то оператор in тут не подойдет:

>>> {1, 2} in {1, 2, 3}
False

Тут подойдут операторы < и >. Чтобы получить True, с «широкой» стороны оператора должно стоять множество, полностью содержащее множество, стоящее по «узкую» сторону галочки:

>>> {1, 2} < {1, 2, 3, 4}
True
>>> {5, 6, 7, 8} > {5, 8}
True
>>> {1, 2, 3} < {1, 2, 4}
False

Итерация множеств

Пробежаться по элементам множества также легко, как и по элементам других коллекций оператором for-in (порядок обхода не определен точно):

my_set = {"Moscow", "Paris", "London"}
for elem in my_set:
    print(elem)
Moscow
London
Paris

Операции над множествами

Самое интересное – проводить математические операции над множествами.

Рассмотрим два множества A и B:

A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}

Объединение

Объединение множеств – множество, в котором есть все элементы одного и другого множеств. Это коммуникативная операция (от перемены мест ничего не меняется).

В Python используется либо метод union(), либо оператор вертикальная черта «|»:

>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}

>>> A | B
{1, 2, 3, 4, 5, 6, 7, 8}

>>> A.union(B)
{1, 2, 3, 4, 5, 6, 7, 8}

>>> B.union(A)
{1, 2, 3, 4, 5, 6, 7, 8}

Пересечение множеств

Пересечение множеств – множество, в которое входят только общие элементы, то есть которые есть и в первом, и во втором множестве. Также коммуникативная операция.

Пересечение вычисляют методом intersection() или оператором амперсандом «&»:

>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}

>>> A & B
{4, 5}

>>> B & A
{4, 5}

>>> A.intersection(B)
{4, 5}

Разность множеств

Разность множеств A и В – множество элементов из A, которых нет в B. Не коммуникативная операция!

Выполняется знаком минус «-» или оператором difference():

>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}

>>> A - B
{1, 2, 3}

>>> B - A
{8, 6, 7}

>>> A.difference(B)
{1, 2, 3}

>>> B.difference(A)
{8, 6, 7}

Как видно есть разница, в каком порядке идут операнды.

Симметричная разность

Симметричная разность – это объединение множеств за исключеним их пересечения. По другому, это сумма разностей. Это коммуникативный оператор.

Используется метод symmetric_difference() или оператор крышка «^»:

>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}

>>> A ^ B
{1, 2, 3, 6, 7, 8}

>>> B ^ A
{1, 2, 3, 6, 7, 8}

>>> A.symmetric_difference(B)
{1, 2, 3, 6, 7, 8}

Обратите внимание на эквивалентность операции определениям, которые я привел в начале этого раздела:

>>> A ^ B == (A - B) | (B - A)   # объединение простых разностей
True

>>> A ^ B == (A | B) - (A & B)   # разность объединения и пересечения
True

Прочее

Ко множествам можно применять стандартные функции all(), any(), enumerate(), len(), max(), min(), sorted(), sum(). Описания их ищите тут.

Прочие методы класса set:

copy() Возвращает копию множества
difference_update(other_set)Удаляет из этого множества все элементы, которые есть во множестве, переданным в аргументе
intersection_update(other_set)Обновляет это множество элементами из пересечения множеств
isdisjoint(other_set)Возвращает True, если множества не пересекаются
issubset(other_set)Возвращает True, если это множество является подмножеством другого
issuperset(other_set)Возвращает True, если это множество является надмножеством другого
symmetric_difference_update(other_set)Добавляет в это множество симметричную разность этого и другого множеств

Замороженное множество

Замороженное множество (frozen set) также является встроенной коллекцией в Python. Обладая характеристиками обычного множества, замороженное множество не может быть изменено после создания (подобно тому, как кортеж является неизменяемой версией списка).

Будучи изменяемыми, обычные множества являются нехешируемыми (unhashable type), а значит не могут применятся как ключи словаря или элементы других множеств.

Замороженные множества являются хэшируемыми, а значит могут быть ключами словаря и элементами других множеств.

Создаются замороженные множества функцией frozenset(), где аргументом будет другая коллекция. Примеры:

>>> A = frozenset({1, 2, 3})
>>> A
frozenset({1, 2, 3})

>>> B = frozenset(['a', 'b', 'cd'])
>>> B
frozenset({'cd', 'b', 'a'})

Над замороженными множествами можно производить все вышеописанные операции, кроме тех, что изменяют содержимое этого множества. Причем результатом логических операций будут тоже замороженные множества:

>>> A = frozenset('hello')
>>> B = frozenset('world')
>>> A | B
frozenset({'o', 'r', 'd', 'e', 'l', 'h', 'w'})
>>> A & B
frozenset({'o', 'l'})
>>> A ^ B
frozenset({'d', 'e', 'h', 'r', 'w'})

Теперь вы знаете много о множествах в Python.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Звезды в Python

Снимок далекой звезды в космосе

Звездочка (этот символ называется «астериск») – один из самых многоликих операторов в Python. Едва ли хватит пальцев руки, чтобы перечислить все его применения. Давайте по порядку.

Умножение и размножение

Самое простое применение одиночного астериска: умножение чисел. Двойного – возведение числа в степень.

>>> 3 * 4
12
>>> 3 ** 3
27
>>> 4 ** 0.5
2.0

Если мы умножим список (или кортеж) на целое число, то получим новый список (или кортеж), где элементы исходного повторены несколько раз. Подобное случится и со строкой. Если мы умножаем на ноль (0) или на число меньше ноля, то получим пустой список, кортеж или строку.

>>> [1, 2, 3] * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> ("abc", "foo", "bar") * 3
('abc', 'foo', 'bar', 'abc', 'foo', 'bar', 'abc', 'foo', 'bar')
>>> "hello world" * 4
'hello worldhello worldhello worldhello world'
>>> [1, 2, 3] * 0
[]
>>> "wat" * -10
''

Звезды в аргументах функций

Одна звездочка позволяет получить все или некоторые позиционные аргументы функции в виде кортежа. Позиционные – это те, которые просто подряд передаются без указания имени. Те, что с именем, это аргументы ключевого слова. Разберемся сначала с первыми. Ниже идут два примера. В первом мы получаем все позиционные аргументы в кортеж с названием args. Во втором случае мы обязуем пользователя наших функций передать как минимум 2 аргумента (они попадут в x и y) и дополнительно произвольное число (можно ни одного) аргументов, которые попадут в кортеж rest. Я специально развал их по-разному, не обязательно всегда называть их args. Обратите внимание, что это всегда будет кортеж, даже если мы передадим один аргумент, то получим кортеж из одного элемента.

def foo(*args):
    print('You passed {} args, they are {}'.format(len(args), args))

def foofoo(x, y, *rest):
    print('x = {}, y = {}, rest = {}'.format(x, y, rest))

>>> foo()
You passed 0 args, they are ()
>>> foo(10, 20, "str", {})
You passed 4 args, they are (10, 20, 'str', {})

>>> foofoo(11, 22)
x = 11, y = 22, rest = ()
>>> foofoo(12, 13, 15)
x = 12, y = 13, rest = (15,)
>>> foofoo(12, 13, 15, 20)
x = 12, y = 13, rest = (15, 20)

Пример такой функции мы недавно рассматривали – это хорошо знакомый нам print. Как известно, он принимает произвольное число аргументов, пользуясь выше описанным механизмом. Можете пройти по ссылке и увить его сигнатуру.

Важно, чтобы «звездная переменная» была после всех позиционных аргументов, иначе мы получим ошибку SyntaxError.

Две звездочки перед названием аргумента позволяют нам получить произвольное число произвольно названных именованных аргументов (еще их называют аргументами ключевых слов). Такую переменную часто называют **kwargs (от key-word arguments). В нее будет записан словарик (dict) из пар название ключевого слова (строка) и значение аргумента. Давайте обратимся к примерам:

def baz(**kwargs):
    print(kwargs)

>>> baz(x=1, y=2, z="hello")
{'y': 2, 'x': 1, 'z': 'hello'}
>>> baz()
{}

Как видно, без аргументов мы получили пустой словарь. А с именованными аргументами получили словарь, где их имена – ключи-строки, а их значения – собственно сами переданные значений. В функцию baz нельзя передать аргументы без имен, будет ошибка, потому что без имен – позиционные аргументы, а мы никак их не обозначили.

def foobaz(x, y, **kwargs):
    print('x = {} and y = {}'.format(x, y))
    print('also:', kwargs)

>>> foobaz(2, 3)
x = 2 and y = 3
('also:', {})

>>> foobaz(2, 3, other=77, z=88)
x = 2 and y = 3
('also:', {'other': 77, 'z': 88})

>>> foobaz(x=100, y=200, z=300)
x = 100 and y = 200
('also:', {'z': 300})

Тут мы требуем ровно два позиционных аргумента (x и – обязательные аргументы) и любое количество аргументов ключевых слов, которые попадут в kwargs. Нюанс: мы может передать x и y по именам (последний пример), но они не попадут в kwargs, а останутся только в своих соответствующих переменных x и y, и только z попал в kwargs, потому что мы заранее его не объявили.

Можно сочетать *args и **kwags в одной функции, причем именно в этом порядке.

def megafunc(x, y, *args, **kwargs):
    print('x = {} and y = {}'.format(x, y))
    print('also args: {}'.format(args))
    print('also kwargs {}'.format(kwargs))

>>> megafunc(10, 15)
x = 10 and y = 15
also args: ()
also kwargs {}

>>> megafunc(10, 15, 20)
x = 10 and y = 15
also args: (20,)
also kwargs {}

>>> megafunc(10, 15, 20, 22)
x = 10 and y = 15
also args: (20, 22)
also kwargs {}

>>> megafunc(10, 15, 20, 25, troll=30, dwarf=40)
x = 10 and y = 15
also args: (20, 25)
also kwargs {'troll': 30, 'dwarf': 40}

Еще одно применение звездочки – заставить вызывать функцию, указывая название аргументов в обязательном порядке.

def strict_foo(x, *, cat, dog):
    print(x, cat, dog)

>>> strict_foo(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: strict_foo() takes 1 positional argument but 3 were given

>>> strict_foo(1, cat=2, dog=3)
1 2 3

Имя x мы можем и не указывать, просто передадим значение, но cat и dog мы обязаны указать по имени. Зачем это надо? Если функция принимает много разных аргументов, то мы можем принудить указывать имена, чтобы пользователь не перепутал их порядок. Еще такой код просто выглядит более читаемым.

Раскрытие коллекций в аргументах функций при вызове

Также звездочка и двойная звездочка находят применение по другую сторону баррикад – при вызове функции. В предыдущем разделе мы обсуждали, как получать аргументы, а сейчас говорим о том, как их передавать.

Передавая кортеж или список со одной звездочкой – он раскроется в череду позиционных аргументов. Справа от звездочки может быть как имя переменной, так и литерал коллекции или даже вызов функции. Определим две функции: в одной foo – переменное число позиционных аргументов, в другой fixed_foo – фиксированное (три).

def foo(*args):
    print(args)

>>> foo(*[1, 2, 3])
(1, 2, 3)
>>> letters = ('a', 'b', 'c', 'd')
>>> foo(*letters)
('a', 'b', 'c', 'd')

def fixed_foo(a, b, c):
    print(a, b, c, sep=' and ')

>>> fixed_foo(*(1, 2, 3))
1 and 2 and 3
>>> fixed_foo(*(1, 2, 3, 4))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: fixed_foo() takes 3 positional arguments but 4 were given

В foo мы вольны передать список или кортеж любой длины, а в fixed_foo мы обязаны передать список или кортеж длины 3, не больше, не меньше.

Допускается использовать в одном вызове звездочку несколько раз:

>>> foo(*[1, 2, 3], *['a', 'b', 'c'])
(1, 2, 3, 'a', 'b', 'c')

Можно догадаться, что двойная звездочка несет схожую функциональность – раскрывает словарь в именованные аргументы функции.

def baz(a, b, c):
    print('a = ', a)
    print('b = ', b)
    print('c = ', c)

>>> baz(**{'a': 1, 'b': 2, 'c': 3})
a =  1
b =  2
c =  3

Если у нас нет **kwargs, то передаваемый словарь должен содержать ровно столько пар ключ-значение, сколько есть аргументов в функции (без значения по-умолчанию, естественно), причем ключи должен совпадать по именам с названиями аргументов (а вот порядок не важен). То есть при ‘a’: 1 в a попадет 1 и так далее.

>>> baz(10, **{'b': 20, 'c': 30})
a =  10
b =  20
c =  30

>>> baz(b=10, **{'a': 20, 'c': 30})
a =  20
b =  10
c =  30

В примерах выше мы передаем один аргумент явно и два аргумента словарем.

Возможны разнообразные варианты вызова функции. Даже такие:

def uber_func(a, b, c, d, x=10, y=20):
    print(f'a = {a} and b = {b} and c = {c} and d = {d}, x = {x}, y = {y}')

>>> uber_func(*[1, 2], 3, **{'d': 100, 'x': 200})
a = 1 and b = 2 and c = 3 and d = 100, x = 200, y = 20

Склеивание списков и словарей

Мы можем «встраивать» одни списки, кортежи и словари в другие с помощью астерисков. Это значит, что мы добавляем элементы одной коллекции в другую. Одинарная звездочка – для списков и кортежей:

>>> ( *(1, 2), *(3, 4) )
(1, 2, 3, 4)

>>> (1, 2) + (3, 4)  # тоже самое же?
(1, 2, 3, 4)

>>> [ *(1, 2), *[3, 4] ]
[1, 2, 3, 4]

>>> ( *[1, 2], *(3, 4) )
(1, 2, 3, 4)

Это похоже на то, как мы склеиваем коллекции оператором плюс (+), вот только плюсом нельзя соединить кортеж и список, будет ошибка. А через звездочку можно. Но согласитесь, сложение читается понятнее и легче.

Со словарями это немного полезнее. Применение двойной звезды (**) позволяет обновить один словарь элементами другого или нескольких других.

>>> d = { 'x': 10, 'y': 20 }
>>> d2 = { 'a': 100, 'b': 200 }

>>> { **d, **d2 }
{'x': 10, 'y': 20, 'a': 100, 'b': 200}

>>> { **d, **d2, 'other': 'foo' }
{'x': 10, 'y': 20, 'a': 100, 'b': 200, 'other': 'foo'}

Работает похоже, как метод update, но не меняет исходные словари, а создает новый. Если есть совпадающие ключи, то будет взято значение последнего из них:

>>> d_old = { 'president': 'Medvedev' }
>>> d_new = { 'president': 'Putin' }
>>> { **d_old, **d_new }
{'president': 'Putin'}

Распаковка

Позволяет раскидывать по переменным содержимое сложных структур из списков и кортежей. В переменную со звездочкой попадут в виде списка все остальные значения распакуемой структуры, кроме указанных явно. Лучше это понять на примерах:

>>> numbers = [1, 2, 3, 4, 5, 6]

>>> *a, = numbers   # да, там реально одинокая запятая после a
>>> a
[1, 2, 3, 4, 5, 6]

>>> *a, b, c = numbers
>>> a, b, c
([1, 2, 3, 4], 5, 6)

>>> a, b, *middle, c = numbers
>>> a, b, middle, c
(1, 2, [3, 4, 5], 6)

>>> [a, b, *middle, c] = numbers   # скобки можно любые
>>> a, b, middle, c
(1, 2, [3, 4, 5], 6)

Имя со звездочкой может быть как в начале, так и в конце кортежа и даже в середине. В последнем и в предпоследнем примере мы берем первый (a), второй (b) элементы; потом все, кроме последнего в middle пойдут как список, и последний в – c.

Если у нас один элемент со звездочкой и ни одного без звездочки – мы должны после него указать запятую (признак кортежа).

Не важно в какие скобки мы обернем верхний уровень: в круглые, квадратные или в никакие. Обычно скобки опускают.

На одном уровне может быть только 1 элемент со звездой.

>>> *n1, x, *n2 = numbers   # так нельзя!
  File "<stdin>", line 1
SyntaxError: two starred expressions in assignment

На нескольких уровней могут быть свои звездные выражения:

>>> a, [x, y, *rest], *others, last = 100, [20, 30, 40, 50, 60], 120, 140, 160
>>> print(a, x, y, rest, others, last)
100 20 30 [40, 50, 60] [120, 140] 160

При таком присваивании значения переменных копируются.

Справа от звездного присваиванию могут быть любые итераблы, например range:

>>> x1, *middle, x2 = range(10)
>>> x1, middle, x2
(0, [1, 2, 3, 4, 5, 6, 7, 8], 9)

Пока это все применения звездочки в Python, которые мне удалось вспомнить или найти.

Специально для канала PyWay.

Индексирование в Python

Бунгало на море

Положительные и отрицательные индексы

Допустим у нас есть список или кортеж.

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 👈 

Троеточие

Троеточие. Оно же многоточие, если по правилам, как утверждала наша учительница русского языка в школе и ставила нам двойки. Что тут сказать? Для некоторых все, что больше двух – уже много.

В питоне есть такая вещь:

...

Да это три простые точки подряд без пробелов.

Зачем она нужна и что это такое? Это Ellipsis, по-русски – оно самое «…точие». По-научному – это литерал встроенной константы (Ellipsis). Типа есть True, False, None, … и Ellipsis.

>>> ...
Ellipsis
>>> type(...)
<class 'ellipsis'>
>>> bool(...)
True

Т. е. троеточие это типа такое значение, которое вычисляется в константу Ellipsis, которая сама по себе равна себе же, имеет класс ellipsis и может быть приведена к bool=True, если надо.

Зачем она нужна? Да черт ее знает… Не припомню мест, где она используется в стандартной библиотеке.

Зато можно креативно использовать ее там, где у нас пустое тело класса или оператора (вместо pass).

class Test:
    pass

# красивее будет
# типа пока здесь пусто, но продолжение следует. загадочный и многозначительный код

class Test:
    ...

# или

if x == 5:
   ...  # я сюда еще чего-нить допишу потом
else: 
   print('not 5')

Можно троеточие передавать как значение аргумента ф-ции. Пример:

def foo(x):
    if x is Ellipsis:
        print('mda.....')
    else:
        print('x =', x)

foo(6)    # напечатает: x = 6
foo(...)  # напечатает: mda.....

Реальные применения троеточия встречаются в библиотеке для математических вычислений numpy.

Там он позволяет выбрать из многомерного массива подмассив, используя полную выборку по тем измерениям, которые не указаны конкретно. Сам не понял, что сказал, тут надо быть математиком!

В примере ниже у нас 4-х мерный массив. Мы фиксируем первый и последний индексы, а 2-й и 3-й будут выбраны полностью.

n[1,…,1] – эквивалент n[1,:,:,1]

>>> n = numpy.arange(16).reshape(2, 2, 2, 2)
>>> n
array([[[[ 0,  1],
         [ 2,  3]],

        [[ 4,  5],
         [ 6,  7]]],


       [[[ 8,  9],
         [10, 11]],

        [[12, 13],
         [14, 15]]]])
>>> n[1,...,1]            # equivalent to n[1,:,:,1]
array([[ 9, 11],
       [13, 15]])
>>> # also Ellipsis object can be used interchangeably
>>> n[1, Ellipsis, 1]
array([[ 9, 11],
       [13, 15]])

Не больше, чем синтаксический сахар, да и тот со привкусом стевии.

Еще одно редкое применение (с версии питона 3.5) – многоточие может пригодиться в подсказках для типов.

а) Для указание типа кортежа неопределенной длины с однородными типами элементов:

Tuple[int, ...]

б) Для указания типа вызываемого объекта, когда сигнатура параметров неизвестна. Пример:

def partial(func: Callable[..., str], *args) -> Callable[..., str]:
    # Body

Это, конечно, уже экзотика.

Может быть у тебя, мой милый читатель, есть идеи, как еще применить…