Сокращение кода на Python для acmp.ru
ПОСЛЕДНЕЕ ОБНОВЛЕНИЕ: 24.01.2017
Существенно обновленные пункты:
6. Логические операторы: Добавлено использование min от пустых коллекций (3 абзац).
18. eval/exec: Добавлено про парсинг float (3 абзац).
19. map, zip: Добавлены краткие реализации без zip (1 и 2 абзацы).
Новые пункты:
28. Оператор **
31. Стек через односвязный список
32. Одномерные массивы вместо двумерных
33. Регулярные выражения
Предисловие
Добрый день.Меня зовут Хворых Павел, и я сокращаю задачи на acmp.ru. Долгое время я занимался этим делом на C++ и накопил изрядное количество интересных приёмов, которые и изложил в предыдущей статье. Однако в мае 2015 администратор сайта революционно обновил сайт, добавив новые языки и обновив старые (что очень круто), и сбросил все топы (что вообще не круто). В связи с этим я решил перейти на Python (который, кстати, на данный момент является самым кратким языком в системе). Об обнаруженных мною способах сокращения кода на Python я и расскажу далее.
Однако сначала требуется обговорить ряд моментов.
- На acmp используется CPython 3.4.3. Далее под Python я буду подразумевать именно эту реализацию Python.
- Основы Python читателю предлагается изучить самостоятельно, объяснять я их не буду.
- У некоторых терминов Python очень туго с переводом на русский: либо общепринятого перевода вообще нет, либо он недостаточно краткий, либо он спорен. Поэтому для некоторых понятий я просто буду использовать англоязычные термины.
- Python является интерпретируемым языком. Это даёт ему мощь и краткость, однако из-за этого он работает ГОРАЗДО медленнее. Если для C++ примерной оценкой производительности является 109 операций в секунду, то Python осуществляет примерно 107 операций в секунду. Более того, Python фактически не производит оптимизаций и при написании эффективного кода необходимо знать некоторые принципы. Поэтому сдать некоторые задачи на Python фактически невозможно.
- Неопределенные фрагменты кода (например, тела циклов) будут опускаться, вместо них будут писаться многоточие.
- Ну и повторю традиционное предостережение: не используйте приёмы сокращения на практике!
Общие советы
В прошлой статье я описал несколько приёмов, применимых также и для Python. Большая часть их достаточно очевидна, поэтому не буду их повторять. Разве что хотелось бы напомнить о битовой арифметике (возможно, позже я даже добавлю о ней пункт и в эту статью). А вот некоторые общие советы я всё же повторю.- Основой кратчайшего кода является кратчайший алгоритм. Иногда этот кратчайший алгоритм может быть значительно медленнее, чем оптимальный, что особенно критично для Python.
- Не следует переусложнять свой код: никаких классов и лишних функций, никаких промежуточных переменных и комментариев, никаких __main__ и других общепринятых способов грамотного оформления кода. Отличным примером сокращенного кода является код, приведенный в примере на acmp:
print(sum(map(int, input().split())))
В нем нет ни одной переменной! - Используйте однобуквенные переменные. Их у вас как минимум 53: 26 строчных латинских букв, 26 заглавных латинских букв и _ (символ подчеркивания). Однако переменную _ удобно использовать в случаях, когда присваиваемое/возвращаемое значение требуется проигнорировать: IDE не будет выдавать надоедливое предупреждение о неиспользованной переменной. В примерах я буду использовать её именно в таких случаях.
- Обращайте внимание на приоритеты операторов. Например, приоритет оператора and выше приоритета оператора or.
- В питоне очень много встроенных функций и типов, которые можно использовать без импорта библиотек. Обязательно просмотрите встроенные функции и методы встроенных типов (особое внимание уделите методам строк (str) и списков (list)).
Разное полезное
1 Стандартная библиотека
В Python достаточно богатая стандартная библиотека, всегда пробуйте найти в ней решения каких-то стандартных задач. Модули, которые мне доводилось использовать:
- re - для работы с регулярными выражениями. В Python регулярные выражения иногда являются наиболее простым и кратким способом распарсить строку либо просто что-то с ней сделать.
- math - математические функции, а также числа pi и e.
- datetime - работа с датами и временем.
- fractions - работа с рациональными числами. В качестве приятного бонуса содержит функцию вычисления НОД - gcd.
- itertools - содержит внутри себя функции генерации перестановок, сочетаний и всего такого.
2 Длинная арифметика
В тип int встроена длинная арифметика, то есть над ним можно производить любые преобразования, не боясь вылететь за границы типа.
Однако тип float представляет собой обычные числа с плавающей запятой двойной точности (double в си-подобных языках), поэтому при преобразовании int в float длинная арифметика теряется (вплоть до того, что значение int может не влезть в float, в таком случае вылетает исключение). Для решения этой проблемы можно использовать decimal (числа с фиксированной запятой) или даже fractions (рациональные числа).
3 Операторы /, //, %
Оператор / выполняет деление без округления, возвращая float даже если аргументами были int:
10 / 4 == 2.5 11.5 / 2.5 == 4.6 4 / 2 == 2.0Оператор // выполняет деление с округлением (целочисленное деление). Если аргументами оператора // являются float, тогда результат тоже имеет тип float, но округление все равно происходит.
10 // 4 == 2 10.0 // 4.0 == 2.0 11.5 // 2.5 == 4.0Оператор %, возвращающий остаток от деления, ведет себя аналогично:
10 % 4 == 2 10.0 % 4.0 == 2.0 11.5 % 2.5 == 1.5
Эти операторы могут быть использованы для работы с float:
x // 1 # возвращает целую часть числа x % 1 # возвращает дробную часть числаК сожалению, как было сказано ранее, результатами этих операций также будет являться float, что зачастую неприемлемо (например, при выводе результата через print).
Важным отличием операторов // и % является то, что в них используется математическое определение целочисленного деления (округление вниз, остаток неотрицателен) вместо принятого в си-подобных языках деления с округлением в сторону нуля. Разница появляется только на отрицательных числах:
-15 // 4 == -4 # в си-подобных языках -3 -15 % 4 == 1 # в си-подобных языках -3Эту особенность можно использовать для реализации деления с округлением вверх:
(n+k-1)//k # 10 символов -n//k*-1 # 8 символов
4 Строковые литералы
Строки могут быть записаны как с помощью одиночных кавычек ('), так и двойных (") - они равнозначны:
'abc' == "abc"Это удобно использовать, если в строке присутствуют кавычки. Их не придется экранировать, если для записи строки использовать другие кавычки:
"What's your name?" == 'What\'s your name?'
Регулярные выражения часто содержат много слэшей, которые приходится экранировать:
"(\\w)(\\w+)(\\w)"Для таких случаев в Python есть raw strings - в них слэш интерпретируется как обычный символ, а не как символ экранирования:
r"(\w)(\w+)(\w)"
Если требуется вывести несколько строк, то может быть удобно использовать многострочные строки:
"""One string Second string Third string"""Это эквивалентно
"One string\nSecond string\nThird string"В многострочных строках можно использовать одинарные и двойные кавычки без экранирования (за исключением удивительного случая, когда потребуется три кавычки подряд, да еще и именно тех, которые открывают строку).
5 Проверка на истинность
В Python все переменные могут быть проверены на истинность (использованы в логическом контексте, например, в условии if или while):
- Число ложно, если оно равно 0.
- Строка (str) ложна, если она пуста, то есть равна "".
- Коллекция ложна, если она пуста. Ко встроенным коллекциям относятся списки (list), кортежи (tuple), множества (set) и словари (dict).
while s>0: ... s -= 1можно написать просто
while s: ... s -= 1
6 Логические операторы
В Python операторы and и or имеют необычное поведение:
x or y # если x истинно, то возвращает x, иначе y x and y # если x истинно, то возвращает y, иначе xИногда это мешает, а в некоторых специфических случаях может быть использовано при сокращении, поэтому не забывайте эту особенность.
Классическим примером использования необычности этих операторов является реализация условного оператора:
x if c else y # 9 символов c and x or y # 8 символовК сожалению, эта конструкция работает некорректно, если x ложно.
Пользуясь таким приёмом, а также тем, что приоритет оператора and выше приоритета оператора or, можно конструировать нечто похожее на switch из си-подобных языков:
x>0 and 1 or x<0 and 2 or 'NO'
Также следует знать, что операторы and и or являются ленивыми, то есть второй операнд не вычисляется, если достаточно первого.
Благодаря этому вместо
if y == 0: print(x)можно написать
y or print(x)Тут может показаться удивительным, что мы используем результат функции print, хотя, как мы знаем, она ничего не возвращает. На самом деле, любая функция возвращает результат, и если он не написан явно, то возвращается специальный объект None (который всегда ложен).
Некоторые функции, например, min, выбрасывают исключение, если передать им пустую коллекцию. Чтобы отловить такую ситуацию, вместо явной проверки на пустоту можно использовать оператор or:
min(s) if s else -1 # 15 символов min(s or [-1]) # 12 символовЕсли в коллекции s что-то есть, то оператор or передаст в функцию min именно её. Если же она пуста, то or передаст в min список [-1], минимум которого равен -1.
7 Логические значения как числа
Класс bool является подклассом класса int, а True и False равны 1 и 0 соответственно. Единственным их отличием от чисел является переопределенный метод преобразования в строку (чтобы получалось "True" вместо "1").
Во всех остальных случаях можно использовать bool как int. Например, вместо
if x>0: s += 1 # 10 символовможно написать
s += x>0 # 6 символов
Этот приём также используется для реализации функции cmp:
(a>b)-(a<b) # 1, если a>b; 0, если a == b; -1, если a<b
В некоторых задачах возникает необходимость вывести -1, если некоторое условие не выполняется:
a<0 and x or -1 # 11 символовЭто можно записать короче:
-(a>=0) or x # 10 символовАналогично, пример из предыдущего пункта может быть сокращен на один символ заменой x>0 and 1 на +(x>0).
8 Операторы сравнения
Операторы сравнения (<, >, ==, <=, >=, !=, in, not in, is, not is) могут быть сцеплены в одну конструкцию, например, 0 < a < b < c. Эта конструкция эквивалентна 0 < a and a < b and b < c за тем исключением, что переменные a и b появляются и вычисляются всего один раз (а значит можно написать 0 < int(input()) < 3).
Сцепку сравнений очень удобно использовать для замены оператора and:
x > 0 and y > 0 # 9 символов x > 0 < y # 5 символов x > 2 and y > 4 # 9 символов x > 2 < 4 < y # 7 символов
Сцепка операторов является ленивой и выполняется слева направо до тех пор, пока результат не будет ясен. Это позволяет использовать её в качестве условного оператора: например, вместо
if x < y: print(z) # 14 символовможно написать
x < y is print(z) # 13 символовСравнение y is print(z), а значит и сама функция print(z), вызовется только в том случае, если x<y. Ну а оператор is определен для любых объектов, включая возвращаемый функцией print объект None, поэтому эта конструкция не вызывает Runtime Error.
9 Циклы
При написании циклов в Python принято использовать range:
for i in range(n): ... # 15 символовВ приведенном примере i пробегает от 0 до n-1. Однако такой вариант требуется далеко не всегда. Очень часто нам вообще не нужен индекс текущей итерации либо нас вполне устроит, если эти индексы будут идти по убыванию. В таких случаях выгодно написать цикл вручную через while. Рассмотрим несколько распространенных случаев.
- Цикл i = 1...n.
С range:
for i in range(1, n+1): ... # 19 символов
Вручную:
i = 0 while i<n: i += 1 ... # 16 символов
- Цикл i = n...1.
С range:
for i in range(n, 0, -1): ... # 20 символов
Вручную:
i = n while i: ... i -= 1 # 14 символов
- Цикл i = n-1...0.
С range:
for i in range(n-1, -1, -1): ... # 23 символа
Вручную:
i = n while i: i -= 1 ... # 14 символов
Для убывающих циклов (2 и 3 случай) существует дополнительная оптимизация: если после цикла больше нигде не требуется переменная n, то можно не тратить три символа на создание переменной i, а итерироваться непосредственно с помощью переменной n.
10 Умножение на число
В Python для многих типов определена операция умножения на число:
"abc" * 3 == "abcabcabc" [1, 2, 3] * 2 == [1, 2, 3, 1, 2, 3] (1, 2, 3) * 2 == (1, 2, 3, 1, 2, 3)Что приятно, при умножении на отрицательное число получается просто пустая коллекция.
Есть два классических примера применения данного приёма:
- Иногда требуется создать копию списка, однако списки в Python являются ссылочными типами и операция y=x не создает копию списка: переменные x и y просто будут указывать на один и тот же список. Операция умножения на число решает эту проблему: y=x*1.
- Пусть нам требуется осуществить n однотипных итераций, например, прочитать n строк, причем нам не требуется знать номер текущей итерации. Из уже известных из предыдущего пункта способов кратчайшим является убывающий цикл:
while n: ... n -= 1 # 11 символов
Однако в этом случае мы портим переменную n и если этого необходимо этого избежать, приходится тратить еще 3 символа на создание временной переменной. Операция умножения на число позволяет избежать этой проблемы:
for _ in ' ' * n: ... # 11 символов
Если необходимо проделать 2*n итераций, то вместо естественного ' ' * 2*n можно написать ' '*n.
Иногда бывают случаи, когда n заранее известно. В этом случае можно сократить код еще сильнее. Например, следующий код проделает ровно 4 итерации:
for _ in ' ': ... # 9 символов
11 Цикл с индексом
Иногда требуется пройтись по строке/списку в цикле, зная при этом индекс текущего элемента. В Python это принято делать так:
for i, x in enumerate(m): ... # 21 символовНо можно сделать гораздо короче:
i = 0 for x in m: ... i += 1 # 15 символов
12 Условный оператор
Python предлагает нам ужасно многословный
x if c else y # 9 символовЕсть несколько вариантов решения проблемы.
c and x or y # 8 символов
Как упоминалось ранее, этот вариант не работает, если x может быть ложно. Такое бывает относительно редко, но всё же бывает, поэтому следует быть очень внимательным.[y,x][c] # 8 символов
Этот вариант работает только если c является логическим выражением или числом 0/1.c*x or y # 6 символов
Переменная x должна быть истинна, для неё должно быть определено умножение на число (то есть она должна быть числом, строкой, списком или кортежем). Но самой большой проблемой этого варианта является то, что переменная c должна быть числом 0/1 либо булевой переменной, что получается крайне редко. При попытке использовать вместо c логическое выражение возникает необходимость поставить вокруг него скобки, что увеличивает количество символов до 8.
Есть несколько случаев, когда данную проблему можно решить: если известно, что n - целое положительное число, то условие n==1 можно заменить на 1//n, а условие n>1 - на 1%n; если n - неотрицательное число, то условие n==0 можно заменить на 0**n.
13 Немного теории: итераторы и итерируемые объекты
Итератор - это объект, который последовательно возвращает некоторые элементы. Итерируемый объект (iterable) - это объект, у которого мы можем получить итератор. Всё это можно рассматривать так: итерируемый объект представляет собой некую последовательность элементов, которую мы можем обойти с помощью итератора.
К итерируемым объектам относятся строки, встроенные коллекции (кортежи, списки, множества, словари), объекты определенных классов (например, файлы) и даже сами итераторы.
Для работы с итерируемыми объектами предназначена конструкция for X in Y: она запрашивает итератор у Y и поочередно получает из этого итератора элементы, подставляя их вместо X.
Большое количество функций (например, sum, max, all, sorted) могут принимать не только списки, но и произвольные итерируемые объекты, что открывает широкие возможности для сокращения (некоторые из них будут рассматриваться в последующих пунктах).
Некоторые функции (например, zip и map) возвращают итераторы, поэтому необходимо понимать следующую их особенность: итератор является "одноразовым" объектом, то есть из него можно получить последовательность элементов только один раз. При попытке повторного обхода он сразу сообщит, что элементов больше нет. С таким поведением можно было столкнуться при использовании функции map:
a = map(int, ["1", "2", "3"]) print(sum(a)) # 6 print(sum(a)) # 0Функция range возвращает итерируемый объект, а не итератор, поэтому его можно обходить несколько раз:
a = range(4) print(sum(a)) # 6 print(sum(a)) # 6
14 Генераторы
В Python есть специальный синтаксис для создания некоторых итерируемых объектов:
# генератор списков (list comprehension) [expr for_comprehension] # порождает список # выражение-генератор (generator expression) (expr for_comprehension) # порождает итератор # генератор множеств (set comprehension) {expr for_comprehension} # порождает множество # генератор словарей (dict comprehension) {expr:expr for_comprehension} # порождает словарьexpr - это произвольное выражение; for_comprehension - это несколько записанных подряд for-выражений (for ... in ...) и if-выражений (if ...), причем первым выражением обязательно должно быть for-выражение. Чаще всего for_comprehension состоит просто из одного for-выражения.
Приведем несколько примеров, чтобы всё стало понятно:
[x*x for x in range(5)] # порождает список [0, 1, 4, 9, 16] [x for x in range(10) if x%2 == 1] # порождает список [1, 3, 5, 7, 9] {x:x*x for x in range(5)} # порождает словарь {0:0, 1:1, 2:4, 3:9, 4:16} [x+y for x in "abc" for y in "xy"] # порождает список ["ax", "ay", "bx", "by", "cx", "cy"]А теперь более полезные примеры:
s = [input() for _ in ' ' * n] # прочитать n строк u = [x for x in u if x != n] # удалить из u все вхождения элемента n
Если выражение-генератор передать в функцию единственным аргументом, то образуется две пары круглых скобок:
all((x>0 for x in m)) # все элементы m положительны?Специально в таком случае разрешено опускать одну из пар:
all(x>0 for x in m) # эквивалентно предыдущему выражению
15 Кортежи
Выражение, где несколько выражений стоят через запятую, автоматически формируется в кортеж:
x = 2, 3+6, 42 # x - кортеж из трёх элементов: 2, 9 и 42 y = 2, # y - кортеж из одного элемента 2Поскольку кортеж является итерируемым объектом, то можно использовать это выражение в инструкции for, когда требуется выполнить одну и ту же операцию для нескольких значений:
for x in 1, -1: ...Если в range используется небольшая заранее известная константа, то короче будет выписать требуемые значения явно:
for i in range(4): ... # 15 символов for i in 0,1,2,3: ... # 14 символов
16 Присваивание
Рассмотрим интересную форму оператора присваивания, которая выглядит следующим образом:
target_list = iterabletarget_list - это последовательность выражений, разделенных запятыми; iterable - произвольный итерируемый объект.
Последовательность выражений target_list может состоять даже из одного элемента, но в этом случае надо обязательно поставить после этого элемента запятую.
При таком присваивании элементы iterable присваиваются соответствующим элементам target_list. Разумеется, количество элементов в target_list должно быть равно количеству элементов в iterable.
a, b, c = x, y, z # a = x, b = y, c = z a, b, c = "xyz" # a = "x", b = "y", c = "z" a, b, c = range(3) # a = 0, b = 1, c = 2 a, = [1] # a = 1 a, b = b, a # меняем значения переменных a и b местами a, b = b, a+b # казалось бы, причём тут Фибоначчи
Перед одним из элементов target_list может стоять звёздочка. Этот элемент станет списком из значений, которые не влезли в другие элементы target_list. Проще всего объяснить это примером:
a, b, *c, d, e = 1, 2, 3, 4, 5, 6, 7 # a = 1, b = 2, c = [3, 4, 5], d = 6, e = 7Важно понять, что помеченная звездочкой переменная будет списком вне зависимости от типа iterable и от того, сколько в неё прилетает значений:
a, b, *c, d, e = 1, 2, 3, 4 # a = 1, b = 2, c = [], d = 3, e = 4 a, b, *c, d, e = 1, 2, 3, 4, 5 # a = 1, b = 2, c = [3], d = 4, e = 5 a, b, *c, d, e = "1234567" # a = "1", b = "2", c = ["3", "4", "5"], d = "6", e = "7"Звёздочка очень полезна для сокращения, несколько полезных приёмов мы рассмотрим позже.
Элементом target_list может быть не только переменная, но и любое выражение, которому можно что-то присвоить: элемент коллекции, получаемый по индексу; срез (о них мы поговорим позже) или даже другой target_list (тогда его надо заключить в круглые или квадратные скобки):
a, (b, c), d = "12", "34", "56" # a = "12", b = "3", c = "4", d = "56"Разумеется, во вложенном target_list также может присутствовать звёздочка:
(*a,), *b = "123", "456", "789" # a = ["1", "2", "3"], b = ["456", "789"]
17 Итератор в список
При необходимости прочитать последовательность чисел хочется написать следующий код:
u = map(int, input().split())Проблема в том, что map возвращает итератор, который разрешает только пробежаться по полученной последовательности чисел, причем всего один раз. В более сложных случаях приходится явно преобразовывать итератор в список:
u = list(map(int, input().split())) # 32 символаВ таком случае можно использовать генератор списков вместо map:
u = [int(x) for x in input().split()] # 31 символОднако звёздочка решает эту проблему короче:
*u, = map(int, input().split()) # 28 символов
18 eval/exec
Функция eval принимает строку и интерпретирует её как произвольное выражение Python, возвращая полученный результат.
Это позволяет нестандартными способами решать некоторые задачи: вместо того, чтобы парсить считанную строку и производить некоторые вычисления с полученными данными (как и подразумевалось автором задачи), можно модифицировать саму строку, чтобы она обрела корректный Python-синтаксис и про-eval-ить её.
Рассмотрим простой пример: в единственной строке находится два числа и требуется поделить первое на второе нацело.
Обычное решение этой задачи:
a, b = map(int, input().split()) print(a//b) # 39 символовРешение с помощью eval:
print(eval(input().replace(' ', '//'))) # 37 символовДостаточно часто в строках бывают лишние пробелы и replace выдаёт некорректную строку. Иногда справиться с этим помогает опциональный параметр count метода replace, задающий максимальное количество замен:
print(eval(input().replace(' ', '//', 1))) # заменяем только первый пробел
Другим популярным применением функции eval является повторение одной инструкции. Рассмотрим некоторые интересные случаи.
- Прочитать n строк:
s = [input() for _ in ' '*n] # 21 символ s = eval('input(),' * n) # 20 символов
- Создать двухмерный массив (матрицу):
s = [[0] * n for _ in ' ' * n] # 19 символов s = eval('[0] * n,' * n) # 18 символов
Для парсинга дробных чисел используется float:
float('2.4') == 2.4Однако функция eval короче на один символ:
eval('2.4') == 2.4Приятный бонус: если входное число было без точки, то оно распарсится как int. Это бывает полезно при парсинге группы чисел, в которых целые и дробные перемешаны:
n, *m = map(eval, '2 2.4 3.6'.split()) # n == 2 (целое), m == [2.4, 3.6] (дробные)
Функция exec, в отличие от eval, позволяет интерпретировать произвольный код. На практике может быть применена только в специфических случаях.
19 map, zip
Функция map может принимать несколько итерируемых объектов:
map(max, [1, 20, 3], [10, 2, 30]) # 10, 20, 30Функция zip позволяет работать с несколькими итерируемыми объектами параллельно:
for a, b in zip(A, B): ...Например, с его помощью можно сгруппировать элементы списка попарно:
for x, y in zip(s[::2], s[1::2]): ... # 28 символовЕсли s представляет собой итератор (например, является результатом вызова функции map), то, благодаря одноразовости извлечения каждого элемента из итератора, выражение упрощается до
for x, y in zip(s, s): ...Более того, с помощью функции iter мы можем получить итератор у любого итерируемого объекта (например, списка) s:
k = iter(s) for x, y in zip(k, k): ... # 26 символовВ случае, когда необходимо разбивать не на пары, а на тройки, становится выгодно использовать следующую конструкцию:
for x, y, z in zip(*[iter(s)]*3): ...Следует отметить, что использование zip для разбиения коллекции на группки не всегда оправдано, иногда можно использовать другой подход:
while s: x, y, *s = s ...Этот подход имеет квадратичную сложность, поэтому не рекомендуется для применения к большим коллекциям.
Другим вариантом использования zip является перебор всех пар соседних элементов коллекции:
for x, y in zip(s, s[1:]): ...Для этого варианта также существует краткая реализация без zip:
x, *u = u for y in u: ... x = y
20 open
Обычно функцию open используют для открытия файлов:
f = open("input.txt") # f - объект файла, связанный с файлом input.txtОднако если заглянуть в документацию, то можно узнать, что open умеет работать с уже открытым файлом, для этого ей надо передать дескриптор этого файла. У стандартных потоков stdin, stdout и stderr есть свои дескрипторы - 0, 1 и 2 соответственно. Следовательно, мы можем написать
f = open(0) # f - объект файла, связанный со стандартным входным потокоми читать из стандартного входного потока, как из файла.
Объект файла является итератором, поэтому можно писать код вроде этого
for s in open(0): ... # цикл по всем строкам файлаОднако чаще всего бывает нужно просто считать весь файл:
*s, = open(0) # s - список строк файлаДостаточно часто в первой строке файла находится просто количество последующих строк. Поскольку мы и так считываем все строки, эту информацию можно проигнорировать:
_, *s = open(0) # s - список строк файла кроме первой
К сожалению, open, в отличие от input, возвращает строки с символами перевода строки (если файл не заканчивается символом перевода строки, то последняя строка без перевода). Это не имеет значения, если в строках находятся числа (ведь они всё равно будут обрабатываться методом split или функцией int, которым безразличен лишний пробельный символ). Однако если нужно работать со строками именно как со строками, то придется избавиться от лишнего символа явно (например, с помощью метода strip).
У объекта файла есть метод read, который позволяет прочитать весь файл за раз, в одну строку. Например, если файл состоит только из чисел и мы хотим все их прочитать, то можно написать
map(int, open(0).read().split())
21 Функции и методы
Функции в Python являются объектами первого класса, то есть ими можно оперировать как с обычными объектами. Поэтому мы можем передавать одни функции в качестве аргументов в другие функции (например, в map) или сохранять их в переменные:
a = input() b = input() # 18 символовсокращается до
I = input a = I() b = I() # 17 символовМетоды классов на самом деле тоже являются функциями, которые первым аргументом принимают объект, у которого был вызван метод (этот аргумент принято называть self). Когда мы вызываем метод непосредственно от имени объекта, он подставляется в качестве первого аргумента автоматически: obj.func(args...) эквивалентно Type.func(obj, args...), если obj имеет тип Type.
Это всё достаточно сложно устроено, поэтому не будем углубляться в детали, а просто отметим, что выражения вида obj.func и Type.func, так же как и обычные функции, являются объектами первого класса, то есть их можно сохранять в переменные
u = str.replace t = u(u(u(s, "a", "A"), "b", "B"), "c", "C")и передавать в другие функции
map(str.split, open(0))Например, таким образом можно реализовать replace для списков:
map({2:5}.get, L, L) # заменяет в списке L все вхождения 2 на 5или найти количество несовпадающих элементов в коллекциях:
sum(map(str.__ne__, A, B)) # 24 символа sum(a != b for a, b in zip(A, B)) # 25 символов
У встроенных типов есть большое количество перегруженных операторов (методов), благодаря которым можно на лету формировать новые функции (что бывает нужно при использовании map):
s.__eq__ # функция, сравнивающая переданный аргумент с s 1 .__add__ # функция, увеличивающая переданный аргумент на 1Обратите внимание на пробел после 1 в последнем примере: он нужен, чтобы парсер Python воспринял цифру и точку как разные токены, а не как один токен 1. (вещественное число).
22 Множественное присваивание
Python позволяет совершать сразу несколько присваиваний одной инструкцией:
expr1 = expr2 = ... = exprN = exprэквивалентно
temp = expr expr1 = temp expr2 = temp ... exprN = tempПриведём несколько примеров:
a = b = c = 0 # создает три переменные a, b и c, равные нулю x = y = [] # создает две переменные x и y, указывающие на один и тот же список *x, = *y, = map(int, "1 2 3".split()) # создает списки x = [1, 2, 3] и y = []Разберемся в причинах столь необычного поведения последнего примера. Как следует из вышесказанного, он эквивалентен
temp = map(int, "1 2 3".split()) *x, = temp *y, = tempИтератор, возвращаемый функцией map, сохраняется во временную переменную temp. При выполнении первого присваивания *x, = temp выполняется пробег по итератору, то есть из него извлекаются все значения. Поэтому при выполнении второго присваивания *y, = temp итератор сразу сообщает, что он пуст, и y присваивается пустой список.
23 Работа с коллекциями
Часто требуется создать пустую коллекцию:
L = [] # пустой список L = {} # пустой словарь L = set() # пустое множествоСоздание пустого множества занимает целых 7 символов. Однако иногда можно создать множество из одного элемента, например, если этот элемент все равно будет добавлен позже либо если он не повлияет на логику программы:
L = {0} # множество из одного элемента 0
Чтобы добавить элемент в конец списка принято использовать следующую конструкцию:
L.append(x) # 11 символовОднако добавлять список к списку намного короче, поэтому сформируем из элемента x список и прибавим его к L:
L += [x] # 6 символовТеперь заметим, что операция += поддерживает добавление не только списка, а вообще любого итерируемого объекта, поэтому будем добавлять кортеж вместо списка:
L += x, # 5 символовА для добавления элемента в начало списка короче всего написать
L = [x]+L
У множеств также можно использовать перегруженные операторы вместо методов:
S |= {x} # добавить x в S S -= {x} # удалить x из S S -= S # очистить SУ множеств вообще много операторов, но хотелось бы упомянуть необычные операторы сравнения множеств:
A <= B # является ли A подмножеством B A < B # является ли A подмножеством B, причем A != BНапример, их можно использовать для проверки того, что строка состоит только из заданных символов:
set(s) <= set(t) # строка s состоит только из символов строки tВ качестве обратной операции можно использовать вычитание:
set(s) - set(t) # символы строки s, не входящие в строку t if set(s) - set(t): ... # если строка s состоит не только из символов строки t
У списков и кортежей также перегружен оператор сравнения, поэтому их можно сортировать.
У словарей есть очень полезный метод get:
D.get(x, y) # вернуть D[x], если такой элемент есть в словаре, иначе вернуть yЕсли ключами словаря являются строки, то может быть короче использовать именованные аргументы (keyword arguments):
{'a':1, 'b':4, 'c':9} # 19 символов dict(a=1, b=4, c=9) # 17 символов
Использование звёздочки позволяет нам распиливать список на части:
a, *T = L # a = L[0], T = L[1:] *T, a = L # a = L[-1], T = L[:-1] (все элементы, кроме последнего)Рассмотрим несколько интересных частных случаев:
# Получить последний элемент списка a = L[-1] # 7 символов *_, a = L # 6 символов # Извлечь последний элемент a = L.pop() # 9 символов *L, a = L # 6 символов # Извлечь первый элемент a = L.pop(0) # 10 символов a, *L = L # 6 символов # Получить первый элемент списка либо y, если список пуст a = L and L[0] or y # 13 символов a = (L+[y])[0] # 12 символов a, *_ = L+[y] # 10 символовВажно отметить, что инструкции со звёздочкой создают новые списки вместо использования/модификации старых, поэтому являются неэффективными.
24 Срезы (slice)
Срезы работают для строк, списков и кортежей (причем срез строки - тоже строка, срез списка - список, срез кортежа - кортеж).
Самый известный пример применения срезов - это разворот:
m[::-1]Благодаря этому мы можем очень кратко проверить, является ли строка палиндромом:
s == s[::-1]
В задачах часто требуется вывести ту или определенную строку в зависимости от некоторого условия. Пользуясь пунктом об условных операторах, можно предложить два разных решения:
c and 'NO' or 'YES' # 15 символов ['YES', 'NO'][c] # 15 символовСрезы дают нам третье решение:
'YNEOS'[c::2] # 13 символов
Огромным преимуществом срезов является то, что если индексы выходят за границы коллекции, то просто возвращается пустая коллекция. Это позволяет использовать их для проверки длины коллекции в условных операторах:
if len(s) > n: ... # 11 символов if s[n:]: ... # 8 символова также для безопасного извлечения символа из строки:
s[i] # если выход за границы - возникает исключение IndexError s[i:i+1] # если выход за границы - возвращается пустая строка
Для изменяемых (mutable) коллекций (например, списков) мы можем модифицировать срезы, при этом меняться будет и исходная коллекция. Для удобства во всех примерах будем полагать A = [0, 1, 2, 3, 4].
A[i:j] = B # заменить элементы среза на элементы итерируемого объекта B A[2:4] = 9, 8, 7 # A = [0, 1, 9, 8, 7, 4] A[:i] += B # вставка элементов итерируемого объекта B в i-ую позицию A[:2] += 7, 7 # A = [0, 1, 7, 7, 2, 3, 4] A[:i] += x, # вставка элемента x в i-ую позицию A[:2] += 7, # A = [0, 1, 7, 2, 3, 4] A[i:j] *= k # размножить срез в коллекции A[2:4] *= 3 # A = [0, 1, 2, 3, 2, 3, 2, 3, 4] del A[i:j] # удалить элементы среза из A del A[2:4] # A = [0, 1, 4] A[i:j:k] = B # заменить элементы среза на элементы итерируемого объекта B # len(A[i:j:k]) должно быть равно len(B) A[1::2] = 9, 9 # A = [0, 9, 2, 9, 4] del A[i:j:k] # удалить элементы среза из A del A[::2] # A = [1, 3]
25 Вывод
Пусть требуется вывести список чисел через пробел. Наивное решение этой задачи выглядит так:
print(' '.join(map(str, m))) # 26 символовОднако лучше написать так:
print(*m) # 9 символовОператор * распаковывает элементы произвольного итерируемого объекта в аргументы функции. В данном случае он распаковывает элементы списка m в функцию print, которая, как известно, выводит переданные ей аргументы через пробел.
У метода print есть параметры sep и end.
Параметр sep определяет, чем разделяются аргументы. По умолчанию sep равен пробелу.
u = [2, 3, 4] print(*u) # выведет "2 3 4" print(*u, sep=', ') # выведет "2, 3, 4"Параметр end определяет, чем завершается вывод. По умолчанию end равен переводу строки.
print(s) # выведет s с переводом строки print(s, end='') # выведет s без перевода строки print(end=s) # выведет s без перевода строкиОбратите внимание, что sep и end должны быть строками, поэтому последний пример применим только если s является строкой.
Если вывод нетривиальный, используйте оператор % вместо ручного формирования вывода или метода format:
print("Red: ", x, ", green: ", y, ", blue: ", z, ".", sep="") # 49 символов print("Red: {}, green: {}, blue: {}.".format(x, y, z)) # 47 символов print("Red: %d, green: %d, blue: %d." % (x, y, z)) # 41 символЭтот оператор может оказаться полезен даже в очень простых случаях:
'('+s+')' # 9 символов '(%s)' % s # 8 символов
На самом деле тестирующему серверу почти во всех задачах безразлично, какими пробельными символами и в каком количестве разделены выводимые данные. Например, если в задаче требуется вывести числа через пробел, то можно спокойно выводить каждое число в отдельной строке, используя print.
26 Оператор in
Оператор in предназначен для проверки наличия элемента в итерируемом объекте:
2 in [2, 3, 4] # True 2 in range(5) # TrueОднако у него есть еще одно полезное предназначение - проверка вхождения подстроки в строку:
"bc" in "abcd" # True
27 Стандартные функции
У некоторых стандартных функций есть параметры по умолчанию, которые могут быть полезны в деле сокращения. Выше уже приводился пример функции print с параметрами sep и end. Рассмотрим еще несколько примеров:
- У функций min, max, sorted и метода list.sort есть опциональный параметр key. Он задаёт функцию, которая извлекает из элемента ключ, по которому и будет производится сравнение. Например, результатом min(["2", "11"], key=int) будет "2", потому что int("2") < int("11").
- У конструктора типа int есть опциональный параметр base, задающий систему счисления:
int("DEAD", 16) # 57005 int("2011", 3) # 58 int("python", 36) # 1570137287
В частности, это можно использовать для более сжатого хранения чисел:
# 96 символов - массив предпосчитанных значений u = [1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790] # 82 символа - тот же массив, сжатый в строку 36-разрядных чисел u = [int(x, 36) for x in '1 1 2 5 E 16 3O BX 13Q 3R2 CYK 19CY 4GI4 FX84 1LBM0 5RSL9 L1U5I 256QK6'.split()]
К сожалению, столь же мощной обратной функции нет, только несколько частных: hex, oct и bin, преобразующие в 16-, 8- и 2-чные системы счисления соответственно. - У функции sum есть параметр start, который указывает, с какого значения начнется суммирование (по умолчанию равен 0).
Это можно использовать, если вам вдруг понадобилось преобразовать список списков в список (другими словами, сложить списки):
sum(l, [])
К сожалению, строки так складывать запрещено, приходится использовать ''.join. - У функции pow есть опциональный третий параметр, который задаёт модуль, по которому будут производиться вычисления.
pow(x, y, z) == x**y % z
Хотя вызов pow длиннее аналогичной записи на операторах, он производит вычисления намного эффективнее, что иногда может оказаться важным. - В функцию input можно передать опциональный параметр, который будет выведен перед началом чтения:
s = input(x) # эквивалентно print(x, end=''); s = input()
28 Оператор **
Оператор ** выполняет возведение в степень, причём если аргументами были int, возвращается тоже int:
3**4 == 81Нельзя забывать, что если требуется возвести переменную в квадрат, следует писать более короткое x*x вместо логичного x**2.
С помощью данного оператора можно находить корень числа без подключения модуля math:
49 ** .5 == 7.0
Также этот оператор может быть применён для сокращенного объявления констант:
1000000007 # 10 символов 10**9 + 7 # 7 символовИногда требуется задать какую-то константу, которая была бы заведомо больше какого-то числа (например, для создания списка через [0] * n). Несколько примеров:
10**4 == 10000 # 5 символов 5**6 == 15625 # 4 символа 10**5 == 100000 # 5 символов 7**6 == 117649 # 4 символаВпрочем, если не требуется целочисленность, то для этой задачи подойдет обычный экспоненциальный формат, например, 1e9.
29 Комплексные числа
В геометрических задачах часто возникает задача вычисления корня из суммы двух квадратов.
Обычно это пишут так:
(a*a + b*b)**.5 # 13 символов ((a-c)**2 + (b-d)**2)**.5 # 23 символаНо лучше использовать комплексные числа:
abs(a + b*1j) # 11 символов abs(a-с + (b-d)*1j) # 18 символовЕсли требуется найти расстояние между точками, заданными кортежем/списком из двух чисел, то может быть удобно конструировать комплексные числа иначе:
c = complex r = abs(c(*x)-c(*y))
Комплексные числа вообще удобно использовать в геометрии - это на самом деле готовый класс для точек или векторов: их можно складывать, вычитать, умножать на константу, поворачивать и т.д. Рассмотрим несколько интересных случаев.
Как уже упоминалось выше, если мы будем хранить точки комплексными числами, расстояние между ними можно вычислить как модуль разности: abs(a - b).
В задачах на графы иногда возникает необходимость обхода двумерного массива (например, волновым поиском). При этом приходится отписывать 4 варианта: идём вправо, вверх, влево, вниз. Если же хранить текущую позицию комплексным числом, то это можно записать так:
for d in 1, 1j, -1, -1j: f(x+d) # 24 символаКак известно, умножение на 1j поворачивает вектор на 90 градусов против часовой стрелки (а на -1j - по часовой). Это не только полезно для реализации поворотов налево/направо, но и может быть использовано для сокращения вышеприведенного кода:
d = 1 for _ in ' ': f(x+d) d *= 1j # 23 символаПри использовании комплексных чисел мы сталкиваемся с тем, что слишком длинно получать координаты из комплексного числа: z.real, z.imag. Следовательно, следует свести получение координат к минимуму, пытаясь производить все вычисления в комплексных числах. Однако если мы работаем с двумерным массивом, то необходимость получать координаты возникает при каждом обращении к массиву. Эту проблему можно попробовать решить заменой двумерного массива на словарь (dict) с комплексными числами в качестве ключа. Такое решение может сильно увеличить потребление памяти, поэтому не всегда приемлемо.
30 import
Если импортируемые из модуля объекты используются более одного раза или имя модуля состоит не менее чем из 5 символов, то выгодно использовать инструкцию from module import * вместо import module:
import math y = math.sin(x) + math.cos(x) # 35 символов from math import * y = sin(x) + cos(x) # 30 символов
Инструкция импорта позволяет импортировать сразу несколько модулей:
import re, math
31 Стек через односвязный список
В качестве стека принято использовать список:
q = [] # пустой стек if q: # проверка стека на наличие элементов q += b, # добавление элемента q[-1] # последний элемент *q, b = q # извлечение элементаИз грустных моментов отметим неэффективное извлечение элемента из стека.
А теперь реализуем стек через односвязный список:
q = 0 # пустой стек if q: # проверка стека на непустоту q = q, b # добавление элемента q[1] # последний элемент q, b = q # извлечение элементаТеперь все операции эффективны, а некоторые еще и короче стали! Разумеется, при такой реализации мы уже не можем кратко пробежаться по стеку и эффективно обратиться к элементу по индексу.
32 Одномерные массивы вместо двумерных
Достаточно часто оказывается короче использовать одномерные массивы вместо двумерных. Разберём несколько сценариев использования.
Создание одномерного массива гораздо короче создания двумерного:
M = [[0]*m for _ in ' '*n] M = n*m * [0]Чтобы пробежаться по одномерному массиву, нужен один цикл вместо двух. Причём это оказывается короче пробега по двумерному массиву, даже если необходимо знать текущие индексы:
for i in range(n): for j in range(m): ... # 30 символов for k in range(n*m): i = k//m j = k%m ... # 28 символовИндексация в одномерном длиннее всего на один символ:
M[i][j] # 7 символов M[i*m+j] # 8 символовНо при одномерном подходе есть возможность использовать одномерную индексацию M[k], которая занимает всего 4 символа. В такой одномерной индексации оказывается короче переходить к соседним ячейкам массива:
M[i][j] # текущая M[i][j+1] # правая от текущей M[i+1][j] # нижняя от текущей M[k] # текущая M[k+1] # правая от текущей M[k+m] # нижняя от текущейДля хранения текущей позиции в одномерном массиве нужно одно число, а в двухмерном - два.
В двумерных массивах строки выражены явно, для получения списка столбцов можно использовать выражение zip(*M). В одномерных массивах строки и столбцы можно получать по индексу с помощью срезов:
M[i*m:i*m+m] # i-я строка M[j::m] # j-й столбецКак видно из вышесказанного, код для одномерного и двумерного подходов кардинально отличается и сравнивать их очень сложно, поэтому лучше всего попробовать оба варианта и посмотреть, какой выйдет короче в данной конкретной задаче.
Важный класс задач на двумерные массивы - это задачи, в которых есть некая карта и по этой карте необходимо перемещаться (поиском в ширину, заливкой, поиском в глубину, ...). В таких задачах обычно имеются запретные клетки, в которые нельзя двигаться. При движении нельзя выходить за границы карты, поэтому приходится при проверке возможности перехода прописывать множество условий примерно следующего вида:
if i: f(i-1, j) if j: f(i, j-1) if i<n-1: f(i+1, j) if j<m-1: f(i, j+1)Проблема обостряется в свете переписывания на с двумерного массива на одномерный: для проверок нужны индексы в явном виде, а на их получение при одномерном подходе тратятся дополнительные символы.
Обычным способом избавления от этих проверок является создание дополнительного слоя запретных клеток вокруг имеющейся карты. В результате алгоритм не выходит за границы карты, потому что она окружена запретными клетками.
В Python этот способ обретает второе дыхание благодаря допустимости отрицательных индексов. Поясним на примере. Предположим, что мы добавили запретные клетки справа от карты. Тогда мы автоматически получаем запретные клетки слева от карты: слева от индекса 0 находится индекс -1, который указывает на конец массива, то есть на правую границу карты, которая состоит из запретных блоков! Аналогично, добавив запретные клетки снизу от карты, мы автоматически получаем верхнюю границу.
Карта часто выдаётся в виде списка строк. Если прочитать их через open, то в конце каждой строки окажется символ '\n'. Если при написании кода считать запретными все клетки, не содержащие определённый символ (например, точку), то правая (и, соответственно, левая) граница возникает автоматически. Для завершения границ карты остается добавить строку с запретными символами в конец списка строк.
После избавления от проверок перебор возможных движений при одномерном подходе превращается примерно в следующее:
for d in 1, m, -1, -m: f(k+d)
33 Регулярные выражения
Типичные регулярные выражения содержат большое количество обратных слешей (\), которые приходится экранировать. Для решения этой проблемы в Python были введены raw строки, в которых \ интерпретируется как обычный символ:
"\\w+\\s+\\1" # 13 символов, обычная строка r"\w+\s+\1" # 11 символов, raw строка
Иногда возникает необходимость задать поведение регулярного выражения, например, игнорирование регистра. Документация предоставляет набор флагов, для данного случая подходит re.I (сокращенная версия re.IGNORECASE). Однако эти флаги на самом деле являются просто числами:
print(re.I) # печатает 2То есть, вместо того, чтобы передавать флаги, передавайте его численное значение. Бонусом получаем более краткое комбинирование флагов:
re.I | re.M # 9 символов I | M # 3 символа (требует импорта имён из re) 2 | 8 # 3 символа (не требует импорта имён) 10 # 2 символа
Функция match проверяет, что некоторый префикс (начало) строки соответствует заданному шаблону. Чтобы проверить на соответствие шаблону строку целиком, предлагается использовать функцию fullmatch. Однако, символ $ в конце шаблона позволяет добиться нужного эффекта и для функции match:
fullmatch('[a-h]\d-[a-h]\d', s) # 30 символов match('[a-h]\d-[a-h]\d$', s) # 27 символов
Второй аргумент функции sub указывает, на что заменяются подходящие под шаблон подстроки. В качестве этого аргумента можно передавать не только строку, но и функцию, которая для находимых подстрок должна определять на что они заменятся.
Неиспользованное
Есть еще достаточно большое количество интересных приёмов, которые мне не удалось нигде применить. Однако я мог просто не встретить нужной задачи, либо мог не додуматься до их применения, поэтому перечислю их ниже. Если у меня выйдет где-то их применить - перенесу в основную часть.34 str.translate
Метод str.translate заменяет все вхождения заданных символов на другие символы либо строки. Замена задаётся словарём, причём в качестве ключа требуется указывать код заменяемого символа:
"data".translate({97: "aaa"}) == "daaataaa" # 97 - код символа 'a'Метод позволяет производить несколько замен одновременно:
"password".translate({97: "@", 111: "0", 115: "$"}) == "p@$$w0rd"Если значением является односимвольная строка, то вместо неё можно написать код этого символа:
"password".translate({97: 64, 111: 48, 115: 36}) == "p@$$w0rd" # эквивалентно предыдущему примеруЕсли требуется удалить символ, то в качестве значения лучше указывать "" вместо рекомендуемого в документации None:
"what".translate({97: "u", 104: ""}) == "wut"Возникающие после замены символы не обрабатываются, то есть новой замены для них не производится, поэтому можно заменять символы друг на друга:
"abcd".translate({97: "b", 98: "a"}) == "bacd"Замену можно задавать не только словарём, но и любым объектом, поддерживающим индексацию (например, список или строка). Пользуясь тем, что тестирующий сервер не подсчитывает пробелы (после недавнего обновления это не всегда верно), получаем:
"abcd".translate(" ba") == "bacd"В этой строке символ b стоит на 97 позиции, а символ a - на 98.
35 hashable
Ключом словаря или элементом множества может быть только hashable: объект, у которого есть не меняющийся со временем хэш. При модификации изменяемых объектов (списки, множества, словари) меняется и их хэш, следовательно, они не являются hashable. Именно поэтому нельзя создать множество множеств. Для решения этой проблемы было придумано неизменяемое множество frozenset:
{ set() } # TypeError: unhashable type: 'set' { frozenset() } # OKАналогично, в качестве неизменяемых списков можно использовать кортежи:
{ list() } # TypeError: unhashable type: 'list' { tuple() } # OK
36 pass
В Python принято писать pass в местах, где синтаксис требует какое-то выражение, но писать нечего. Вместо pass можно написать любое другое выражение, например 0:
try: ... except: 0 # подавляем все исключения
37 Используем побочные эффекты
Обычно map рассматривают как способ сгенерировать новую последовательность по уже имеющейся. Функция, передаваемая в map, рассматривается просто как некоторый конвертер из старого значения в новое. Однако давайте изменим смысл работы этой функции: пусть вместо генерации нового значения она будет выполнять некоторую полезную работу:
map(print, L) # выводит все элементы L (но не совсем)Тут мы сталкиваемся с проблемой ленивости map: так как мы не используем результат её вызова, она не проделывает ни одной итерации. Поэтому нам требуется явно проитерироваться по возвращаемому ей результату. Самый короткий способ сделать это - поискать в нём элемент, которого там заведомо нет:
0 in map(print, L) # выводит все элементы LПолученный код на 1 символ короче аналогичного цикла:
for x in L: print(x)
В предыдущем примере мы искали элемент, которого нет в коллекции. Однако искать элемент, который там есть, то мы получим итератор, который остановится сразу после искомого элемента:
z = iter(range(7)) # z - итератор на последовательность 0, 1, 2, 3, 4, 5, 6 3 in z # бежит по итератору, пока не встретится 3 print(*z) # выводит "4 5 6"
38 bytes
Подобно тому, как строки являются неизменяемой коллекцией односимвольных строк, bytes является неизменяемой коллекцией однобайтовых чисел:
l = b"golf" list(l) # [103, 111, 108, 102] l[2] # 108 108 in l # True max(l) # 111Иногда это можно использовать для краткой записи списка небольших чисел. Если числу не соответствует никакой ASCII-символ, то его можно записать с помощью экранирования, например для 128 это будет b'\x80' или b'\200'.
# первые десять чисел Фибоначчи s = [1,1,2,3,5,8,13,21,34,55] # 27 символов s = b'\1\1\2\3\5\b\r\25"7' # 24 символа
39 Неожиданный else
У циклов for и while есть блок else, который выполняется, если цикл завершился естественным образом, а не через break.
for x in L: # выводим первое положительное число в L if x > 0: print(x) break else: # либо -1, если таких нет print(-1)Также блок else есть у инструкции try: он исполняется, если исключение не возникло.
40 Глобальные переменные
При попытке присвоить значение глобальной переменной внутри функции просто создаётся локальная переменная с таким именем.
Если вы присваиваете какой-то переменной значение внутри функции, то оно всегда присваивается локальной переменной функции, даже если глобальная переменная с таким именем уже есть. Если вы хотите изменить именно глобальную переменную, то нужно явно об этом сообщить:
k = 5 def f(): global k k = 6 f() print(k) # выводит "6" # 31 символОднако обращаться к глобальным переменным (и даже модифицировать их, если они изменяемые) разумеется не запрещено, поэтому в некоторых специфических случаях короче использовать список из одного элемента вместо самого элемента:
k = [5] def f(): k[0] = 6 f() print(*k) # выводит "6" # 30 символов