#pragma css /css/2015.css <<BI>> == Форматирование выдачи == Когда мы пишем программу, которая собирает какие-то данные, возникает потребность во-первых как-то перемежать выходные данные с пояснениями, во-вторых, при выводе чисел, форматировать их каким-нибудь специальным образом -- с десятичной точкой или без, отводить под число какое-то число символов, писать его с нулями или пробелами вначале или с пробелами в конце и т.п. Каждую такую задачу можно решить самостоятельно, но это довольно частая потребность, а каждый раз решать одну и ту же задачу нудно и неинтересно. Поэтому в питоне для этого есть готовое средство: метод format у строк. Работает он так: вы пишете строку с вашим текстом и описанием того, куда и как вставить в неё ваши данные (эта строка называется шаблоном), и вызываете для этой строки метод format, которой даёте вставляемые данные в качестве аргументов. Место, куда нужно вставить ваши данные, обозначается в шаблоне знаком `{0}`. Пример: {{{#!python >>> who = "world" >>> print "Hello, {0}!".format(who) "Hello, world!" }}} Описания бывают вида: * `{0}` .. `{N}` -- вставить сюда аргумент номер ''N'' метода format * `{0:5}` -- оставить под этот текст как минимум 5 символов -- это полезно для табличной выдачи * `{0:05.3f}` -- выводить как число с плавающей точкой в формате через точку, выводить суммарно не менее 5 символов, из них не менее 3 символов после точки, свободное пространство слева заполнять нулями Примеры: {{{#!python >>> print 'Name: {0:5}, Surname: {1:5}'.format('w', 'orld') Name: w , Surname: orld >>> print "4 + 5 = {0:05.3f}!".format(9) 4 + 5 = 9.000! }}} Кроме того, методу `format` можно передавать параметры по имени, и тогда по имени же к ним нужно будет и обращаться из шаблона. Это бывает очень удобно, особенно, если выводимый текст длинный или наоборот, пестрит пунктуацией: {{{#!python >>> message = '{filename}:{line_no}: {error_text}' >>> print message.format(line_no=10, filename="hello.txt", error="No error") hello.txt:10: No error }}} Возможности указания форматов в этом месте очень велики и мы не будем всё охватывать в наших лекциях, но отошлём вас к [[http://docs.python.org/library/string.html#formatstrings|документации]]. = Параметры командной строки. = Когда мы вызываем программу через командную строку, мы можем также указывать ее параметры (если они есть). Например, вспомним команду ls. Ее можно вызвать с различными параметрами. {{{ ls -lR ls --long --recursive ls -l -R }}} Все три записи выше равнозначны между собой. {{{ ls --sort=time # параметры могут принимать какое-то значение ls a.txt b.txt }}} Если параметр написан через знак дефиса, то он называется именованным, иначе – позиционным. В переменной sys.argv содержится список строк - все то, что было написано в командной строке (разделенное по пробелам). Например, если мы напишем маленькую программку test_argv.py: {{{#!python import sys print sys.argv }}} И запустим ее с какими-нибудь параметрами, то получится так: {{{#!python $ python test_argv.py file.txt -r -w 5 ['test_argv.py', 'file.txt', '-r', '-w', '5'] }}} Для того, чтобы автоматизировать разбор командной строки подобно программе ls, надо импортировать библиотеку argparse. {{{#!python import argparse parser=argparse.ArgumentParser() }}} parser - это так называемый объект-разборщик (далее будем называть его просто парсером). Разберем это на примере программы сортировки (пусть будет называться sort.py). Пусть для нее можно будет указывать три параметра: {{{ -r --reverse -w --word -a --sort-alg }}} Для этого надо прописать: {{{#!python import argparse import sys parser=argparse.ArgumentParser() parser.add_argument('-w', '--word', help='word number to sort by', type=int) parser.add_argument('-r', '--reverse', action='store_true', help='reverse sort order') parser.add_argument('-a', '--sort-alg', dest='alg', help='sorting algorithm; can be "qsort" (qsort) or "merge" (merge-sort)', type=str, default='qsort') options=parser.parse_args() print(options) print(options.word) print(options.alg)# значение положилось в options.alg, как это указано в 'dest' print(options.reverse) 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'): {{{#!python import argparse import sys parser=argparse.ArgumentParser() parser.add_argument('-w', '--word', help='word number to sort by', type=int) parser.add_argument('-r', '--reverse', action='store_true', help='reverse sort order') parser.add_argument('-a', '--sort-alg', dest='alg', help='sorting algorithm; can be "qsort" (qsort) or "merge" (merge-sort)', type=str, default='qsort') options=parser.parse_args() print(options.word) if not options.word: 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, а потом использовать ее в коде. Но если вам нужно на лету в одном конкретном месте сделать несложное превращение - лямбды очень удобны. Чтобы разобраться, как они устроены, определим лямбда-функцию возведения в квадрат: {{{#!python f = lambda x: x*x }}} это будет эквиваленто: {{{#!python def f(x): return x*x }}} слово ''def'' не используется, вместо него - знак равенства и слово ''lambda''; параметры лямбда-функции указываются не в скобках, как вы привыкли, а после слова ''lambda'', перед двоеточием (если переменных несколько - они разделяются запятыми, также без скобок); возвращаемое значение указывается без слова return после двоеточия Еще пример (следующие функции идентичны): {{{#!python def add(a, b): c = a + b return c def add_short(a, b): return a + b add_lambda = lambda a, b: a + b # обратите внимание! add_lambda - это не переменная, а имя функции >>> 3+6 9 >>> add(3,6) 9 >>> add_short(3,6) 9 >>> add_lambda(3,6) # и вызывается со скобочками, как обычная функция, хотя в определении функции выше скобок не было 9 }}} Можно еще подсократить (но читаемость кода от этого несколько страдает): {{{#!python >>> add_lambda = lambda a, b: a + b >>> add_lambda(3,6) 9 }}} # а теперь просто подставим лямбду туда, где хотим ее использовать: {{{#!python >>> (lambda a, b: a + b)(3,6) #конструкция (lambda ... : ...) "на лету" превращается в функцию, которую можно вызывать как обычную функцию, "со скобочками" 9 }}} Но реальная польза от лямбд есть - это их использование в функции sorted(): {{{#!python >>> d = {1:1, 2:3, 5:2} >>> d_items = d.items() >>> d_items [(1,1),(2,3),(5,2)] # чтобы сортировать по второму элементу кортежа создадим лямбда-функцию, которая умеет брать второй элемент кортежа: >>> second = lambda x: x[1] # эквивалентно def second(x): return x[1] >>> second( (1,2) ) 2 # и теперь сортируем с помощью параметра key функции sorted: >>> sorted(d_items, key = second) [(1,1),(5,2),(2,3)] # новый список сортирован по второму элементу каждого кортежа # так можно превратить элементы словаря в список, сортированный по значениям словаря # как мы уже знаем, лямбду можно не определять заранее, а подставить сразу в место использования: >>> sorted(d.items(), key = lambda x: x[1]) [(1,1),(5,2),(2,3)] # но помните, что такая лямбда не проверяет, что у x есть элемент [1] - для словаря это не страшно, т.к. все элементы d.items() - всегда кортежи; но в общем случае - это ваша задача написать такую лямбду, которая учитывает особенности данных, иначе будете падать с исключениями # для этого можно использовать inline if (см. далее): # сортировка элементов словаря по абсолютной величине значения: >>> d = {1:1, 2:3, 5:-2} >>> sorted(d.items(), key = lambda x: x[1] if x[1] >= 0 else -x[1]) [(1,1),(5,-2),(2,3)] # при этом исходные значения сохраняются }}} = List comprehensions (списочные сокращения) = Это небольшая темка про то, как можно одновременно ужать свой код и при этом сделать его более понятным. Однако синтаксическая конструкция, позволяющая это делать, позволяет также и сильно усложнить код. Поэтому сразу хочу призвать вас чувствовать меру в использовании списочные сокращения (list comprehensions) и использовать их только по делу. Итак, в питоне можно писать такое: [ выражение for переменная in список ] и значить это будет следующее: для каждого элемента списка, указать на него этой переменной и вычислить в этом контексте выражение, собрать все результаты таких вычислений и положить снова в список. {{{#!python >>> [ x ** 2 for x in [1, 2, 3]] [1, 4, 9] }}} Эта конструкция эквивалентна трём строкам питонского кода: {{{#!python result = [] for x in [1, 2, 3]: result.append(x ** 2) }}} Знакомая конструкция, не правда ли? Теперь мы знаем, как это писать короче! Сразу несколько примеров применения. Мы хотим разобрать csv файл, в котором записаны числа. Подход первый, самый старый: {{{#!python def parse_numbers(file): result = [] for line in file: numbers = [] for word in line.split(","): numbers.append(int(word)) result.append(numbers) return(result) with open("data.csv") as f: res=parse_numbers(f) print(res) }}} Тут мы сразу видим, что внутренний цикл можно представить в виде "выделения списка": {{{#!python def parse_numbers(file): result = [] for line in file: numbers = [int(word) for word in line.split(",")] result.append(numbers) return result }}} И снова упираемся в точно такую же конструкцию. Значит, и её можно свернуть: {{{#!python def parse_numbers(file): return [[int(word) for word in line.split(",")] for line in file] }}} Нельзя назвать эту запись самой очевидной, но зато она короче исходной в 4 раза (или вообще в 7 раз, смотря как считать). Ещё один пример, связанный с разбором форматов. Предположим, мы хотим разобрать строку, в которой записан список присваиваний значений ключ=значение (и мы сразу требуем, чтобы внутри значений не было пробелов, и если значения повторяются, то мы будем использовать последнее). И, разумеется, мы хотим такую строку превратить в словарь. Что может быть проще! {{{#!python def parse_keys(line): return dict([word.split('=', 1) for word in line.split()]) }}} ''line.split()'' – превратили строку в список слов. ''word.split("=", 1)'' – разбили слово по первому вхождению "=" (если никакого равенства в слове нет, нам вернётся список из одного элемента, и это вызовет ошибку, что хорошо; если есть больше одного равенства, то мы предполагаем, что второе равенство – это часть текста значения; ''split(..., 1)'' возвращает список длины не более 2). Полученный список (списков длины 2) превращаем в словарь и возвращаем. А еще словари можно создавать по списку ключей с помощью списочных сокращений: {{{#!python >>> squares_list = [ x*x for x in range(1,6) ] >>> squares_list [1,4,9,16,25] >>> squares_dict = { x: x*x for x in range(1,6) } # генерируем словарь так же, как и список, только указываем "ключ:значение", и используем фигурные скобки вместо квадратных >>> squares_dict {1: 1, 2: 4, 3: 9, 4: 16, 5: 25} }}} Следующий элемент сложности, который мы можем внести в list comprehensions: мы можем ходить не по одному списку, а по нескольким. Это будет эквивалентно нескольким вложенным циклам. Пример: {{{#!python >>> [a + str(b) for a in ["a", "b"] for b in range(3)] ['a0', 'a1', 'a2', 'b0', 'b1', 'b2'] }}} То есть эта конструкция эквивалентна четырём строкам: {{{#!python result = [] for a in ["a", "b"]: for b in range(3): result.append(a + str(b)) }}} Способ запомнить, в каком порядке случается обход: если в list comprehension воткнуть переносов строк, отступов и двоеточий, и убрать немного лишнего, то получатся вложенные циклы, обходящие списки ровно в том же порядке. Иными словами: первый for внешний (меняется реже всего), внутри него второй for (пробегает все значения для каждой итерации первого), внутри него третий, и так далее. Наконец, в list comprehensions есть и ещё одна вещь, которую можно вставлять: проверки. Синтаксис столь же простой, как и раньше: {{{#!python squares = [ x*x if x > 2 else -x for x in range(1,6) ] }}} Эквивалентно: {{{#!python squares = [] for x in range(1,6): if x > 2: squares.append(x*x) else: squares.append(-x) }}} Надеюсь, в этом месте не нужно никаких больше пояснений. Как и for, if'ы можно добавлять в любом количестве, и их можно чередовать. Эффект будет таким же, как ровно в той же последовательности записанные вложенные for'ы и if'ы. В более вложенных конструкциях можно использовать переменные, определённые в более внешних (т.е. раньше по тексту).