Форматирование выдачи
Когда мы пишем программу, которая собирает какие-то данные, возникает потребность во-первых как-то перемежать выходные данные с пояснениями, во-вторых, при выводе чисел, форматировать их каким-нибудь специальным образом – с десятичной точкой или без, отводить под число какое-то число символов, писать его с нулями или пробелами вначале или с пробелами в конце и т.п. Каждую такую задачу можно решить самостоятельно, но это довольно частая потребность, а каждый раз решать одну и ту же задачу нудно и неинтересно. Поэтому в питоне для этого есть готовое средство: метод format у строк.
Работает он так: вы пишете строку с вашим текстом и описанием того, куда и как вставить в неё ваши данные (эта строка называется шаблоном), и вызываете для этой строки метод format, которой даёте вставляемые данные в качестве аргументов.
Место, куда нужно вставить ваши данные, обозначается в шаблоне знаком {0}.
Пример:
Описания бывают вида:
{0} .. {N} – вставить сюда аргумент номер N метода format
{0:5} – оставить под этот текст как минимум 5 символов – это полезно для табличной выдачи
{0:05.3f} – выводить как число с плавающей точкой в формате через точку, выводить суммарно не менее 5 символов, из них не менее 3 символов после точки, свободное пространство слева заполнять нулями
Примеры:
Кроме того, методу format можно передавать параметры по имени, и тогда по имени же к ним нужно будет и обращаться из шаблона. Это бывает очень удобно, особенно, если выводимый текст длинный или наоборот, пестрит пунктуацией:
Возможности указания форматов в этом месте очень велики и мы не будем всё охватывать в наших лекциях, но отошлём вас к документации.
Параметры командной строки.
Когда мы вызываем программу через командную строку, мы можем также указывать ее параметры (если они есть).
Например, вспомним команду ls. Ее можно вызвать с различными параметрами.
ls -lR ls --long --recursive ls -l -R
Все три записи выше равнозначны между собой.
ls --sort=time # параметры могут принимать какое-то значение ls a.txt b.txt
Если параметр написан через знак дефиса, то он называется именованным, иначе – позиционным.
В переменной sys.argv содержится список строк - все то, что было написано в командной строке (разделенное по пробелам).
Например, если мы напишем маленькую программку test_argv.py:
И запустим ее с какими-нибудь параметрами, то получится так:
Для того, чтобы автоматизировать разбор командной строки подобно программе ls, надо импортировать библиотеку argparse.
parser - это так называемый объект-разборщик (далее будем называть его просто парсером).
Разберем это на примере программы сортировки (пусть будет называться sort.py).
Пусть для нее можно будет указывать три параметра:
-r --reverse -w --word -a --sort-alg
Для этого надо прописать:
1 import argparse
2 import sys
3 parser=argparse.ArgumentParser()
4 parser.add_argument('-w', '--word', help='word number to sort by', type=int)
5 parser.add_argument('-r', '--reverse', action='store_true', help='reverse sort order')
6 parser.add_argument('-a', '--sort-alg', dest='alg', help='sorting algorithm; can be "qsort" (qsort) or "merge" (merge-sort)', type=str, default='qsort')
7 options=parser.parse_args()
8 print(options)
9 print(options.word)
10 print(options.alg)# значение положилось в options.alg, как это указано в 'dest'
11 print(options.reverse)
12 print sys.argv
Итак, парсер будет разбирать слово, стоящее после записи '-w' ('--word'), '-a' ('--sort-alg') или '-r' ('--reverse'). Для '-w' оно должно быть целым числом, для '-a' - строкой и по умолчанию равно 'qsort'.
Фраза action='store_true' означает, что данный параметр является флагом. Если бы этого не было, то параметр нёс бы какое-то значение.
Рассмотрим диалог с командной строкой:
$ python sort.py Namespace(alg='qsort', reverse=False, word=None) None qsort False ['test1.py'] $ python sort.py --help Usage: sort.py [options] Options: -h, --help show this help message and exit -w WORD, --word=WORD word number to sort by -r, --reverse reverse sort order -a ALG, --sort-alg=ALG sorting algorithm; can be "qsort" (qsort) or "merge" (merge-sort) $ python sort.py -x sort.py [options] sort.py: error: unrecognized arguments: -x
Мы можем захотеть как-то реагировать на отсутствие значения для обязательного параметра (например, параметра '-w'):
1 import argparse
2 import sys
3 parser=argparse.ArgumentParser()
4 parser.add_argument('-w', '--word', help='word number to sort by', type=int)
5 parser.add_argument('-r', '--reverse', action='store_true', help='reverse sort order')
6 parser.add_argument('-a', '--sort-alg', dest='alg', help='sorting algorithm; can be "qsort" (qsort) or "merge" (merge-sort)', type=str, default='qsort')
7 options=parser.parse_args()
8 print(options.word)
9 if not options.word:
10 parser.error('Word number is not given')
Тогда диалог с командной строкой будет выглядеть так:
$ python sort.py None Usage: sort.py [options] sort.py: error: Word number is not given $ python sort.py -w 3 3
Лямбда-функции
Лямбда-функции - это (упрощенно) такие функции, которые записаны в одну строку, и определены не с помощью ключевого слова def отдельно, а прямо в месте применения. Если вы используете какую-то функцию многократно, то лямбда-функция для нее вам, конечно, не нужна - лучше (читабельнее) определить эту функцию один раз с помощью def, а потом использовать ее в коде. Но если вам нужно на лету в одном конкретном месте сделать несложное превращение - лямбды очень удобны. Чтобы разобраться, как они устроены, определим лямбда-функцию возведения в квадрат:
1 f = lambda x: x*x
это будет эквиваленто:
слово def не используется, вместо него - знак равенства и слово lambda; параметры лямбда-функции указываются не в скобках, как вы привыкли, а после слова lambda, перед двоеточием (если переменных несколько - они разделяются запятыми, также без скобок); возвращаемое значение указывается без слова return после двоеточия Еще пример (следующие функции идентичны):
1 def add(a, b):
2 c = a + b
3 return c
4
5 def add_short(a, b):
6 return a + b
7
8 add_lambda = lambda a, b: a + b # обратите внимание! add_lambda - это не переменная, а имя функции
9
10 >>> 3+6
11 9
12 >>> add(3,6)
13 9
14 >>> add_short(3,6)
15 9
16 >>> add_lambda(3,6) # и вызывается со скобочками, как обычная функция, хотя в определении функции выше скобок не было
17 9
Можно еще подсократить (но читаемость кода от этого несколько страдает):
# а теперь просто подставим лямбду туда, где хотим ее использовать:
Но реальная польза от лямбд есть - это их использование в функции sorted():
1 >>> d = {1:1, 2:3, 5:2}
2 >>> d_items = d.items()
3 >>> d_items
4 [(1,1),(2,3),(5,2)]
5 # чтобы сортировать по второму элементу кортежа создадим лямбда-функцию, которая умеет брать второй элемент кортежа:
6 >>> second = lambda x: x[1] # эквивалентно def second(x): return x[1]
7 >>> second( (1,2) )
8 2
9 # и теперь сортируем с помощью параметра key функции sorted:
10 >>> sorted(d_items, key = second)
11 [(1,1),(5,2),(2,3)] # новый список сортирован по второму элементу каждого кортежа
12
13 # так можно превратить элементы словаря в список, сортированный по значениям словаря
14 # как мы уже знаем, лямбду можно не определять заранее, а подставить сразу в место использования:
15 >>> sorted(d.items(), key = lambda x: x[1])
16 [(1,1),(5,2),(2,3)]
17 # но помните, что такая лямбда не проверяет, что у x есть элемент [1] - для словаря это не страшно, т.к. все элементы d.items() - всегда кортежи; но в общем случае - это ваша задача написать такую лямбду, которая учитывает особенности данных, иначе будете падать с исключениями
18 # для этого можно использовать inline if (см. далее):
19
20 # сортировка элементов словаря по абсолютной величине значения:
21 >>> d = {1:1, 2:3, 5:-2}
22 >>> sorted(d.items(), key = lambda x: x[1] if x[1] >= 0 else -x[1])
23 [(1,1),(5,-2),(2,3)] # при этом исходные значения сохраняются
List comprehensions (списочные сокращения)
Это небольшая темка про то, как можно одновременно ужать свой код и при этом сделать его более понятным. Однако синтаксическая конструкция, позволяющая это делать, позволяет также и сильно усложнить код. Поэтому сразу хочу призвать вас чувствовать меру в использовании списочные сокращения (list comprehensions) и использовать их только по делу. Итак, в питоне можно писать такое: [ выражение for переменная in список ] и значить это будет следующее: для каждого элемента списка, указать на него этой переменной и вычислить в этом контексте выражение, собрать все результаты таких вычислений и положить снова в список.
Эта конструкция эквивалентна трём строкам питонского кода:
Знакомая конструкция, не правда ли? Теперь мы знаем, как это писать короче! Сразу несколько примеров применения. Мы хотим разобрать csv файл, в котором записаны числа. Подход первый, самый старый:
Тут мы сразу видим, что внутренний цикл можно представить в виде "выделения списка":
И снова упираемся в точно такую же конструкцию. Значит, и её можно свернуть:
Нельзя назвать эту запись самой очевидной, но зато она короче исходной в 4 раза (или вообще в 7 раз, смотря как считать). Ещё один пример, связанный с разбором форматов. Предположим, мы хотим разобрать строку, в которой записан список присваиваний значений ключ=значение (и мы сразу требуем, чтобы внутри значений не было пробелов, и если значения повторяются, то мы будем использовать последнее). И, разумеется, мы хотим такую строку превратить в словарь. Что может быть проще!
line.split() – превратили строку в список слов. word.split("=", 1) – разбили слово по первому вхождению "=" (если никакого равенства в слове нет, нам вернётся список из одного элемента, и это вызовет ошибку, что хорошо; если есть больше одного равенства, то мы предполагаем, что второе равенство – это часть текста значения; split(..., 1) возвращает список длины не более 2). Полученный список (списков длины 2) превращаем в словарь и возвращаем. А еще словари можно создавать по списку ключей с помощью списочных сокращений:
1 >>> squares_list = [ x*x for x in range(1,6) ]
2 >>> squares_list
3 [1,4,9,16,25]
4 >>> squares_dict = { x: x*x for x in range(1,6) } # генерируем словарь так же, как и список, только указываем "ключ:значение", и используем фигурные скобки вместо квадратных
5 >>> squares_dict
6 {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Следующий элемент сложности, который мы можем внести в list comprehensions: мы можем ходить не по одному списку, а по нескольким. Это будет эквивалентно нескольким вложенным циклам. Пример:
То есть эта конструкция эквивалентна четырём строкам:
Способ запомнить, в каком порядке случается обход: если в list comprehension воткнуть переносов строк, отступов и двоеточий, и убрать немного лишнего, то получатся вложенные циклы, обходящие списки ровно в том же порядке. Иными словами: первый for внешний (меняется реже всего), внутри него второй for (пробегает все значения для каждой итерации первого), внутри него третий, и так далее. Наконец, в list comprehensions есть и ещё одна вещь, которую можно вставлять: проверки. Синтаксис столь же простой, как и раньше:
1 squares = [ x*x if x > 2 else -x for x in range(1,6) ]
Эквивалентно:
Надеюсь, в этом месте не нужно никаких больше пояснений. Как и for, if'ы можно добавлять в любом количестве, и их можно чередовать. Эффект будет таким же, как ровно в той же последовательности записанные вложенные for'ы и if'ы. В более вложенных конструкциях можно использовать переменные, определённые в более внешних (т.е. раньше по тексту).