/привет/мир/etc
Ниже обзор возможностей и приемов работы с последовательностями в Python 3, включая следующие темы (но не ограничиваясь ими):
- задание последовательностей,
- доступ к элементам последовательности для чтения и изменения,
- методы, общие для последовательностей,
- преобразования последовательностей,
- агрегирование элементов последовательности.
Последовательности конструируются явно, с помощью литерала или другой последовательности, или аналитически, с помощью итератора или генератора. Примеры явно заданных последовательностей:
[1, 13, 42, -7, 5] # список (1, 2, 3, 5) # кортеж 'привет' # строка b'hello' # строка байтов bytearray([100, 101, 102]) # массив байтов
Список ( list ) и массив байтов ( bytearray ) — изменяемые последовательности, кортеж ( tuple ), строка ( str ) и строка байтов ( bytes ) — неизменяемые.
Примеры последовательностей, построенных с помощью итератора, предоставленного функцией range() :
>>> list(range(10)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> tuple(range(10)) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) >>> bytes(range(10)) b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t' >>> str(bytes(range(65, 91)), encoding='ascii') 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
Функция range() также позволяет задать шаг, в том числе отрицательный:
>>> list(range(1, 10, 2)) [1, 3, 5, 7, 9] >>> list(range(10, 1, -2)) [10, 8, 6, 4, 2]
Генератор — это объект класса generator , который Python автоматически создает при вызове функции c предложением yield внутри:
>>> def down(n): . while n > 0: . yield n . n -= 1 . >>> type(down) >>> down10 = down(10) >>> type(down10)
Каждый генератор — это итератор, так что можно инициализировать последовательность с его помощью:
>>> list(down10) [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] >>> bytes(down(127)) b'\x7f~>|=
Элементы всякой последовательности доступны по индексу, причем
- индексирование от начала последовательности начинается с 0 и заканчивается числом, равным длине последовательности минус один, а
- индексирование с конца начинается с -1 и заканчивается отрицательным числом, равным длине последовательности.
>>> a = [1, 5, 13] >>> a[0] 1 >>> a[1] = 3 >>> a [1, 3, 13] >>> a[2] 13 >>> a[-1] 13 >>> a[-2] = 5 >>> a [1, 5, 13] >>> a[-3] 1
Замечательной возможностью является срез ( slice ), позволяющий из исходной последовательности получить подпоследовательность, задаваемую начальным и конечным индексами и (опционально) шагом. В сущности, срез - это фильтр по индексу, порождающий из исходной последовательности новую:
>>> a [1, 5, 13] >>> a[1:2] # c 1-го по 2-й элемент, не включая последний [5] >>> a[1:] # c 1-го до последнего элемента [5, 13] >>> a[:2] # c нулевого по 2-й элемент, не включая последний [1, 5] >>> a[::-1] # отрицательный шаг работает от конца к началу [13, 5, 1]
В последнем примере последовательность обращается. Обращение последовательности нагляднее всего продемонстрировать на строке:
>>> 'привет python'[::-1] 'nohtyp тевирп'
Срез без ограничений с двух сторон включает все элементы исходной последовательности и создает ее копию:
Само собой, срез позволяет получить копию только изменяемой последовательности, а в случае с неизменяемой последовательностью возвращает ее саму:
>>> a is a[:] False >>> s = 'привет' >>> s is s[:] True >>> b = b'hello' >>> b is b[:] True
Если срез последовательности используется слева от знака присваивания, то семантика совсем другая: вместо создания новой последовательности выполняется замена элементов среза на значение справа от знака присваивания:
>>> a [1, 5, 13] >>> a[:1] = [5, 6] >>> a [5, 6, 5, 13] >>> a[:] = [-1] >>> a [-1]
При использовании среза с шагом, каждому значению среза должно соответствовать значение справа от знака присваивания, иначе возникает ошибка:
>>> a[:] = [1, 2, 3, 4] >>> a[::2] [1, 3] >>> a[::2] = [-1, -1] >>> a [-1, 2, -1, 4] >>> a[::-1] = range(4) >>> a [3, 2, 1, 0] >>> a[::-1] = -1 Traceback (most recent call last): File "", line 1, in TypeError: must assign iterable to extended slice
Следующая таблица представляет операции и методы, общие для всех последовательностей Python:
Операция | Описание | Работает с range ? |
---|---|---|
x in s | True, если в s есть элемент, равный x, иначе False | да |
x not in s | False, если в s нет элемента, равного x, иначе True | да |
s + t | конкатенация s и t | |
s * n или n * s | конкатенация s с собой n раз | |
s[i] | i-ый элемент s, считая с 0 | да |
s[i:j] | срез s от i до j | да |
s[i:j:k] | срез s от i до j с шагом k | да |
len(s) | длина s | да |
min(s) | наименьший элемент s | да |
max(s) | наибольший элемент s | да |
s.index(x[, i[, j]]) | индекс первого вхождения x в s (начиная с i и заканчивая j) | да |
s.count(x) | всего вхождений x в s | да |
Операции in и not in для строк и строк байтов способны проверить вхождение не только отдельных элементов, но и подпоследовательностей из нескольких элементов:
>>> 'o' in 'hello' True >>> 'hell' in 'hello' True >>> 'bye' not in 'hello' True >>> b'o' in b'hello' True >>> b'hell' in b'hello' True >>> b'bye' in b'hello' False
Для других последовательностей проверятся вхождение ровно одного элемента:
>>> a [3, 2, 1, 0] >>> [3, 2] in a False >>> 3 in a True >>> 2 in a True >>> [3, 2] in [[3, 2], [1, 0]] True
Конкатенация создает новую последовательность, содержащую элементы исходных последовательностей:
>>> 'hello' + ' python!' 'hello python!' >>> [-1, 0] + [1, True, None] [-1, 0, 1, True, None] >>> [-1, 0] * 5 [-1, 0, -1, 0, -1, 0, -1, 0, -1, 0] >>> 5 * [-1, 0] [-1, 0, -1, 0, -1, 0, -1, 0, -1, 0]
Тип range можно рассматривать как неизменяемую последовательность с некоторыми ограничениями на общие операции. Так, нельзя сложить (конкатенировать) два объекта range или умножить объект range на целое число, зато объект range можно индексировать, срезать, проверять вхождение в него значений.
>>> range(10)[5] 5 >>> range(10)[5:] range(5, 10) >>> range(10)[::-1] range(9, -1, -1) >>> 7 in range(10) True >>> 10 in range(10) False >>> max(range(10)) 9 >>> min(range(10)) 0 >>> len(range(10)) 10 >>> range(10).count(5) 1 >>> range(10).index(5) 5
Если срез, или slice - это фильтр по индексам, то встроенная функция filter() позволяет отфильтровать элементы последовательности по их значениям с помощью заданной функции (обычно лямбда):
>>> a [3, 2, 1, 0] >>> filter(lambda x: x > 2, a) >>> list(filter(lambda x: x > 1, a)) [3, 2]
Встроенная функция map() позволяет получить новую последовательность из исходной путем замены каждого элемента на значение, вычисленное с помощью заданной функции (обычно лямбда):
>>> map(lambda x: x**2, a) >>> list(map(lambda x: x**2, a)) [9, 4, 1, 0]
Композиция filter() и map() позволяет и отфильтровать элементы по значению и получить новые значения из исходных:
>>> a [3, 2, 1, 0] >>> list(map(lambda x: x**2, filter(lambda x: x > 2, a))) [9] >>> list(filter(lambda x: x > 2, map(lambda x: x**2, a))) [9, 4]
Конструкция list comprehension (как это по-русски?) может работать как filter() , map() или их комбинация:
>>> # filter >>> [x for x in a if x > 2] [3] >>> # map >>> [x**2 for x in a] [9, 4, 1, 0] >>> # filter then map >>> [x**2 for x in a if x > 2] [9] >>> # map then filter >>> [x for x in [x**2 for x in a] if x > 2] [9, 4]
А следующий фрагмент демонстрирует, как с помощью list comprehension и метода count() найти повторяющиеся элементы в последовательности:
>>> s = 'qwertyq' >>> [x for x in s if s.count(x) > 1] ['q', 'q']
List comprehension поддерживает вложенность как циклов, так и условий:
>>> b = [ . [0, 1, 2], . [3, 4, 5] . ] >>> [y for x in b for y in x] [0, 1, 2, 3, 4, 5]
Последнее предложение эквивалентно следующему фрагменту:
>>> a = [] >>> for x in b: . for y in x: . a.append(y) . >>> a [0, 1, 2, 3, 4, 5]
Вложенные условия в list comprehension эквивалентны составному условию с оператором and или вложенным if внутри цикла:
>>> [x for x in a if x > 0 if x < 5] [1, 2, 3, 4] >>> [x for x in a if x > 0 and x < 5] [1, 2, 3, 4] >>> c = [] >>> for x in a: . if x > 0: . if x < 5: . c.append(x) . >>> c [1, 2, 3, 4]
Если list comprehension немедленно порождает список (объект класса list ), то генераторное выражение, заключенное, в отличие от list comprehension, в обычные скобки ( и ) , порождает генератор, который будет возвращать элементы последовательности, когда они понадобятся:
>>> a [0, 1, 2, 3, 4, 5] >>> g = (x*x for x in a if x%2 == 0) >>> type(g) >>> list(g) [0, 4, 16]
От list comprehension генераторное выражение отличается только ленивым предоставлением элементов последовательности, в остальном поддерживая синтаксис и семантику list comprehension. Для передачи генераторного выражения в качестве аргумента функции достаточно одной пары скобок:
>>> max(x*x for x in a if x%2 == 0) 16 >>> bytes(x*x for x in a if x%2 == 0) b'\x00\x04\x10' >>> list(x*x for x in a if x%2 == 0) [0, 4, 16]
Встроенные функции max() , min() и sum() возвращают максимальное, минимальное значение и сумму элементов последовательности, соответственно. Вместо готовой последовательности эти функции принимают также итерируемые объекты:
>>> a [0, 1, 2, 3, 4, 5] >>> max(a) 5 >>> min(a) 0 >>> min('qwerty') 'e' >>> max('qwerty') 'y' >>> sum(a) 15 >>> sum(x*x for x in a if x%2 == 0) 20
Что если нужно найти не сумму, а произведение элементов? Или сумму их квадратов? С этим нам поможет функция reduce() из модуля functools :
>>> a = a[1:] >>> a [1, 2, 3, 4, 5] >>> from functools import reduce >>> reduce(lambda x, y: x*y, a) 120
Здесь первый параметр x лямбда-функции есть аккумулятор, в котором накапливается результат вычисления, а второй параметр y - каждый следующий элемент последовательности. Перед началом вычисления аккумулятору присваивается значение первого элемента. Если же необходимо использовать другое начальное значение, то оно передается третьим аргументом:
>>> reduce(lambda x, y: x+y*2, 'qwerty', '') 'qqwweerrttyy'
Мы удвоили каждую букву в слове, но без третьего аргумента этого бы сделать не удалось:
>>> reduce(lambda x, y: x+y*2, 'qwerty') 'qwweerrttyy'
Теперь посчитаем сумму квадратов элементов списка a , инициализировав аккумулятор нулем:
>>> reduce(lambda x, y: x+y**2, a, 0) 55
Еще две встроенные функции, сводящие последовательность к единственному значению, - это any() и all() , возвращающие булевы значения.
>>> a [1, 2, 3, 4, 5] >>> any(a) True >>> all(a) True >>> any(x-1 for x in a) True >>> all(x-1 for x in a) False
Функция any() возвращает True , если хотя бы один из элементов последовательности оценивается как True , иначе - возвращает False . Функция all() возвращает True , если все элементы последовательности оцениваются как True , иначе - False . С помощью этих функций и генераторного выражения легко проверить, удовлетворяют ли элементы последовательности некоторому условию:
>>> any(x>5 for x in a) False >>> all(x
Циклы, list comprehension и генераторное выражение позволяют обходить все элементы последовательности. А для параллельного обхода нескольких последовательностей - двух и более - Python предлагает функцию zip() :
>>> a [1, 2, 3, 4, 5] >>> b = 'hello' >>> c = range(-5, 0) >>> for x, y, z in zip(a, b, c): . print(x, y, z) . 1 h -5 2 e -4 3 l -3 4 l -2 5 o -1
Функция zip() завершает работу по концу самой короткой из последовательностей:
>>> b = 'bye' >>> for x, y, z in zip(a, b, c): . print(x, y, z) . 1 b -5 2 y -4 3 e -3
Если необходимо дойти до конца самой длинной из последовательностей, то нужно воспользоваться функцией zip_longest() из модуля itertools :
>>> from itertools import zip_longest >>> for x, y, z in zip_longest(a, b, c): . print(x, y, z) . 1 b -5 2 y -4 3 e -3 4 None -2 5 None -1
Вместо None на месте отсутствующих элементов можно получить значение, заданное с помощью именованного параметра fillvalue :
>>> for x, y, z in zip_longest(a, b, c, fillvalue='*'): . print(x, y, z) . 1 b -5 2 y -4 3 e -3 4 * -2 5 * -1
В завершение обзора, приведу операции и методы, общие для изменяемых последовательностей, то есть, для list и bytearray :
Операция | Описание |
---|---|
s[i] = x | замена i-го элемента s на x |
del s[i] | удаление i-го элемента из s |
s[i:j] = t | замена среза s от i до j на содержимое t |
del s[i:j] | то же, что и s[i:j] = [] |
s[i:j:k] = t | замена элементов s[i:j:k] на элементы t |
del s[i:j:k] | удаление элементов s[i:j:k] из s |
s.append(x) | добавление x в конец последовательности (эквивалентно s[len(s):len(s)] = [x] ) |
s.clear() | удаление всех элементов из s (эквивалентно del s[:] ) |
s.copy() | создание поверхностной копии s (эквивалентно s[:] ) |
s.extend(t) или s += t | расширяет s содержимым t |
s *= n | обновляет s его содержимым, повторенным n раз |
s.insert(i, x) | вставляет x в s по индексу i (эквивалентно s[i:i] = [x] ) |
s.pop([i]) | извлекает элемент с индексом i и удаляет его из s |
s.remove(x) | удаляет первое вхождение x в s |
s.reverse() | меняет порядок элементов в s на обратный |
Часть перечисленных операций и методов были продемонстрированы в действии, другие ждут ваших экспериментов с ними.
Это был (неисчерпывающий) обзор возможностей и приемов работы с последовательностями в Python 3.