Kodomo

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

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

Указания к заданию 10

Функции

Собственная функция объявляется ключевым словом def, после которого следует имя функции с перечисленными в скобках аргументами.

В конце функции может следовать ключевое слово return, после которого следует, что возвращает функция.

   1 def naive_copy_slice(lst):
   2    new_lst = []
   3    for elem in lst:
   4       new_lst.append(elem)
   5    return new_lst

Если после return ничего нет или оно вообще опущено, то считается, что функция возвращает специальное значение None.

   1 def lazy_func():
   2     return
   3 
   4 print lazy_func() # None

Если после return идет перечисление переменных, то функция возвращает кортеж из них

   1 def count_2_and_5(lst):
   2    num_2 = 0
   3    num_5 = 0
   4    for elem in lst:
   5       if elem == 2:
   6           num_2 += 1
   7       elif elem == 5:
   8           num_5 += 1
   9 
  10    return num_2, num_5
  11 
  12 print count_2_and_5([2,3,4,5,5,5]) # (1, 3)

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

   1 def count(n=5, phrase="Count"):
   2    for i in range(n):
   3        print phrase
   4 
   5 count(2, "Hi") # Hi\nHi\n
   6 count(n=2, phrase="Hi") # Hi\nHi\n
   7 count(n=3) # "Count\nCount\nCount\n"
   8 count(3) # "Count\nCount\nCount\n"
   9 count() # "Count\nCount\nCount\nCount\nCount\n"
  10 #count("Count") # так работать не будет, т.к Python посчитает, что вы передали ему n
  11 count(phrase="Hi") # Hi\nHi\nHi\nHi\nHi\n

Если вы объявите две функции с одинаковыми именами, то вызываться будет последняя объявленная.

   1 def hello():
   2    print "Hello"
   3 
   4 hello() # "Hello\n"
   5 
   6 def hello():
   7    print "Hi"
   8 
   9 hello() # "Hi\n"

Рекурсия

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

Пример — рекурсивная версия функции count.

   1 def count(n=5, phrase="Count"):
   2     if n > 0:
   3        print phrase
   4        return count(n=n-1, phrase=phrase)
   5     n -= 1
   6     if n <= 0:
   7        return

Собственный модуль

Собственный модуль может представлять собой файл с расширением .py или директорию, содержащую файлы .py и поддиректории (в случае с директорией такой модуль надо оформлять определенным образом; здесь мы не будем рассматривать, как это делать).

Собственный модуль подключается так же, как библиотека. Для этого он должен находиться в одной директории с вашим скриптом. Если нужные вам функции fun1 и fun2 описаны в файле mymodule.py,

   1 #mymodule.py
   2 def fun1(b):
   3     print "Hi_" + str(b) + "_fun1"
   4 
   5 def fun2(b):
   6     print "Hi_" + str(b) + "_fun2"

то пишете:

   1 import mymodule
   2 
   3 mymodule.fun1(b)
   4 mymodule.fun2(a)

или

   1 from mymodule import fun1, fun2
   2 
   3 fun1(b)
   4 fun2(a)

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

К примеру:

   1 #hellomodule.py
   2 
   3 print "Hello, I've been imported"

   1 #main.py
   2 import hellomodule
   3 
   4 print "Main script"

При запуске main.py выведется

"Hello, I've been imported"
"Main script"

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

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

    if __name__ == "__main__":

   1 def func1():
   2     return 1
   3 
   4 def func2():
   5     return 2
   6 
   7 def main():
   8     print func1()
   9     print func2()
  10 
  11 if __name__ == "__main__":
  12    main()

Примечание (С.А.С.). Думаю, что использовать этот приём начинающим не надо. На мой взгляд, необходимо разделять модули с функциями, которые могут понадобиться в нескольких программах, и файлы с собственно программами (которые могут импортировать "многоразовые" функции или содержать какие-то "одноразовые" функции). В первых не должно быть "main"'а, разве что с печатью справки о содержании модуля, да и то, для такой справки есть более каноническое место.

Обработка ошибок в Python. Exception

Во время выполнения программы может возникнуть непредвиденный случай — "Exception". Если это никак не предусмотрено программистом, то программа, выдав исключение, аварийно завершает работу. Иногда данный сценарий для нас неприемлем: допустим, мы обрабатываем независимые файлы одной программой и время обработки каждого файла достаточно велико. В таком случае мы заинтересованы в том, чтобы программа в случае ошибки (например, отсутствие какого-то файла), не "падала", а продолжала работу с другими файлами, выдав предупреждение пользователю в консоль.

