Kodomo

Пользователь

Учебная страница курса биоинформатики,
год поступления 2015

Форматирование выдачи

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

Работает он так: вы пишете строку с вашим текстом и описанием того, куда и как вставить в неё ваши данные (эта строка называется шаблоном), и вызываете для этой строки метод format, которой даёте вставляемые данные в качестве аргументов.

Место, куда нужно вставить ваши данные, обозначается в шаблоне знаком {0}.

Пример:

   1 >>> who = "world"
   2 >>> print "Hello, {0}!".format(who)
   3 "Hello, world!"

Описания бывают вида:

Примеры:

   1 >>> print 'Name: {0:5}, Surname: {1:5}'.format('w', 'orld')
   2 Name: w    , Surname: orld 
   3 >>> print "4 + 5 = {0:05.3f}!".format(9)
   4 4 + 5 = 9.000!

Кроме того, методу format можно передавать параметры по имени, и тогда по имени же к ним нужно будет и обращаться из шаблона. Это бывает очень удобно, особенно, если выводимый текст длинный или наоборот, пестрит пунктуацией:

   1 >>> message = '{filename}:{line_no}: {error_text}'
   2 >>> print message.format(line_no=10, filename="hello.txt", error="No error")
   3 hello.txt:10: No error

Возможности указания форматов в этом месте очень велики и мы не будем всё охватывать в наших лекциях, но отошлём вас к документации.

Параметры командной строки.

Когда мы вызываем программу через командную строку, мы можем также указывать ее параметры (если они есть).

Например, вспомним команду ls. Ее можно вызвать с различными параметрами.

ls -lR
ls --long --recursive
ls -l -R

Все три записи выше равнозначны между собой.

ls --sort=time # параметры могут принимать какое-то значение
ls a.txt b.txt

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

В переменной sys.argv содержится список строк - все то, что было написано в командной строке (разделенное по пробелам).

Например, если мы напишем маленькую программку test_argv.py:

   1 import sys
   2 print sys.argv

И запустим ее с какими-нибудь параметрами, то получится так:

   1 $ python test_argv.py file.txt -r -w 5
   2 ['test_argv.py', 'file.txt', '-r', '-w', '5']

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

   1 import argparse
   2 parser=argparse.ArgumentParser()

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

это будет эквиваленто:

   1 def f(x):
   2     return 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

Можно еще подсократить (но читаемость кода от этого несколько страдает):

   1 >>> add_lambda = lambda a, b: a + b
   2 >>> add_lambda(3,6)
   3 9

# а теперь просто подставим лямбду туда, где хотим ее использовать:

   1 >>> (lambda a, b: a + b)(3,6)  #конструкция (lambda ... : ...) "на лету" превращается в функцию, которую можно вызывать как обычную функцию, "со скобочками"
   2 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 список ] и значить это будет следующее: для каждого элемента списка, указать на него этой переменной и вычислить в этом контексте выражение, собрать все результаты таких вычислений и положить снова в список.

   1 >>> [ x ** 2 for x in [1, 2, 3]]
   2 [1, 4, 9]

Эта конструкция эквивалентна трём строкам питонского кода:

   1 result = []
   2 for x in [1, 2, 3]:
   3         result.append(x ** 2)

Знакомая конструкция, не правда ли? Теперь мы знаем, как это писать короче! Сразу несколько примеров применения. Мы хотим разобрать csv файл, в котором записаны числа. Подход первый, самый старый:

   1 def parse_numbers(file):
   2         result = []
   3         for line in file:
   4                 numbers = []
   5                 for word in line.split(","):
   6                         numbers.append(int(word))
   7                 result.append(numbers)
   8 
   9         return(result)
  10 
  11 with open("data.csv") as f:
  12     res=parse_numbers(f)
  13     print(res)

Тут мы сразу видим, что внутренний цикл можно представить в виде "выделения списка":

   1 def parse_numbers(file):
   2         result = []
   3         for line in file:
   4                 numbers = [int(word) for word in line.split(",")]
   5                 result.append(numbers)
   6         return result

И снова упираемся в точно такую же конструкцию. Значит, и её можно свернуть:

   1 def parse_numbers(file):
   2         return [[int(word) for word in line.split(",")] for line in file]

Нельзя назвать эту запись самой очевидной, но зато она короче исходной в 4 раза (или вообще в 7 раз, смотря как считать). Ещё один пример, связанный с разбором форматов. Предположим, мы хотим разобрать строку, в которой записан список присваиваний значений ключ=значение (и мы сразу требуем, чтобы внутри значений не было пробелов, и если значения повторяются, то мы будем использовать последнее). И, разумеется, мы хотим такую строку превратить в словарь. Что может быть проще!

   1 def parse_keys(line):
   2         return dict([word.split('=', 1) for word in line.split()])

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: мы можем ходить не по одному списку, а по нескольким. Это будет эквивалентно нескольким вложенным циклам. Пример:

   1 >>> [a + str(b) for a in ["a", "b"] for b in range(3)]
   2 ['a0', 'a1', 'a2', 'b0', 'b1', 'b2']

То есть эта конструкция эквивалентна четырём строкам:

   1 result = []
   2 for a in ["a", "b"]:
   3         for b in range(3):
   4                 result.append(a + str(b))

Способ запомнить, в каком порядке случается обход: если в list comprehension воткнуть переносов строк, отступов и двоеточий, и убрать немного лишнего, то получатся вложенные циклы, обходящие списки ровно в том же порядке. Иными словами: первый for внешний (меняется реже всего), внутри него второй for (пробегает все значения для каждой итерации первого), внутри него третий, и так далее. Наконец, в list comprehensions есть и ещё одна вещь, которую можно вставлять: проверки. Синтаксис столь же простой, как и раньше:

   1 squares = [ x*x if x > 2 else -x for x in range(1,6) ]  

Эквивалентно:

   1 squares = []
   2 for x in range(1,6):
   3     if x > 2:
   4         squares.append(x*x)
   5     else:
   6         squares.append(-x)

Надеюсь, в этом месте не нужно никаких больше пояснений. Как и for, if'ы можно добавлять в любом количестве, и их можно чередовать. Эффект будет таким же, как ровно в той же последовательности записанные вложенные for'ы и if'ы. В более вложенных конструкциях можно использовать переменные, определённые в более внешних (т.е. раньше по тексту).