Функции в Python

Функции в Python — определение (def) и вызов, работа с параметрами и аргументами, области видимости и множество примеров

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

Преимущества использования функций:

  • Нет дублирования кода. Один раз создали функцию и просто вызываем ее в нужном месте, без лишнего дублирования кода.
  • Модульность. Функции дают возможность разбивать свой код на модули, чтобы каждый модуль выполнял свою задачу.
  • Удобство редактирования. Т.к. функция создается и описывается один раз, то редактировать нужно непосредственно функцию, независимо от того, сколько раз она вызывается в коде.

В Python есть несколько типов функций:

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

Здесь мы рассмотрим именно пользовательские функции.

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

def my_function(a, b):
    function_block
    return expression

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

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

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

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

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

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

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

def my_function():
    print('Python')
    
    
my_function()
#Python

Мы создали функцию my_function(), которая просто выводит ‘Python’, далее ниже в коде мы вызвали эту функции, просто обратившись к ней по ее имени.
В результате мы получили вывод ‘Python’.

Фунция с return

Теперь создадим функцию, которая возвращает результат своей работы с помощью оператора return:

def my_sum():
    a = 1
    b = 1
    return a + b
    
    
print(my_sum())
#2

В созданной функции my_sum() мы складываем переменные a и b, далее с помощью return возвращаем результат работы функции, т.е. сумму и после этого функцией print() выводим результат работы нашей функции.

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

Присвоим переменной результат функции из предыдущего примера:

def my_sum():
    a = 1
    b = 1
    return a + b
    
    
a = my_sum()
print(a)
#2

Но помимо этого, мы можем присваивать переменной еще и саму функцию, а не только ее значение:

def my_sum():
    a = 1
    b = 1
    return a + b
    
    
a = my_sum
print(a())
#2

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

Аргументы функции — значение, которое передается в функцию.

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

def my_sum(a, b):  #a, b - параметры функции
    return a + b
    

my_sum(1, 2)       #1, 2 - аргументы функции

В свою очередь аргументы функции делятся на:

  • Позиционные (ключевые) аргументы.
  • Именованные аргументы.

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

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

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

def hello_user(first_name, last_name):
    print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user('Роман', 'Пупкин')
#first_name = Роман, last_name = Пупкин
#Привет, Роман!

Мы передаем позиционные аргументы ‘Роман’, ‘Пупкин’, которые попадают в параметры функции first_name, last_name. После чего выводятся через print().

А что будет, если мы поменяем местами аргументы на ‘Пупкин’, ‘Роман’:

def hello_user(first_name, last_name):
    print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user('Пупкин', 'Роман')
#first_name = Пупкин, last_name = Роман
#Привет, Пупкин!

Как видно, теперь аргумент ‘Пупкин’ попал в параметр first_name, а ‘Роман’ в last_name.

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

Именованные аргументы функций

Именованный аргумент — это пара ‘имя=значение’, которая передается в функцию.

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

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

def hello_user(first_name, last_name):
    print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user(first_name='Роман', last_name='Пупкин')
#first_name = Роман, last_name = Пупкин
#Привет, Роман!

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

def hello_user(first_name, last_name):
    print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user(last_name='Пупкин', first_name='Роман')
#first_name = Роман, last_name = Пупкин
#Привет, Роман!

Комбинирование позиционных и именованных аргументов

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

def hello_user(first_name, last_name):
    print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user('Пупкин', last_name='Роман')
#first_name = Пупкин, last_name = Роман
#Привет, Пупкин!

Но важно помнить, что сначала идут позиционные аргументы, а после них уже именованные, иначе будет ошибка:

def hello_user(first_name, last_name):
    print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user(last_name='Роман', 'Пупкин')
#Syntax Error: positional argument follows keyword argument

Параметры функций со значениями по умолчанию

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

def hello_user(first_name, last_name, log=True):
    if log:
        print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user('Роман', 'Пупкин')
#first_name = Роман, last_name = Пупкин
#Привет, Роман!

В функцию hello_user, мы добавили параметр log, который по умолчанию равен True.
Далее в теле функции мы добавили условие, если log равен True, то выводим информацию о том, что присвоено каждой переменной print(f'first_name = {first_name}, last_name = {last_name}').
И так как, значение log у нас по умолчанию True, то мы видим first_name = Роман, last_name = Пупкин.

А теперь давайте при вызове функции изменим значение этого параметра по умолчанию:

def hello_user(first_name, last_name, log=True):
    if log:
        print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user('Роман', 'Пупкин', False)
#Привет, Роман!

Теперь мы видим только приветствие, т.к. условие if log вернуло результат False и блок с условием не вывелся.

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

def hello_user(first_name, last_name, log=True):
    if log:
        print(f'first_name = {first_name}, last_name = {last_name}')
    print(f'Привет, {first_name}!')


hello_user('Роман', 'Пупкин', log=False)
#Привет, Роман!

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

Функции с произвольным числом аргументов

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

Произвольное число позиционных аргументов

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

def my_function(*args):
    print(args)
    
    
my_function(1, 2, 3, 4)
#(1, 2, 3, 4)

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

Но если нужно передавать в функцию и позиционные элементы (количество которых известно), и произвольное число аргументов, то сначала следует вводить позиционные, а потом произвольные:

def my_function(a, b, *args):
    print(a, b, args)
    
    
my_function(1, 2, 3, 4)
#1 2 (3, 4)

Если сделать наоборот, то будет ошибка:

def my_function(*args, a, b):
    print(a, b, args)
    
    
my_function(1, 2, 3, 4)
#TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'

Произвольное количество именованных аргументов

Для того, чтобы передать в функцию переменное число именованных аргументов (т.е. пар ‘ключ=значение’), перед именем параметра нужно поставить **:

def my_function(**kwargs):
    print(kwargs)
    
    
my_function(a=1, b=2, c=3, d=4)
#{'a': 1, 'b': 2, 'c': 3, 'd': 4}

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

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

def my_function(a, b, *args, c=0, d=0, **kwargs):
    print(a, b, args, c, d, kwargs)
    
    
my_function(1, 2, 3, 4, 5, e=6, f=7)
#1 2 (3, 4, 5) 0 0 {'e': 6, 'f': 7}

Именно в таком порядке нужно указывать параметры в функции:

  1. Позиционные параметры.
  2. Произвольное число позиционных аргументов (*args).
  3. Формальные параметры (со значениями по умолчанию).
  4. Произвольное число именованных аргументов (**kwargs).

Рекурсивные функции

Рекурсивная функция — это функция, которая вызывает сама себя.

С помощью рекурсивной функции выведем числ от 1 до n:

def rec_n(n):
    if n > 1:
        rec_n(n - 1)
    print(n)


rec_n(5)
#1
#2
#3
#4
#5

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

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

Lambda функции — анонимные функции, которые не имеют имени и записываются в одну строку.

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

lambda <аргумент(ы)>: <выражение>

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

Без аргументов:

a = lambda: 'Просто строка'
print(a())
#Просто строка

С одним аргументом:

a = lambda x: x ** 2
print(a(2))
#4

С несколькими аргументами:

a = lambda x, y: x + y
print(a(1, 2))
#3

А вот также самая функция записанная классическим способом:

def func(x, y):
    return x + y

print(func(1, 2))
#3

Также в lambda функциях можно использовать тренарный оператор:

[если истина] if [выражение] else [если ложь]
a = lambda x: True if x > 0 else False
print(a(-9))
#False

Здесь мы передаем в анонимную функцию аргумент и если он больше 0, то возвращаем True, а иначе False.

Области видимости переменных

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

В Python существует 3 области видимости:

  • Локальная.
  • Глобальная.
  • Нелокальная.

Локальная переменная

Локальная переменная находится внутри функции.

Создадим простую функцию и вызовем ее:

def my_sum(a, b):
    s = a + b
    print(s)


my_sum(1, 2)
#3

В этой функции переменная s является локальной переменной. Это значит, что сама переменная s существует только внутри нашей созданной функции. И если мы попытаемся обратиться к этой переменной за пределом функции, то получим ошибку:

def my_sum(a, b):
    s = a + b
    print(s)


my_sum(1, 2)
print(s)
#3
#NameError: name 's' is not defined

Т.е. так как переменная s является локальной, то Python нам говорит, что переменная неопределена.

Глобальная переменная

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

x = 100

def my_sum(a, b):
    s = a + b + x
    print(s)


my_sum(1, 2)
#103

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

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

x = 100

def my_sum(a, b):
    x = 0
    s = a + b + x
    print(s)


my_sum(1, 2)
print(x)
#3
#100

Глобальная переменная x = 100, внутри функции, мы присвоили переменной x = 0, в итоге функции отработала ожидаемо 1 + 2 + 0 = 3, но print(x) все равно показал значение x = 100.

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

Если же нам нужно работать именно с глобальной переменной, то нужно прописать ключевое слово global:

x = 100

def my_sum(a, b):
    global x
    x = 0
    s = a + b + x
    print(s)


my_sum(1, 2)
print(x)
#3
#0

В этом случае после того, как мы написали global x, мы начали работать внутри функции именно с глобальной переменной x, присвоили ей значение 0 и print(x) показал значение x = 0.

Нелокальная переменная

Как работают нелокальные переменные наглядно можно увидеть на примере вложенных функций:

x = 0

def func_1():
    x = 1
    def func_2():
        x = 2
        print('func_2 x =', x)
    func_2()
    print('func_1 x =', x)

func_1()
print('global x =', x)
#func_2 x = 2
#func_1 x = 1
#global x = 0

Здесь мы создали глобальную переменную x = 0, функцию func_1() в которой x = 1 и вложенную функцию func_2() в которой x = 2. В обеих функциях прописали print() для вывода их локальных переменных x и вызвали вложенную функцию func_2() из функции func_1().

А в конце просто вызвали функцию func_1() и print(x) для отображения глобальной переменной.

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

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

x = 0

def func_1():
    x = 1
    def func_2():
        nonlocal x
        x = 2
        print('func_2 x =', x)
    func_2()
    print('func_1 x =', x)

func_1()
print('global x =', x)
#func_2 x = 2
#func_1 x = 2
#global x = 0

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

Но если мы напишем nonlocal в родительской функции, то получим ошибку:

x = 0

def func_1():
    nonlocal x
    x = 1
    def func_2():
        x = 2
        print('func_2 x =', x)
    func_2()
    print('func_1 x =', x)

func_1()
print('global x =', x)
#Syntax Error: no binding for nonlocal 'x' found

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

Здесь уже нужно использовать global:

x = 0

def func_1():
    global x
    x = 1
    def func_2():
        x = 2
        print('func_2 x =', x)
    func_2()
    print('func_1 x =', x)

func_1()
print('global x =', x)
#func_2 x = 2
#func_1 x = 1
#global x = 1

Содержание: