Функции

Материал из Институт биоинформатики
Перейти к: навигация, поиск

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

Встроенные функции

В языке Python имеется внушительная стандартная библиотека так называемых встроенных функций (built-in functions). Эти функции можно вызвать сразу без определения, просто передав в них необходимые аргументы. Самый простой пример такой функции — print()[1], она служит для вывода чего-либо на печать. Она принимает разные объекты в качестве аргументов (в том числе и несколько позиционных аргументов).

Например:

  1. >> a = 13    # a — это числовая переменная
  2. >> print(a)		
  3. 13
  4.  
  5. >> b = "Python ate the bird"    # b — строковая
  6. >> print(b)		
  7. Python ate the bird
  8.  
  9. >> c = ["Python", "ate", "the", "bird"]    # c — список
  10. >> print(c)	
  11. ['Python', 'ate', 'the', 'bird']

И так далее.

Также можно напечатать не только значение переменной, но и какое-то выражение:

  1. >> print(a * 2 + 4)
  2. 30
  3. # Или так:
  4. >> print(a, "*", 2, "+", 4, "=", a * 2 + 4)
  5. 13 * 2 + 4 = 30			
  6. # Однако:
  7. >> print(a + b)
  8. TypeError: unsupported operand type(s) for +: 'int' and 'str'

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

Определяемые пользователем функции

Определяемые пользователем функции (user-defined functions) — это те, что вы пишете самостоятельно. Впрочем, built-ins тоже были когда-то кем-то определены и написаны, но сейчас вы можете ими пользоваться в готовом виде. Функции определяются при помощи зарезервированного слова def (сокращение от слова define). Далее указывается имя функции, за которым следует пара скобок — в этих скобках можно указать имена переменных — после которых идет заключительное двоеточие.

Пример #1:

  1. >> def empty_func():
  2.        pass

Пожалуй это самая простая пользовательская функция, которую можно определить. В нее не передаются аргументы, и она фактически ничего не исполняет. Однако в Python функции с пустым телом запрещены, потому в качестве тела приведенной выше функции используется «пустой оператор» pass [2].

Пример #2:

  1. >> def printme(to_print):
  2.        print(to_print)

Здесь мы определили простейшую функцию, которая ведет себя как print(), однако имеет синтаксис определяемой пользователем функции. Можно видоизменить ее, чтобы получить что-то более полезное (и осмысленное):

  1. >> def print_name():
    			
  2.        name = input("Input string (name)")
  3.        phrase = "My name is" + " " + name
    			
  4.        print(phrase)
  5.        return name
  6.  
  7. >> print_name()
  8.  
  9. >> Input string (name) Mr. Bird
  10. My name is Mr. Bird

Здесь функции все еще не передается никаких параметров (аргументов) при её определении. Эта функция содержит в теле встроенную функцию — input() [3] — которая, так скажем, интерактивна. При вызове my_name() пользователю необходимо подать на вход строку (наиболее логичным кажется написать свое имя), эту строку функция присваивает переменной name. Переменной phrase присваивается результат конкатенации "My name is" и строки в name. Эту переменную функция и печатает. В конце тела функции есть еще оператор return. С его помощью можно что-то возвращать из функции в виде результата ее вызова. Функция может и не заканчиваться return [4], при этом она вернет значение None [5]. Функция может быть любой сложности и возвращать любые объекты (списки, кортежи и даже функции!). В нашем примере это переменная name. Однако это не делает её доступной вне тела функции — она останется локальной. Чтобы иметь доступ к результату работы функции, можно присвоить возвращаемый через return объект какой-то переменной:

  1. >> my_name = print_name()

Или же использовать его напрямую как аргумент для другой функции:

  1. >> print(print_name())
  2.        Mr. Bird

Чтобы функция вернула не одно значение, можно возвращать кортеж из нескольких значений:

  1. >> def foo():
  2.        a = 14
  3.        b = a * 2
  4.        return (a, b)

Тогда результат вызова функции тоже нужно присваивать кортежу:

  1. (n, m) = foo(a, b)

Параметры и аргументы функции

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

Функции бывают с произвольным числом параметров, с позиционными и именованными параметрами, обязательными и необязательными [6].

