Kodomo

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

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

Советы и подсказки к заданию 9

Подключение модулей

dir(math)

Смысл конкретной функции (например, exp из модуля math) можно посмотреть командой

help(math.exp)

Правда, с константами так не получается :(.

from math import sin, cos

...
a = cos(x) + sin(x)
...

Если же (как обычно в случае math), понадобится много чего, то пишете в программе так же, как и в интерактивном режиме:

import math

...
a = math.cos(x) + math.sin(x)
...

Кроме модуля math, вам понадобится модуль random. В модуле random имеется функция, которая возвращает случайный элемент списка. Найдите её сами с помощью dir и help. Если вам нужно случайное целое число из заданного диапазона, можно применить ту же функцию к результату встроенной функции range (хотя есть и другой способ: в модуле random имеется соответствующая функция).

Список доступных модулей см. https://docs.python.org/2/library/index.html .

Форматирование числа

Если переменная mynum имеет тип float, то выражение format(mynum, ".3f") имеет значение строки, выражающей mynum с тремя знаками после десятичной точки. Полное описание того, что может стоять в качестве второго аргумента встроенной функции format, см. здесь, но если вы не всё там поняли, сильно не расстраивайтесь :)

Есть два других способа форматирования чисел. Разделы %-форматирование и str.format() описывают их. Для зачёта их знать не обязательно.

Часто вам хочется не просто отформатировать какое-то число, а вставить значение переменной в строку на определенную позицию.

=== %-форматирование (дополнительная информация)===

Исторически первый способ, появившийся в Python. Просто ставим на то место, куда потом нужно будет вставить переменную, знак %, а после него — символ, обозначающий тип данной переменной:

После строки снова тавится знак %, а затем — кортеж из нужных переменных.

   1 my_beat_str = "String %s\nInteger %d\nFloat %f\nFloat with precision %.3f" % ("hi", 4, 5.789, 5.789)
   2 print my_beat_str
   3 #String hi
   4 #Integer 4
   5 #Float 5.789000
   6 #Float with precision 5.789

=== str.format() (дополнительная информация)===

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

   1 my_beat_str = "{} {} {} {:.1f}".format("hi", 4, 5.789, 5.789)
   2 print my_beat_str
   3 #hi 4 5.789 5.8
   4 my_beat_str = "{0} {1} {2} {2:.1f}".format("hi", 4, 5.789) # specify position of argument in format
   5 print my_beat_str
   6 #hi 4 5.789 5.8
   7 my_beat_str = "{string} {integer} {float} {float:.1f}".format(string="hi", integer=4, float=5.789) # pass as keywords
   8 print my_beat_str
   9 #hi 4 5.789 5.8

И это ещё не всё: в Python3 появился нагло позаимствованный из Perl четвертый способ форматирования вывода — https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep498 )

Запись информации в файл

Чтобы писать в файл, его нужно открыть на запись командой

outf = open("file.txt", "w")

Если открываемый файл существовал ранее, то его старое содержимое при этом исчезнет! После этого переменная outf имеет тип file и представляет собой "выходной поток".

(Как ещё можно открыть файл, см. в документации).

Писать в выходной поток (в нашем случае — в файл, открытый на запись) можно методом outf.write(строка). Внимание: метод write работает не так, как оператор print (поэкспериментируйте в интерактивном режиме, чтобы увидеть различия). В частности, print принимает любые значения, а write — только строки. Но и со строками write обращается несколько иначе.

Как при печати на консоль, так и при выводе в файл могут понадобиться байты "табулятор" (удобен для вывода таблиц) и "перенос строки" (без него вообще почти невозможно обойтись).

print str(a) + "\t" + str(b)

напечатает значения переменных a и b, разделённые табулятором.

Полезно привыкнуть сразу после окончания чтения из файла или записи в файл закрывать его методом close, например:

outf.close()

Дополнительная информация.

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

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

Если программа в середине работы упадет, то тем более никто не обещает, что в выходной файл запишется вся информация, которую программа всё же успела обработать.

Более того, часто в биоинформатике вам приходится писать программы, работающие долгое время, возможно, отвечая на тысячи задач. Если так случится, что вы забываете на каждый запрос закрыть файл, то в какой-то момент вы не сможете открыть новый файл и ваша программа завершится аварийно. По-умному такая ситуация называется "утечка файловых дескрипторов".

Способ избежать держания в (своей) памяти необходимости закрыть файл (дополнительная информация)

Для того, чтобы уменьшить число ошибок, связанных с незакрытием файлов (), в Python существует конструкция with ... as

   1 with open("input.file", "w") as in_file:
   2     in_file.write("Du Du hast Du hast mich\n")
   3     # do something and finally get error
   4 
   5 with open("input_file", "r") as in_file:
   6     text = in_file.read()
   7     print text # "Du Du hast Du hast mich\n"

Словари

Словарь (dict) – это тип данных, хранящий соответствие одних значений другим (ключам словаря). Чтобы получить значение из словаря, надо написать имя словаря и в квадратных скобках — ключ (выглядит это как вызов элемента списка по индексу)

Примеры (обратите внимание: в примере встречаются три типа скобок)

>>> da = {"a":5, "b":6, "c":7, "d":5}
>>> db = dict()
>>> db["a"] = 2
>>> db["b"] = 3
>>> print db
{'a': 2, 'b': 3}
>>> da["a"] + db["a"]
7

Значения словаря могут повторяться, ключи – нет.

Метод keys() возвращает список ключей словаря:

>>> db = dict()
>>> db["a"] = 2
>>> db["b"] = 3
>>> db.keys()
['a', 'b']

Ключом словаря может быть любое значение хэшируемого типа (в первом приближении – неизменяемое). К неизменяемым относятся типы int, float, bool, str, к изменяемым – list и сам dict. То есть словарь не может быть ключом словаря. Но словарь вполне может быть значением словаря, то есть ситуация вида x["a"]["b"] вполне допустима.

Проверка наличия значения в словаре

Если вы пытаетесь получить значение по ключу, которого нет в словаре, то вы получаете ошибку. Есть несколько способов избежать этого. Первый — проверить, а есть ли ключ в словаре.

Неправильный способ проверки — получить все ключи словаря методом keys и проверить наличие в них запрашиваемого. Специалисты считают этот способ крайне нежелательным.

Правильная проверка — для словарей, списков и упоминаемых далее кортежей и множеств есть конструкция "elem in <имя переменной>".

>>> db = {} # another way to create dict
>>> db['a'] = 5
>>> 'a' in db
True
>>> 'b' in db
False

Кроме in есть также not in, проверяющий отсутствие ключа в словаре.

Получение значения по умолчанию (дополнительная информация)

Иногда нам хочется получить значение для элемента в словаре, а если элемента в словаре нет, просто вернуть какое-нибудь заранее заданное значение. Для этого у словаря определен метод get, принимающий два параметра: возможный ключ и значение, возварщаемое, если ключа нет.

>>> db = {"a" : 4, "b" : 5}
>>> db.get("a", 0)
4
>>> db.get("c", 0)
0
>>> db["d"] = db.get("d", 0) + 1 # get value for key "d", increase it by one, if key is not in dictionary, assign 1

Проход по ключам словаря

Есть несколько способов прохода по словарю.

Неправильный способ — да обрушится кара Господня на того, кто так делает для большого словаря

   1 db = {"a" : 4, "b" : 5, "g" : 7}
   2 for key in db.keys():
   3     print key, db[key]

Рекомендуемые способы

   1 for key in db:
   2     print key, db[key]

или

   1 for key in db.iterkeys():
   2     print key, db[key]

Преимущества словарей

Получение значения по ключу и добавление нового ключа в словарь происходит значительно быстрее, чем если бы вам пришлось перебирать список в поисках нужного значения.

Вследствие этого есть огромный набор задач, где словари использовать можно и нужно.

Вместе со списками — возможность представить структуру данных любой сложности (дополнительная информация — json).

Недостатки

Словари "кушают" много памяти.

Все операции на словарях быстры в среднем — отдельная операция может длиться очень долго.

Совсем дополнительная информация про словари

В модуле collections есть несколько модификаций стандартного словаря, позволяющих делать некоторые действия значительно проще (к примеру, подсчет разных элементов в списке)

Ну и тем, кто интересуется, как все устроено со словарем — https://ru.wikipedia.org/wiki/Хеш-таблица

Кортежи

Если очень хочется сделать ключом словаря список, то нужно использовать не list, а другой тип данных — кортеж (tuple). Кортеж (на первый взгляд) отличается от списка только заменой квадратных скобок на круглые, например:

>>> ta = (1, 2, 5, 4)
>>> ta[0]
1
>>> ta[3]
4
>>> len(ta)
4

Но есть и различия, главное из которых – неизменяемость кортежа:

>>> la = [1, 2, 3, 4]
>>> la[0] = 7
>>> la
[7, 2, 3, 4]
>>> ta = (1, 2, 3, 4)
>>> ta[0] = 7
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Кортеж неизменяем и потому может быть ключом словаря.

Кроме того неплохо понимать, что операции доступа (получить элемент кортежа, срез и т.д.) выполняются для кортежа быстрее. Python может оптимизировать работу программы, в которой создается множество кортежей малой длины, что для списков недоступно. С другой стороны, любая операция редактирования кортежа приводит к созданию нового кортежа, что в больших количествах может замедлить работу программы и съесть память.

Множества

В некоторых случаях удобно пользоваться типом данных "множество" (set). Это именно множество, то есть его элементы не упорядочены и не могут повторяться (в отличие от списка и кортежа). На множествах определены операции объединения, пересечения и разности:

>>> s1 = set([1, 3, 5])
>>> s2 = {"a", "b", 1}
>>> s1 & s2
set([1])
>>> s1 | s2
set(['a', 1, 3, 5, 'b'])
>>> s1 - s2
set([3, 5])

Множества можно сравнивать:

>>> s1 < s2
False
>>> s1 < set([1, 3, 5, 7])
True

Множество — изменяемый тип (не может быть ключом словаря, но может быть значением словаря, что можно применить в задании 6). Изменяемость нужна, чтобы использовать удобные в некоторых ситуациях методы add, pop и remove (см. help(set.add) и т.п.).

Как и у списка (tuple вместо list) у множества есть неизменяемый вариант: frozenset. (Для самостоятельного изучения: разберитесь, какого типа получается результат, если операцию пересечения или объединения примерить к паре значений, одно из которых — типа set, а другое — frozenset).

PEP8

Создатели языка (и Google) имеют строгие рекомендации к написанию кода — как лучше писать его, какие действия приемлемы, а какие — нет.

Все это изложено в PEP8: https://www.python.org/dev/peps/pep-0008/

Основные требования к коду, которые вам нужно знать на данный момент (остальные узнаете от тестирующего сервиса).

Doc-string в начале программы

Программа на Python должна начинаться с комментария, объясняющего, что она делает. Желательно, чтобы в комментарии была информация о том, как эту программу запускать.

Названия переменных

Не стоит называть переменные, кроме индексов, одной буквой. Это мешает пониманию кода и скорее всего приведет к ошибкам.

Переменные типа cat1, cat2, cat3 и т.д хороши только в обучающих примерах.

Названия переменных должно содержать только маленькие буквы, цифры и символ подчеркивания "_" . Например, great_job — правильное название, GreatJob — неправильное название для переменной.

Названия констант (типа числа pi), должно писать большими заглавными буквами и _. THIS_VERY_IMPORTANT_CONSTANT

Все объявленные переменные должны использоваться. Вместо переменных, которые не используются, но их приходится писать, надо ставить "пустую переменную" '_'

   1 for _ in range(100):
   2     print "Hello"

Отступы

По PEP8 в качестве отступов надо использовать 4 пробела. Можно настроить ваш редактор так, чтобы он автоматически вставлял вместо нажатого таба 4 пробела.

Пустые строчки

Между участками кода допустима одна пустая линия, предназначенная для разделения смысловых частей программы. Между user-defined функциями, которые будут разобраны на следующим занятии, необходимо ставить две пустые строки.

Присваивание переменным значений другого типа

Так делать в Python можно технически, но чаще всего это неоправданно и вызывает ошибки, потому будет штрафоваться. То есть не стоит писать, например:

my_line = my_line.split()

чтобы "превратить строку в список"; для хранения списка надо завести другую переменную.

Проверка, является ли строка/список/словарь пустым

Многие уже поняли, что функция len возвращает длину объекта. В связи с этим возникает искушение написать что-то типа

   1 if len(items) != 0:
   2     do something

Однако вызов функции len оправдан только тогда, когда вам нужна именно длина, а не проверка на пустоту (например, нужно проверить, что в вашем списке меньше пяти элементов). Для тех же случаев, когда нужна проверка объекта на пустоту, достаточно написать:

   1 if items:
   2     do something

или

   1 if not items:
   2     do something

Дело в том, что в Python списки, словари, строки и некоторые другие объекты при их наличии в условии автоматически конвертируются в False, если они пустые, и в True, если они непустые.

Кроме просто вопроса эстетики, использование такого синтаксиса работает быстрее. Подробнее — здесь: https://stackoverflow.com/questions/43121340/why-is-the-use-of-lensequence-in-condition-values-considered-incorrect-by-pyli