Для данной задачи в Python существует конструкция try .. except

   1 try:
   2    #do some code
   3 except Exception:
   4     printError occured

Кроме того, в except можно указывать, что именно мы хотим перехватить — например, у нас возникает ошибка ValueError. Тогда Вместо Exception указываем именно ValueError.

   1 try:
   2    #do some code
   3 except ValueError:
   4     printValue error occurred

А если хотим обрабатывать ValueError одним способом, а остальные исключения — другим? Тогда можно повторить except несколько раз:

   1 try:
   2    #do some code
   3 except ValueError:
   4     printValue error occurred
   5 except Exception:
   6     printError occurred

Хорошим стилем в Python считается "ловить то, что ловишь", то есть указывать в except имя именно той ошибки, которую вы хотите обработать, а не просто Exception. К сожалению, для этого надо знать точное имя ошибки, что не всегда удобно.

Часто можно встретить такой стиль написания:

   1 try:
   2     #do some code
   3 except:
   4     printAny error occured, I don't care about”

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

Работа с сетью. Модуль urllib2

Пакет urllib2 предназначен для работы с сетью.

Все, что пока вам из него нужно знать — функция urlopen — открывает веб-ресурс как обычный файл. После открытия с ним можно работать как с обычным файлом, открытым на чтение.

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

   1 import urllib2
   2 url_file = urllib2.urlopen("https://kodomo.fbb.msu.ru/wiki/2017/1/hints10")
   3 local_file = open("local.txt", "w")
   4 for line in url_file:
   5     local_file.write(line)
   6 local_file.close()
   7 url_file.close()

Это позволяет избежать проблем, возникающих при временных неполадках в сети.

Лайфхаки (дополнительная информация)

Склеивание строк. Если ml — список, состоящий только из строк, то выражение "\t".join(ml) возвращает строку из элементов списка, "склееных" табулятором (попробуйте!). Терминология: join есть метод объекта типа "строка", принимающий аргумент типа "список".

Итерация без создания списка. Если вам не нужен сам список, создаваемый range, то можно вместо range использовать в цикле for фукнцию xrange — она расходует значительно меньше памяти и выполняется быстрее. В Python3 range ведет себя как xrange сразу

   1 for i in xrange(5):
   2     print (i) 
   3 # "1\n2\n3\n4\n5"

Больше об ошибках

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

Возвращение ошибки

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

При этом ошибка может возвращаться функцией как дополнительное значение:

   1 NOT_ERROR = 0
   2 ERROR = 1
   3 
   4 def toint(number_str):
   5     if number_str.isdigit():
   6         return int(number_str), NOT_ERROR
   7     else:
   8         return 0, ERROR
   9 
  10 number_str = raw_input()
  11 num, err = toint(number_str)
  12 if err != NOT_ERROR:
  13     printError occured

А может вместо самого значения:

   1 def toint_or_none(number_str):
   2     if number_str.isdigit():
   3         return int(number_str)
   4     else:
   5         return None
   6 
   7 number_str = raw_input()
   8 num = toint(number_str)
   9 if num is None: 
  10     printError occured

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

Обратите внимание на сравнение чего-то с None:

   1 if num is None: 
   2     printHI

только так сравнивать и надо. Испольовать для этого обычное сравнение:

   1 if num == None:
   2     printHI

неправильно (дело в том, что == для разных типов в Python может значить разное).

Исключения

   1 def must_toint(number_str):
   2     return int(number_str)
   3 
   4 number_str = raw_input()
   5 
   6 try:
   7     num = must_toint(number_str)
   8 except Exception:
   9     printError occurred

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

Чтобы "ожидать" исключение и используется конструкции типа try… except

Что лучше?

Механизм передачи ошибок функцией, как правило, работает быстрее. Однако он требует бОльшего количества, иногда более запутанного кода.

Механизм исключений, как правило, создает менее запутанный код, но работает медленнее, к тому же, частое использование

   1 try:
   2    # do a lot
   3 except:
   4    # handle everything

полностью нивелирует пользы от exception

В Python принято использовать механизмы исключений ("сначала пробуй, потом извиняйся"), они оптимизированы, насколько это возможно в Python.