Во встроенных функциях аргументы уже заранее предопределены автором.

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

Пример:

  1. >>def printMax(a, b):
  2.        if a > b:
  3.             print(a, 'максимально')
  4.        elif a == b:
  5.             print(a, 'равно', b)
  6.        else:
  7.             print(b, 'максимально')
  8.  
  9. >> printMax(3, 4) # прямая передача значений
  10. 4 максимально
  11.  
  12. >> x = 5
  13. >> y = 7
  14.  
  15. >> printMax(x, y) # передача переменных в качестве аргументов
  16. 7 максимально

Здесь была определена функция, использующая два аргумента a и b. Применяя конструкцию if ... elif ... else, мы находим наибольшее из переданных в функцию чисел и выводим это число на печать, ничего не возвращая из функции. В первом вызове мы передаем числа в качестве аргументов напрямую, во втором — вызываем функцию с переменными в качестве аргументов. printMax(x, y) передает значение аргумента x параметру a, а значение аргумента y — параметру b. В обоих случаях функция printMax работает одинаково.

Значения аргументов по умолчанию

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

Например:

  1. >> def say_bird(word, times=1)
  2.        print("Bird says", word * times)
  3.  
  4. >> say_bird("pi")
  5. Bird says pi
  6. >> say_bird("pi", 3)
  7. Bird says pipipi

Или уже знакомая функция print(), которая также может принимать некоторые ключевые аргументы, заданные по умолчанию:

  1. >> a = 1
  2. >> b = 2
  3. >> c = 3
  4. >> print(a, b, c, sep = '*', end = '!')

По умолчанию для этих параметров заданы значения sep=' ', end='\n'. То есть, все символы, перечисленные через запятую, будут напечатаны через пробел, и после вывода всех значений будет поставлен символ \n, который не печатается, но указывает программе, что следующий вывод будет происходить с новой строки. В примере мы заменили их собственными аргументами, таким образом, в консоли появится:

1*2*3!

Стоит помнить, что значениями по умолчанию могут быть снабжены только параметры, находящиеся в конце списка параметров. Это связано с тем, что значения присваиваются параметрам в соответствии с их положением. Например, def foo(a, b=5) допустимо, а def foo(a=5, b) – не допустимо (и выдаст ошибку).

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

  1. >> def_val = 2
  2. >> def our_func(val = def_val):
  3.        print val
  4.  
  5. >> our_func(4)    # выведет 4
  6. >> our_func()     # выведет 2 – значение по умолчанию
  7. >> def_val = 12
  8. >> our_func()     # все равно 2, так как def_val было равно 2 на момент объявления

Стоит помнить такую особенность: значения по умолчанию разделяются между объектами. Предположим, функция:

  1. >> def foo(mydict={}):  
  2.        ... compute something ...
  3.            mydict[key] = value
  4.            return mydict

При вызове foo() в первый раз mydict содержит одно значение. Во второй раз mydict содержит 2 элемента, поскольку, когда foo() начинает выполняться, mydict уже содержит элемент. Часто ожидается, что вызов функции создаёт новые объекты для значений по умолчанию. Но это не так. Значения по умолчанию создаются лишь однажды, когда функция определяется. Если этот объект изменяется (например, список или словарь), последующие вызовы функции будут использовать изменённый объект.

По определению, неизменяемые объекты (числа, строки, кортежи и None) безопасны при изменении. Изменение изменяемых объектов, таких как словари, списки и экземпляры пользовательских классов, может привести к неожиданным последствиям.

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

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

Позиционные и ключевые аргументы (position and keyword arguments)

Если имеется некоторая функция с большим числом параметров и при её вызове требуется указать только некоторые из них, значения этих параметров могут задаваться по их имени — это называется ключевые параметры. Тогда для передачи аргументов функции используется имя (ключ) вместо позиции в списке параметров. Таким образом можно не отслеживать порядок аргументов, а также теряется необходимость перезадавать значения всем параметрам, заданным по умолчанию — достаточно изменить лишь необходимые. Например:

  1. >> def foo(a, b=5, c=10, d=20)
  2.        print("a =", a, "b =", b, "c =", c)
  3. >> foo(1, 6)
  4. a = 1 b = 6 c = 10 d = 20
  5. >> foo(1, d=3)
  6. a = 1 b = 5 c = 10 d = 3
  7. >> foo(c=16, a=4)
  8. a = 4 b = 5 c = 16 d = 20

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

Может понадобиться, чтобы определяемая функция принимала любое число параметров. Например, функция расчета суммы элементов списка. Конечно, можно передавать все аргументы как один параметр типа list, но это выглядит некрасиво. Потому в Python был придуман специальный механизм, называемый position arguments [7]. Вот пример, демонстрирующий использование.

  1. >> def list_sum(*args):
  2.        smm = 0
  3.        for arg in args:
  4.            smm += arg
  5.        return smm
  6.  
  7. >> list_sum(1, 2, 3)
  8. 6
  9.  
  10. >> list_sum(1)
  11. 1
  12.  
  13. >> list_sum()
  14. 0

В данном случае все наши параметры «упаковываются» в список args в соответствии с их «порядковым номером» при передаче. Причем *args — это слово, которое неплохо использовать: так договорились для повышения считываемости кода другими пользователями. Можно искользовать любое другое слово со значком астериска (который обязателен), однако это некрасиво.

Возможна и обратная операция — допустим, у нас есть список значений, и мы хотим передать их как список параметров функции:

  1. >> lst = [1, 10, 2]
  2. >> list(range(*lst))
  3. [1, 3, 5, 7, 9]

В этом примере список lst был «распакован» и подставлен на место параметров функции range.

Еще один пример распаковки списка аргументов:

  1. >> def fox(*args, start='What does the fox say?'):
  2.        print(start)
  3.        for arg in args:
  4.            print(arg)
  5.  
  6. >> fox(*["Ring-ding-ding-ding-dingeringeding", "Gering-ding-ding-ding-dingeringeding"])
  7. What does the fox say?
  8. Ring-ding-ding-ding-dingeringeding
  9. Gering-ding-ding-ding-dingeringeding

Функция также может принимать произвольное число параметров, заданных по ключу. Для этого используют конструкцию **kwargs (по аналогии с *args). Например:

  1. >> def klezmatics(time, **kwargs):
  2.        print("I'll call you at half past ", time)
  3.        for key in kwargs:
  4.            print(key, kwargs[key])
  5. >> klezmatics(time="three", three="is for this warships at sea", two="for the love of me and you", 
  6.          one="is for the pretty little baby")

Любопытно, что песня, скорее всего, не получится, так как фактически в kwargs будет храниться словарь, а он представляет собой неупорядоченные пары "ключ-значение".

Опциональные или именованные параметры также можно передавать из одной функции в другую (вложенную). Получить такие параметры можно с помощью спецификаторов * и ** в списке аргументов функции; они возвращают кортеж позиционных аргументов и словарь именованых параметров. После этого можно передать их в другую функцию, используя в её вызове * и **:

  1. >> def f(x, *args, **kwargs):
  2.        ...
  3.        kwargs['width'] = '14.3c'
  4.        ...
  5.        g(x, *args, **kwargs)

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

  1. >> def printer(name, a="Dear me", *args, b=',', c, **kwargs):
  2.        print(a, b, name, b, a, c)
  3.  
  4. >> printer("Mr. Holmes", c='!')
  5. Dear me , Mr. Holmes , Dear me !

Синтаксис функции

В итоге полную схему синтаксиса функции можно представить так: def function_name([positional_args,

     [positional_args_with_default,
     [*positional_args_named,
     [keyword_only_args,
     [keyword_args_named]]]]]):
   ... compute something...
   return some_arg

Локальные и глобальные переменные в применении к функциям

При объявлении переменных внутри функции они никоим образом не связаны с другими переменными с таким же именем за пределами функции – т.е. имена переменных являются локальными в функции. Это называется областью видимости переменной. Область видимости всех переменных ограничена блоком, в котором они объявлены, начиная с точки объявления имени.

Так внутри функции можно использовать переменные, объявленные вне этой функции.

  1. >> def f():
  2.        print(a)
  3. >> a = 1
  4. >> f()
  5. 1

Но если инициализировать какую-то переменную внутри функции, использовать эту переменную вне функции не удастся. Например:

  1. >> def f():
  2.        a = 1
  3. >> f()
  4. >> print(a)

Получим NameError: name 'a' is not defined. Такие переменные, объявленные внутри функции, называются локальными. Эти переменные становятся недоступными после выхода из функции.

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

  1. >> x = 50
  2.  
  3. >> def func(x):
  4.        print('x equals', x)
  5.        x = 2
  6.        print('Replacing local x for', x)
  7.  
  8. >> func(x)
  9. >> print('x still', x)

Как итог выполнения этих команд на экран будет выведено:

  1. x equals 50
  2. Replacing local x for 2
  3. x still 50

Несмотря на то, что значение переменной х изменилось внутри функции, вне функции оно осталось прежним x = 50. Это сделано в целях “защиты” глобальных переменных от случайного изменения из функции (например, если функция будет вызвана из цикла по переменной i, а в этой функции будет использована переменная i также для организации цикла, то эти переменные должны быть различными). То есть, если внутри функции модифицируется значение некоторой переменной, то переменная с таким именем становится локальной переменной, и ее модификация не приведет к изменению глобальной переменной с таким же именем. Однако хорошим тоном на первых порах будет называть такие переменные разными именами (просто чтобы не запутаться самим).

Более формально: интерпретатор Python считает переменную локальной, если внутри функции есть хотя бы одна инструкция, модифицирующая значение этой переменной (это может быть оператор =, += и т.д. или использование этой переменной в качестве параметра цикла for [8] [9]). Такая переменная считается локальной и не может быть использована до инициализации, даже если инструкция, модицифицирующая переменную, никогда не будет выполнена: интерпретатор это проверить не может, и переменная все равно считается локальной.

В этой особенности языка Python может скрываться много подводных камней.

Например:

  1. >> x = 10
  2. >> def foo():
  3.        print(x)
  4. >> foo()
  5. 10    # такой код будет работать

Однако, если написать:

  1. >> x = 10
  2. >> def foo():
  3.        print(x)
  4.        x += 1

То при вызове функции получится:

  1. >> foo()
  2. Traceback (most recent call last):
  3. ...
  4. UnboundLocalError: local variable 'x' referenced before assignment

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

Зарезервированные слова global и local

Чтобы изнутри функции присвоить некоторое значение переменной, определённой на высшем уровне программы (т.е. не в какой-либо области видимости, как то функции или классы), необходимо указать Python, что её имя не локально, а глобально (global). Сделаем это при помощи зарезервированного слова global. Без применения зарезервированного слова global невозможно присвоить значение переменной, определённой за пределами функции.

Можно использовать уже существующие значения переменных, определённых за пределами функции (при условии, что внутри функции не было объявлено переменной с таким же именем). Однако это не приветствуется, и этого следует избегать, поскольку человеку, читающему текст программы, будет непонятно, где находится объявление переменной. Использование зарезервированного слова global достаточно ясно показывает, что переменная объявлена в самом внешнем блоке.

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

  1. >> def foo():
  2.        global a
  3.        a = 1
  4.        print(a)
  5.    a = 0
  6.    foo()
  7.    print(a)

На экран будет выведено

       1
       1 

так как переменная a объявлена как глобальная и ее изменение внутри функции приводит к тому, что и вне функции переменная будет доступна.

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

Функции более высокого порядка

Есть два пути: использовать вложенные функции или вызываемые объекты. Например, использование вложенных функций:

  1. >> def linear(a, b):
  2.        def result(x):
  3.            return a * x + b
  4.        return result

Использование вызываемого объекта:

  1. >> class linear:
  2.        def __init__(self, a, b):
  3.            self.a, self.b = a, b
  4.  
  5.        def __call__(self, x):
  6.            return self.a * x + self.b

Использование вызываемого объекта — немного медленнее, и в результате получается больше кода. Немного больше о вложенных функциях и их преимуществах можно прочесть тут [10], [11].

Анонимные функции (lambda)

