Книга: Как устроен Python. Гид для разработчиков, программистов и интересующихся
Назад: 16. Словари
Дальше: 18. Индексирование и срезы

17. Функции

Мы прошли долгий путь без обсуждения функций, которые являются основными структурными элементами программ Python. Функции представляют собой фрагменты кода, выделенные в отдельный блок. В примерах уже использовались такие встроенные функции, как dir и help (а также классы, которые ведут себя как функции преобразования типа — float, int, dict, list и bool).

Функцию можно рассматривать как «черный ящик», которому передаются входные данные (хотя их наличие не обязательно). Затем «черный ящик» выполняет серию операций и возвращает результат (если функция завершается без вызова return, она неявно возвращает None). Главным преимуществом функций является возможность повторного использования кода. После того как функция будет определена, вы сможете вызывать ее снова и снова. Если в программе имеется код, который должен выполняться многократно, то вместо копирования/вставки вы можете один раз оформить его в виде функции, а затем вызывать эту функцию. При этом сокращается объем кода, а сама программа становится более понятной. Кроме того, становится проще вносить изменения (и исправлять ошибки), поскольку это делается в одном месте.

Рассмотрим простой пример функции. Эта функция с именем add_2 получает на входе число, прибавляет к нему 2 и возвращает результат:

>>> def add_2(num):

... '''

... return 2 more than num

... '''

... result = num + 2

... return result

514674.png 

Рис. 17.1. Функция работает как «черный ящик»: она получает входные данные и выдает результат (выходные данные). Функции можно передавать при вызове других функций, а также использовать повторно

514695.png 

Рис. 17.2. Создание функции. Обратите внимание: Python создает новый объект функции, а затем сохраняет указатель на него в переменной с именем функции. Для просмотра атрибутов только что созданной функции можно вызвать функцию dir для имени функции

Из каких частей состоит функция? Весь фрагмент кода называется определением функции. Определение начинается с команды def (сокращение от define, то есть «определение»). За def следует обязательный пробел (достаточно одного) и имя функции — add_2. Это имя будет использоваться для вызова функции (то есть ее выполнения). При создании функции Python создаст новую переменную, имя которой совпадает с именем функции.

За именем функции следует открывающая круглая скобка, за которой следует num и закрывающая круглая скобка. Имена между скобками (их может быть сколько угодно, хотя в данном случае имя только одно) определяют входные параметры — объекты, передаваемые функции.

За круглыми скобками следует двоеточие (:). Когда вы видите двоеточие в Python, за ним почти наверняка следует блок с отступом — по аналогии с телом цикла for (см. выше). Весь код с отступом образует тело функции.

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

СОВЕТ

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

>>> help(add_2)

Help on function add_2 in module

__main__:

 

add_2()

return 2 more than num

(END)

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

После строки документации (обратите внимание: строка документации не является обязательной) следует логика функции. Здесь вычисляется результат. Наконец, команда return сообщает, что у функции имеется результат, то есть выходное значение. Команда return не является обязательной, и при ее отсутствии функция по умолчанию возвращает None. Функция может содержать несколько команд return, и они даже не обязаны находиться в конце функции. Например, условная команда может содержать две команды return: в блоке if и в блоке else.

Подведем итог. Основные части функции:

• Ключевое слово def.

• Имя функции.

• Параметры функции в круглых скобках.

• Двоеточие (:).

• Отступ:

• Строка документации.

• Логика.

• Команда return.

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

17.1. Вызов функций

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

>>> add_2(3)

5

Чтобы вызвать функцию, укажите ее имя, за которым следует открывающая круглая скобка, входные параметры и закрывающая круглая скобка. Количество параметров должно соответствовать количеству параметров в объявлении функции. Обратите внимание: REPL выводит результат вызова — целое число 5 (то, что возвращает команда return).

Функции add_2 можно передать произвольный объект. Но если этот объект не поддерживает сложение с числами, будет выдано исключение. При передаче строки выдается исключение TypeError:

>>> add_2('hello')

Traceback (most recent call last):

...

TypeError: must be str, not int

17.2. Область видимости

Python ищет переменные в разных местах. Эти места называются областями видимости или пространствами имен. При поиске переменной (не забывайте, что функции в Python также являются переменными — как и классы, модули и т.д.), Python выполняет поиск в следующих местах и в следующем порядке:

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

• Глобальная область видимости — переменные, определяемые на глобальном уровне.

• Встроенная область видимости — переменные, заранее определенные в Python.

В следующем коде поиск переменных осуществляется по всем трем областям видимости:

>>> x = 2 # Глобальная

>>> def scope_demo():

... y = 4 # Локальная для scope_demo

... print("Local: {}".format(y))

... print("Global: {}".format(x))

... print("Built-in: {}".format(dir))

 

>>> scope_demo()

Local: 4

Global: 2

Built-in: <built-in function dir>

После вызова scope_demo локальная переменная y уничтожается в ходе уборки мусора и становится недоступной в глобальной области видимости:

>>> y

Traceback (most recent call last):

...

NameError: name 'y' is not defined

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

ПРИМЕЧАНИЕ

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

>>> def dir(x):

... print("Dir called")

 

>>> dir('')

Dir called

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

>>> del dir

>>> dir('')

['__add__', '__class__', '__contains__', ... ]

 

подсказка

Функции locals и globals используются для вывода содержимого этих областей видимости. Они возвращают словари с текущим содержимым области видимости:

>>> def foo():

... x = 1

... print(locals())

 

>>> foo()

{'x': 1}

Переменная __builtins__ выводит имена из встроенной области видимости. Ее атрибут __dict__ выдает такой же словарь, как для глобальных и локальных имен.

17.3. Множественные параметры

Функции могут получать несколько параметров. Следующая функция получает два параметра и возвращает их сумму:

>>> def add_two_nums(a, b):

... return a + b

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

>>> add_two_nums(4, 6)

10

А может суммировать числа с плавающей точкой:

>>> add_two_nums(4.0, 6.0)

10.0

И строки тоже:

>>> add_two_nums('4', '6')

'46'

Обратите внимание: для строк используется операция + для выполнения конкатенации (сцепления двух строк).

Но если вы попробуете сложить строку с числом, Python сообщит об ошибке:

>>> add_two_nums('4', 6)

Traceback (most recent call last):

...

TypeError: Can't convert 'int' object to str implicitly

Это одна из тех ситуаций, когда Python требует более точно описать нужную операцию и не пытается угадывать за вас. Если вы хотите сложить строковый тип с числом, возможно, сначала нужно преобразовать их к числовому формату (при помощи float или int). С другой стороны, если нужно выполнить операцию конкатенации, следует преобразовать числа в строки. Python не выбирает выполняемую операцию автоматически. Вместо этого выдается ошибка, которая заставляет программиста разрешить неоднозначность.

17.4. Параметры по умолчанию

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

Следующая функция похожа на add_two_nums, но если при вызове второе число не указано, по умолчанию прибавляется 3:

>>> def add_n(num, n=3):

... """default to

... adding 3"""

... return num + n

 

>>> add_n(2)

5

>>> add_n(15, -5)

10

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

ПРИМЕЧАНИЕ

Параметры по умолчанию должны объявляться после обычных параметров, в противном случае Python выдаст ошибку SyntaxError:

>>> def add_n(num=3, n):

... return num + n

Traceback (most recent call last):

...

SyntaxError: non-default argument follows

default argument

Python требует, чтобы обязательные параметры были объявлены ранее необязательных. Приведенный выше код не будет работать для вызова вида add_n(4), потому что отсутствует обязательный параметр.

СОВЕТ

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

>>> def to_list(value, default=[]):

... default.append(value)

... return default

 

>>> to_list(4)

[4]

>>> to_list('hello')

[4, 'hello']

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

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

>>> def to_list2(value, default=None):

... if default is None:

... default = []

... default.append(value)

... return default

 

>>> to_list2(4)

[4]

>>> to_list2('hello')

['hello']

Следующий код:

... if default is None:

... default = []

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

... default = default if default is not None else []

 

17.5. Правила выбора имен для функций

Правила выбора имен функций имеют много общего с правилами выбора имен переменных (и они также находятся в документе PEP 8). В именах используется так называемый змеиный регистр, который проще читается. Имена функций:

• должны записываться в нижнем регистре;

• слова_должны_разделяться_подчеркиваниями;

• не должны начинаться с цифр;

• не должны переопределять встроенные имена;

• не должны совпадать с ключевыми словами.

В таких языках, как Java, используется так называемый «верблюжий регистр». По этой схеме создаются имена переменных вида sectionList или hasTimeOverlap. В Python переменным были бы присвоены имена section_list и has_time_overlap соответственно. Хотя код Python должен следовать соглашениям PEP 8, в PEP 8 также принимается в расчет единство стиля. Если в коде, над котором вы работаете, используются разные схемы назначения имен, следуйте примеру и используйте схему, применяемую в существующем коде. Собственно, в модуле unittest из стандартной библиотеки до сих пор применяется схема в стиле Java (потому что изначально этот модуль был импортирован из библиотеки Java junit).

17.6. Итоги

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

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

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

17.7. Упражнения

1. Напишите функцию is_odd, которая получает целое число и возвращает True для нечетных чисел или False для четных.

2. Напишите функцию is_prime, которая получает целое число и возвращает True для простых чисел или False для чисел, не являющихся простыми.

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

4. Напишите функцию, которая получает строки в «верблюжьем регистре» (ThisIsCamelCased) и преобразует их в «змеиный регистр» (this_is_camel_cased). Измените функцию, добавив в нее аргумент separator, чтобы функция также могла выполнять преобразование к «кебаб-регистру» (this-is-camel-case).

Назад: 16. Словари
Дальше: 18. Индексирование и срезы