#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'ы. В более вложенных конструкциях можно использовать переменные, определённые в более внешних (т.е. раньше по тексту).