Анонимные функции могут содержать лишь одно выражение, но и выполняются они быстрее. Анонимные функции создаются с помощью инструкции lambda. Иногда нужно передать функцию в качестве аргумента или сделать короткую, но сложную операцию несколько раз. Можно определить функцию обычным способом, а можно использовать лямбда-функцию — маленькую функцию, возвращающую результат одного выражения. Следующие два определения полностью идентичны:

  1. def add(a,b): return a+b
  2.  
  3. add2 = lambda a,b: a+b

Преимущество лямбда-функции в том, что она является выражением и может быть использована внутри другого выражения. Ниже приведен пример, использующий функцию map [12], которая вызывает функцию для каждого элемента списка и возвращает список результатов.

  1. squares = map(lambda a: a*a, [1,2,3,4,5])      # теперь squares = [1,4,9,16,25]

Синтаксис лямбда-функции:

lambda переменные: выражение

переменные — список аргументов, разделенных запятой. Нельзя использовать ключевые слова. Аргументы не надо заключать в скобки.

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

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

Например, есть следующий код:

  1. >> squares = []
  2. >> for x in range(5):
  3.        squares.append(lambda: x**2)

Это даст список из 5 функций, считающих x**2. Можно ожидать, что, будучи вызванными, они вернут, соответственно, 0, 1, 4, 9 и 16. Однако все они возвращают 16:

  1. >> squares[2]()
  2. 16
  3. >> squares[4]()
  4. 16

Это случается, поскольку x не является локальной для lambda, а определена во внешней области видимости, и получается тогда, когда она вызывается — а не когда определяется. В конце цикла x = 4, поэтому все функции возвращают 4**2, то есть 16. Чтобы избежать подобного, необходимо сохранять значения переменных локально:

  1. >> squares = []
  2. >> for x in range(5):
  3.        squares.append(lambda n=x: n**2)

Здесь n=x создаёт локальную для функции переменную n и вычисляется в момент определения функции.

Строка документации (docstring)

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

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

  1. >> print(foo.__doc__)

Cтроки документации применимы также к модулям и классам.

Исполнение кода, cтек вызовов

При исполнении кода создается единица выполнения кода — "поток". Стек — это область памяти, в которой поток может хранить данные, необходимые ему для выполнения. Работает по принципу «пришел первым, вышел последним». При исполнении следующей функции:

  1. def multiplication(x, y):
  2.     def sum(x, y):
  3.         def subtraction(x, y=2):
  4. 	    return x - y
  5. 	b = subtraction(x)
  6. 	return b + y
  7.     a = sum(x, y)
  8.     return a * 2
  9. multiplication(2, 5)

сначала вызывается функция multiplication, ее вызов помещается на стек вызовов, при этом создается namespace этой функции, куда будут записываться объявленные в ней переменные.

Fun1.jpg


Внутри функции multiplication вызывается функция sum и помещается на стек вызовов, при этом создается namespace функции sum для записи объявленных в ней переменных.

Fun2.jpg


Далее внутри функции sum происходит вызов функции subtraction, которая, в свою очередь, добавляется на стек вызовов и создается ее namespace.

Funn3.jpg


Доступ к тем или иным переменным в python зависит от того, где они были объявлены.

  • Аргументы функции и переменные, объявленные внутри функции, являются локальными переменными.
  • Объявленные вне – глобальные (в данном случае функция multiplication).
  • Последним осматривается namespace builtins, который содержит имена стандартных функций.


Когда внутри функции вызывается переменная, интерпретатор сначала будет искать переменную среди локально объявленных переменных, затем среди глобально объявленных, и после — в namespace builtins.

То есть, локальные переменные, объявленные каждой функцией, будут доступны в обратном порядке.

  1. def multiplication(x, y):
  2.     def sum(x, y):
  3.         def subtraction(x, y=2):
  4. 	    print(z)
  5. 	    return x - y
  6. 	b = subtraction(x)
  7. 	return b + y
  8.     a = sum(x, y)
  9.     return a * 2
  10. multiplication(2, 5)
  11. z = 8

Если внутри функции subtraction вызвать переменную z, то интерпретатор сначала будет искать ее среди локальных переменных функции subtraction(1). Не обнаружив ее там, он перейдет к поиску внутри функции sum(2), затем к поиску внутри функции multiplication(3). Не обнаружив переменную внутри функции multiplication, он будет искать переменную среди глобально объявленных переменных(4).


Fun7.jpg