Функции в Python — синтаксис, аргументы, вызов, выход
- Синтаксис
- Термины и определения
- Важность функций
- Абстракция
- Возможность повторного использования
- Модульность
- Пространство имен
- Объявление и вызов функций
- Область видимости функций
- Локальная (L)
- Область объемлющих функций (E)
- Глобальная (G)
- Аргументы
- Позиционные
- Именованные
- Необязательные параметры (параметры по умолчанию)
- Аргументы переменной длины (args, kwargs)
- Передача по значению и по ссылке
- Словарь в качестве аргументов (упаковка)
- Возвращаемые значения (return)
- Что можно возвращать
- Распаковка возвращаемых значений
- Пустая функция
- Чистые функции и побочные эффекты
- Lambda функции
- Docstring
- Аннотация типов
- Функции vs процедуры — в чем отличие?
- Время выполнения функции
- Вложенные функции и рекурсия
Функция — это фрагмент программного кода, который решает какую-либо задачу.
Его можно вызывать в любом месте основной программы. Функции помогают избегать дублирования кода при многократном его использовании. А также имеют ряд других преимуществ, описанных ниже.
Синтаксис
💁♀️ Простой пример: Вы торгуете мёдом, и после каждой продажи вам нужно печатать чек. В нём должно быть указано: название фирмы, дата продажи, список наименований проданных товаров, их количество, цены, общая сумма, а также сакраментальная фраза "Спасибо за покупку!".
Если не пользоваться функциями, всё придётся прописывать вручную. В простейшем случае программа будет выглядеть так:
print("ООО Медовый Гексагон")
print("Мёд липовый", end=" ")
print(1, end="шт ")
print(1250, end="р")
print("\nCумма", 1250, end="р")
print("\nСпасибо за покупку!")
А теперь представьте, что произойдёт, когда вы раскрутитесь, и покупатели станут приходить один за другим. В таком случае, чеки надо будет выдавать очень быстро. Но что делать, если вдруг нагрянет ваш любимый клиент и купит 10 сортов мёда в разных количествах? Далеко не все в очереди согласятся ждать, пока вы посчитаете общую сумму и внесёте её в чек.
Хорошо, что данный процесс можно легко оптимизировать с использованием функций.
def print_check(honey_positions):
sum = 0 # переменная для накопления общей суммы
print("ООО Медовый Гексагон\n")
# в цикле будем выводить название, количество и цену
for honey in honey_positions:
name = honey[0]
amount = honey[1]
price = honey[2]
print(f"{name} ({amount} шт.) - {price} руб.")
sum += amount * price # здесь же будем считать ещё и общую сумму
print(f"\nИтого: {sum} руб.")
print("Спасибо за покупку!")
Встаёт резонный вопрос: где же обещанное упрощение и куда подевались товары? Как раз для этого, мы и будем описывать состав покупки не напрямую в функции, а в отдельном списке кортежей. Каждый кортеж состоит из трёх элементов: название товара, количество и цена.
# (название, количество, цена за штуку)
honey_positions = [
("Мёд липовый", 3, 1250),
("Мёд цветочный", 7, 1000),
("Мёд гречишный", 6, 1300),
("Донниковый мёд", 1, 1750),
("Малиновый мёд", 10, 2000),
]
Теперь этот список передадим в функцию как аргумент, и самостоятельно считать больше не придётся.
print_check(honey_positions)
>
ООО Медовый Гексагон
Мёд липовый (3 шт.) - 1250 руб.
Мёд цветочный (7 шт.) - 1000 руб.
Мёд гречишный (6 шт.) - 1300 руб.
Донниковый мёд (1 шт.) - 1750 руб.
Малиновый мёд (10 шт.) - 2000 руб.
Итого: 40300 руб.
Спасибо за покупку!
Да, код стал более массивным. Однако теперь для печати чека вам не придётся самостоятельно вычислять итог. Достаточно лишь изменить количество и цену товаров в списке. Существенная экономия времени! Слава функциям!
Термины и определения
Ключевое слово def
в начале функции сообщает интерпретатору о том, что следующий за ним код — есть её определение. Всё вместе — это объявление функции.
# объявим функцию my_function()
def my_function():
# тело функции
Аргументы часто путают с параметрами:
- Параметр — это переменная, которой будет присваиваться входящее в функцию значение.
- Аргумент — само это значение, которое передается в функцию при её вызове.
# a, b - параметры функции
def test(a, b):
# do something
# 120, 404 — аргументы
test(120, 404)
Ключевая особенность функций — возможность возвращать значение
Для этого используется слово return
. Предположим, вы часто умножаете числа. Вы не осведомлены заранее, целые они или вещественные, но хотите, чтобы результат был целым всегда. Решим задачу с помощью функции:
# она будет принимать два множителя, а возвращать их округленное
# до целого числа произведение
def int_multiple(a, b):
product = a * b
# возвращаем значение
return int(product)
print(int_multiple(341, 2.7))
> 920
☝️ Главная фишка возвращаемых значений в том, что их можно использовать в дальнейшем коде: присваивать переменным, совершать с ними разные операции и передавать как аргументы в другие функции.
# найдём квадратный корень из возврата функции int_multiple
# во встроенную функцию sqrt() мы передали вызов int_multiple
print(math.sqrt(int_multiple(44, 44)))
> 44
Важность функций
Абстракция
Человек бежит, машина едет, корабль плывёт, а самолёт летит. Всё это — объекты реального мира, которые выполняют однотипные действия. В данном случае, они перемещаются во времени и пространстве. Мы можем абстрагироваться от их природы, и рассматривать эти объекты с точки зрения того, какое расстояние они преодолели, и сколько времени на это ушло.
Мы можем написать функцию, которая вычисляет скорость в каждом конкретном случае. Нам не важно, кто совершает движение: и для человека и для самолёта средняя скорость будет рассчитываться одинаково.
def calculate_speed(distance, time):
return distance / time
Это простой пример и простая функция, но абстракции могут быть куда более сложными. И именно тогда раскрывается настоящая сила функций. Вместо того чтобы решать задачу для каждого конкретного случая, проще написать функцию, которая находит решение для целого ряда однотипных, в рамках применяемой абстракции, объектов. В случае сложных и длинных вычислений, это повлечёт за собой значительное сокращение объёмов кода, а значит и времени на его написание.
Возможность повторного использования
Функции были созданы ради возможности их многократного применения. Код без функций превратился бы в огромное нечитаемое полотно, на порядки превышающее по длине аналогичную программу с их использованием.
Например, при работе с массивами чисел, вам нужно часто их сортировать. Вместо того чтобы реализовать простой алгоритм сортировки (или использовать встроенную функцию), вам пришлось бы каждый раз перепечатывать тело этой или похожей функции:
# пузырьковая сортировка
def bubble_sort(nums):
for i in range(0, len(nums) - 1):
for j in range(len(nums) - 1):
if nums[j] > nums[j + 1]:
nums[j], nums[j + 1] = nums[j + 1], nums[j]
return nums
Всего 10 таких сортировок, и привет, лишние 50 строк кода.
Модульность
Разбитие больших и сложных процессов на простые составляющие — важная часть, как кодинга, так и реальной жизни. В повседневности мы занимаемся этим неосознанно. Когда убираемся в квартире, мы пылесосим, моем полы и окна, очищаем поверхности от пыли и наводим блеск на всё блестящее. Всё это — составляющие одного большого процесса под названием "уборка", но каждую из них также можно разбить на более простые подпроцессы.
В программировании модульность строится на использовании функций. Для каждой подзадачи — своя функция. Такая компоновка в разы улучшает читабельность кода и уменьшает сложность его дальнейшей поддержки.
Допустим, мы работаем с базой данных. Нам нужна программа, которая считывает значения из базы, обрабатывает их, выводит результат на экран, а затем записывает его обратно в базу.
Без применения модульности получится сплошная последовательность инструкций:
# Database operation program
# Код для чтения данных из базы
# ...
# ...
# Код для выполнения операций над данными
# ...
# ...
# Код для вывода результата
# ...
# ...
# Код для записи данных в базу
# ...
# ...
Но если вынести каждую операцию в отдельную функцию, то текст главной программы получится маленьким и аккуратным.
def read_from_db():
# Код для чтения данных из базы
# ...
# ...
# ...
def operate_with_data():
# Код для выполнения операций над данными
# ...
# ...
# ...
def print_result():
# Код для вывода результата
# ...
# ...
# ...
def write_to_db():
# Код для записи данных в базу
# ...
# ...
# ...
# код основной программы
# Database operation program
read_from_db()
operate_with_data()
print_result()
write_to_db()
Это и называется модульностью.
Пространство имен
Концепция пространства имён расширяет понятие модульности. Однако цель — не облегчить читаемость, а избежать конфликтов в названиях переменных.
💁♀️ Пример из жизни: в ВУЗе учатся два человека с совпадающими ФИО. Их нужно как-то различать. Если сделать пространствами имён группы этих студентов, то проблема будет решена. В рамках своей группы ФИО этих студентов будут уникальными.
Объявление и вызов функций
Объявим функцию:
def hello():
print('Adele is cute')
После того как мы это сделали, функцию можно вызвать в любой части программы, но ниже самого объявления.
# код выполняется последовательно, поэтому сейчас интерпретатор
# не знает о существовании функции hello
hello()
def hello():
print('Adele is cute')
> NameError: name 'hello' is not defined
Поэтому стоит лишь поменять объявление и вызов местами, и всё заработает:
def hello():
print('Adele is cute')
hello()
> Adele is cute
Область видимости функций
Рассмотрим подробнее области видимости:
Локальная (L)
Локальная область видимости находится внутри def
:
def L():
# переменная i_am_local является локальной внутри L()
i_am_local = 5
Область объемлющих функций (E)
Объявили функцию e()
. Внутри неё объявили функцию inner_e()
. Относительно inner_e()
все переменные, объявленные в e()
будут относиться к области объемлющих функций. Такие переменные являются нелокальными в inner_e()
. Чтобы с ними взаимодействовать, нужно использовать ключевое слово nonlocal
:
def e():
x = 5
def inner_e():
nonlocal x
x = x + 1
return x
return inner_e()
print(e())
> 6
Глобальная (G)
Глобальная область видимости лежит за пределами всех def
.
# G
num = 42
def some_function(n):
res = n + num
return res
print(some_function(1))
> 43
Аргументы
Позиционные
Вспомним, аргумент — это конкретное значение, которое передаётся в функцию. Аргументом может быть любой объект. Он может передаваться, как в литеральной форме, так и в виде переменной.
Значения в позиционных аргументах подставляются согласно позиции имён аргументов:
nums = [42, 11, 121, 13, 7]
state = True
# в данном примере
# 1-я позиция "nums" -> parameter_1
# 2-я позиция "state" -> parameter_2
def test_params(parameter_1, parameter_2):
pass
# равнозначные варианты вызова функции
test_params(nums, state)
test_params([42, 11, 121, 13, 7], True)
Именованные
Пусть есть функция, принимающая три аргумента, а затем выводящая их на экран. Python позволяет явно задавать соответствия между значениями и именами аргументов.
def print_trio(a, b, c):
print(a, b, c)
print_trio(c=4, b=5, a=6)
> 6 5 4
При вызове соответствие будет определяться по именам, а не по позициям аргументов.
Необязательные параметры (параметры по умолчанию)
Python позволяет делать отдельные параметры функции необязательными. Если при вызове значение такого аргумента не передается, то ему будет присвоено значение по умолчанию.
def not_necessary_arg(x='My', y='love'):
print(x, y)
# если не передавать в функцию никаких значений, она отработает со значениями по умолчанию
not_necessary_arg()
> My love
# переданные значения заменяют собой значения по умолчанию
not_necessary_arg(2, 1)
> 2 1
Аргументы переменной длины (args, kwargs)
Когда заранее неизвестно, сколько конкретно аргументов будет передано в функцию, мы пользуемся аргументами переменной длины. Звёздочка "*" перед именем параметра сообщает интерпретатору о том, что количество позиционных аргументов будет переменным:
def infinity(*args):
print(args)
infinity(42, 12, 'test', [6, 5])
> (42, 12, 'test', [6, 5])
Переменная args
составляет кортеж из переданных в функцию аргументов.
Функции в питоне могут также принимать и переменное количество именованных аргументов. В этом случае перед названием параметра ставится "**
":
def named_infinity(**kwargs):
print(kwargs)
named_infinity(first='nothing', second='else', third='matters')
> {'first': 'nothing', 'second': 'else', 'third': 'matters'}
Здесь kwargs уже заключает аргументы не в кортеж, а в словарь.
Передача по значению и по ссылке
В Python аргументы могут быть переданы, как по ссылке, так и по значению. Всё зависит от типа объекта.
Если объект неизменяемый, то он передаётся в функцию по значению. Неизменяемые объекты это:
- Числовые типы (int, float, complex).
- Строки (str).
- Кортежи (tuple).
num = 42
def some_function(n):
# в "n" передается значение переменной num (42)
n = n + 10
print(n)
some_function(num)
print(num) # "num" по прежнему содержит 42
>
52
42
Изменяемые объекты передаются в функцию по ссылке. Изменяемыми они называются потому что их содержимое можно менять, при этом ссылка на сам объект остается неизменной.
В Python изменяемые объекты это:
- Списки (list).
- Множества (set).
- Словари (dict).
num = [42, 43, 44]
def some_function(n):
# в "n" передается ссылка на переменную "num".
# "n" и "num" ссылаются на один и тот же объект
n[0] = 0
print(n)
some_function(num)
print(num) # "num" изменился
>
[0, 43, 44]
[0, 43, 44]
Будьте внимательны при передаче изменяемых объектов. Одна из частых проблем новичков.
💭 В функциональном программировании существует понятие "функциями с побочными эффектами" — когда функция в процессе своей работы изменяет значения глобальных переменных. По возможности, избегать таких функций.
Словарь в качестве аргументов (упаковка)
Передаваемые в функцию аргументы можно упаковать в словарь при помощи оператора "**":
def big_dict(**arguments):
print(arguments)
big_dict(key='value')
> {'key': 'value'}
Возвращаемые значения (return)
Что можно возвращать
Функции в Python способны возвращать любой тип объекта.
Распаковка возвращаемых значений
В Питоне поддерживается возврат функциями сразу несколько значений. Достаточно перечислить их через запятую после инструкции return
. Возвращаемым типом будет кортеж (tuple
), который можно распаковать в переменные.
def calculate(num1, num2):
return num1 + num2, num1 - num2, num1 * num2
# для так называемой распаковки нескольких значений
# их следует присвоить равному количеству аргументов
res1, res2, res3 = calculate(7, 6)
print(res1, res2, res3)
> 13 1 42
print(type(calculate(7, 6)))
<class 'tuple'>
☝️ Обратите внимание, что количество возвращаемых значение в кортеже должно совпадать с количеством переменных при распаковке. Иначе произойдет ошибка:
def calculate(num1, num2):
return num1 + num2, num1 - num2
# для так называемой распаковки нескольких значений
# их следует присвоить равному количеству аргументов
res1, res2, res3 = calculate(7, 6)
print(res1, res2, res3)
>
ValueError: not enough values to unpack (expected 3, got 2)
Пустая функция
Иногда разработчики оставляют реализацию на потом, и чтобы объявленная функция не генерировала ошибки из-за отсутствия тела, в качестве заглушки используется ключевое слово pass
:
def empty():
pass
Чистые функции и побочные эффекты
Немного функционального программирования. Есть такие функции, которые при вызове меняют файлы и таблицы баз данных, отправляют данные на сервер или модифицируют глобальные переменные. Всё это — побочные эффекты.
У чистых функций побочных эффектов нет. Такие функции не изменяют глобальные переменные в ходе выполнения, не рассылают и не выводят на печать никакие данные, не касаются объектов, и так далее.
Чистые функции производят вычисления по заданным аргументам и возвращают зависящий только от них самих результат.
Lambda функции
Кроме инструкции def
в питоне можно создавать объекты функций в виде выражений. Так называемые анонимные функции создаются с помощью инструкции lambda
. Чаще всего их применяют для получения встроенной функции или же для отложенного выполнения фрагмента программного кода.
lambda_test = lambda a, b: pow(a, b)
print(lambda_test(2, 4))
> 16
Docstring
Документировать код — особое искусство. Оно существует параллельно с разработкой и сопоставимо с ней по важности. Поэтому нередко документации в программе больше, чем самого кода.
Когда над проектом работает большая команда, а может и не одна, да и еще и много лёт подряд, то значение и важность документации возрастают прямо пропорционально.
Аннотация типов
Python — язык с динамической типизацией. По этой причине вполне возможны ситуации, когда вопреки ожиданиям разработчика в функцию подаются, например, не целые числа, а, допустим, строки. Чтобы отслеживать подобные случаи и сильнее контролировать процесс выполнения программы, была изобретена аннотация типов.
С помощью аннотации типов мы указываем, что параметры в функции имеют строго определенный тип.
def prod(a: int, b: int) -> int:
return a * b
В этой функции мы показали, что аргументы и результат должны быть целыми. Если передать float
, то функция выполнится как обычно, однако IDE предупредит нас, что было получено неожиданное значение.
При этом интерпретатор считывает аннотации типов, но никак их не обрабатывает.
Функции vs процедуры — в чем отличие?
Для языка нет различий между функциями и процедурами. Но с точки зрения программиста — это разные сущности.
Отличие в том, что функции возвращают значение, а процедуры — нет. Отсюда вытекают и разные области их применения и смысл использования. Скажем, производить некие вычисления в процедуре бессмысленно.
def proc(i, j):
pow(i, j)
proc(1, 200)
Она успешно отработает, но не вернёт нам результат. Поэтому добавляем ключевое слово return
, и вот этот код обретает смысл:
def func(i, j):
return pow(i, j)
print(func(3, 2))
> 9
И наоборот, оформлять набор инструкций, выполняющий некую обработку, в виде функции также лишено смысла:
def print_low_word(word):
print(word.lower())
return 0
s = 'GOOD'
print_low_word(s)
> good
Возвращаемое значение не представляет собой никакой ценности, поэтому print_low_word(s)
лучше оформить, как процедуру.
Время выполнения функции
Чтобы оценить время выполнения функции, можно поместить её вызов внутрь следующего кода:
from datetime import datetime
import time
start_time = datetime.now()
# здесь вызываем функцию
time.sleep(5)
print(datetime.now() - start_time)
Вложенные функции и рекурсия
Функции, которые объявляются и вызываются внутри других функций, называются вложенными.
def outerFunc():
def firstInner():
print('This is first inner function')
def secondInner():
print('This is second inner function')
firstInner()
secondInner()
outerFunc()
> This is first inner function
> This is second inner function
Рекурсия является частным случаем вложенной функции. Это функция, которая вызывает саму себя.
# посчитаем сумму чисел от 1 до num
def sum_from_one(num):
if num == 1:
return 1
return num + sum_from_one(num - 1)
print(sum_from_one(5))
> 15
😉
Понятно изложен большой объём материала. Спасибо
блин, я так долго подступался к функциям, думая что это сложно... это наверно 5 или 6 ресурс, и тут всё идеально просто и ясно разложено по полочкам. автору 5 звезд!!!
Последний код что-то не особо
Отлично. но считаю что рекурсии нужно посвятить больше внимания)
cool
супер!