Функции в Python

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

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

Существует множество встроенных в язык программирования функций. С некоторыми такими в Python вы уже сталкивались. Это print(), input(), int(), float(), str(), type(). Код их тела нам не виден, он где-то "спрятан внутри языка". Нам же предоставляется только интерфейс - имя функции.

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

Определение функции. Оператор def

В языке программирования Python функции определяются с помощью оператора def. Рассмотрим код

def foo():
    a = int(input())
    b = int(input())
    print("Сумма равна:", a + b)

Это пример определения функции. Как и другие сложные инструкции вроде условного оператора и циклов, функция состоит из заголовка и тела. Заголовок оканчивается двоеточием и переходом на новую строку. Тело имеет отступ.

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

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

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

Вызов функции

Рассмотри следующий код

def foo():
    a = int(input())
    b = int(input())
    print("Сумма равна:", a + b)

print("Подсчет суммы двух чисел")
foo()

print("Еще один подсчет суммы двух чисел")
foo()

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

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

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

Подсчет суммы двух чисел
Traceback (most recent call last):

  File "C:\Users\nickg\.spyder-py3\temp.py", line 2, in <module>
    foo()

NameError: name 'foo' is not defined

Возврат значений из функции. Оператор return

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

Если Python, выполняя тело функции, встречает return, то он "забирает" значение, указанное после этой команды, и "уходит" из функции.

def getSum():
    a = int(input())
    b = int(input())
    return a + b

answer = getSum()
print(answer)

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

В основной ветке программы это значение присваивается переменной answer. То есть выражение answer = getSum() выполняется так:

  1. вызывается функция getSum();

  2. из нее возвращается значение;

  3. это значение присваивается переменной answer.

Не обязательно присваивать результат переменной, его можно сразу вывести на экран:

...
print(getSum())

Здесь число, полученное из getSum(), непосредственно передается функции print(). Если мы в программе просто напишем getSum(), не присвоив полученные данные переменной или не передав их куда-то дальше, то эти данные будут потеряны. Но ошибки не будет.

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

def foo():
    a = int(input("Введите число больше 0: "))
    if (a <= 0):
        return
        
    return a * a

print(foo())

Если ввести число меньше нуля, то в консоли будет выведено

None

"Пустой" оператор return вернул слово None. Это специальный тип данных - "ничто".

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

В Python можно возвращать несколько значений, перечислив их через запятую после команды return

def foo():
    a = input()
    b = input()
    return a, b

var1, var2 = foo()

print(var1 + var2)

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

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

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

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

Когда функция вызываются, ей передаются аргументы. Когда Python переходит к функции, чтобы начать ее исполнение, он присваивает переменным-параметрам переданные в функцию значения-аргументы. В примере переменной a будет присвоено 100, переменной b будет присвоено 12.

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

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

def foo(a, b = 5):
    return a * b

print(foo(4, 10)) # Будет выведено 40
print(foo(4)) # Будет выведено 20

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

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

def foo(a, b = 5):
    return a * b

print(foo(10, 25))
print(foo(a = 10, b = 25))

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

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

Локальные и глобальные переменные

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

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

"Видны" - значит, известны, доступны. К ним можно обратиться по имени и получить связанное с ними значение

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

Рассмотрим следующий код

def rect():
    a = float(input("Шиирна: "))
    b = float(input("Высота: "))
    print("Площадь: ", a * b)

def triangle():
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    print("Площадь: ", 0.5 * a * h)
    
figure = input("Введите 1(прямоугольник) или 2(треугольник): ")
if figure == "1":
    rect()
elif figure == "2":
    triangle()

Сколько здесь переменных? Какие из них являются глобальными, а какие - локальными?

Здесь пять переменных. Глобальной является только figure. Переменные a и b из функции rect(), а также a и h из triangle() - локальные. При этом локальные переменные с одним и тем же идентификатором a, но объявленные в разных функциях - разные переменные.

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

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

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

...
elif figure == "2":
    triangle()
    
print(a)

>>> Консольный вывод:

Введите 1(прямоугольник) или 2(треугольник): 1

Шиирна: 20

Высота: 30
Площадь:  600.0
Traceback (most recent call last):

  File "C:\Users\nickg\.spyder-py3\temp.py", line 17, in <module>
    print(a)

NameError: name 'a' is not defined

Однако мы можем обращаться из функций к глобальным переменным

def rect():
    print("Вы выбрали опцию:", figure) # обращение к глобальной переменной
    a = float(input("Шиирна: "))
    b = float(input("Высота: "))
    print("Площадь: ", a * b)

def triangle():
    print("Вы выбрали опцию:", figure) # обращение к глобальной переменной
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    print("Площадь: ", 0.5 * a * h)
    
figure = input("Введите 1(прямоугольник) или 2(треугольник): ")
if figure == "1":
    rect()
elif figure == "2":
    triangle()

>>> Консольный вывод:

Введите 1(прямоугольник) или 2(треугольник): 1
Вы выбрали опцию: 1

Шиирна: 20

Высота: 30
Площадь:  600.0

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

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

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

result = 0.0

def rect():
    print("Вы выбрали опцию:", figure)
    a = float(input("Шиирна: "))
    b = float(input("Высота: "))
    result = a * b

def triangle():
    print("Вы выбрали опцию:", figure)
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    result = 0.5 * a * h
    
figure = input("Введите 1(прямоугольник) или 2(треугольник): ")
if figure == "1":
    rect()
elif figure == "2":
    triangle()

print("Площадь: ", result)

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

Введите 1(прямоугольник) или 2(треугольник): 1
Вы выбрали опцию: 1

Шиирна: 10

Высота: 20
Площадь:  0.0

Как видим, программа работает некорректно - в глобальную переменную result значение записано не было.

Дело в том, что в Python присвоение значения переменной совмещено с ее объявлением. Поэтому, когда имя result впервые упоминается в локальной области видимости, и при этом происходит присваивание ей значения, то создается локальная переменная result. Это другая переменная, локальная, никак не связанная с глобальной переменной result.

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

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

В случае с result он ничего не ищет. Он выполняет вычисления справа от знака присваивания, создает локальную переменную result, связывает ее с полученным значением.

На самом деле можно принудительно обратиться к глобальной переменной. Для этого существует ключевое слово global

result = 0.0

def rect():
    print("Вы выбрали опцию:", figure)
    a = float(input("Шиирна: "))
    b = float(input("Высота: "))
    global result
    result = a * b

def triangle():
    print("Вы выбрали опцию:", figure)
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    global result
    result = 0.5 * a * h

...

В таком варианте программа будет работать правильно.

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

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

Функции придают программе структуру

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

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

figure = input("1-прямоугольник, 2-треугольник, 3-круг: ")

if figure == '1':
    a = float(input("Ширина: "))
    b = float(input("Высота: "))
    print("Площадь: %.2f" % (a*b))
elif figure == '2':
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    print("Площадь: %.2f" % (0.5 * a * h))
elif figure == '3':
    r = float(input("Радиус: "))
    print("Площадь: %.2f" % (3.14 * r**2))
else:
    print("Ошибка ввода")

Здесь нет никаких функций. Напишем вариант с функциями:

def rectangle():
    a = float(input("Ширина: "))
    b = float(input("Высота: "))
    print("Площадь: ", a * b)
    
def triangle():
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    print("Площадь: ", 0.5 * a * h)
    
def circle():
    r = float(input("Радиус: "))
    print("Площадь: ", 3.14 * r**2)
    
figure = input("1-прямоугольник, 2-треугольник, 3-круг: ")

if figure == '1':
    rectangle()
elif figure == '2':
    triangle()
elif figure == '3':
    circle()
else:
    print("Ошибка ввода")

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

Программа теперь состоит из отдельных "кирпичиков". В основной ветке мы можем комбинировать их как угодно. Она играет роль управляющего механизма.

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

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

Last updated