- Синтаксис
- Термины и определения
- Важность функций
-
Абстракция
-
Возможность повторного использования
-
Модульность
-
Пространство имен
- Объявление и вызов функций
- Область видимости функций
-
Локальная (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
😉
#статьи
-
0
База каждого уважающего себя питониста.
Иллюстрация: Оля Ежак для Skillbox Media
Пишет про digital и машинное обучение для корпоративных блогов. Топ-автор в категории «Искусственный интеллект» на Medium. Kaggle-эксперт.
Функция (def) в Python — это фрагмент кода, который выполняет определённые операции и отдаёт результат. Его можно написать один раз и переиспользовать во всей программе. В этой статье рассказываем, как работают функции в Python, чем отличаются от процедур и зачем им нужны аргументы.
Содержание
- Синтаксис функций
- Область видимости функций
- Аргументы функций
- Возвращаемые значения (return)
- Lambda-функции
- Процедуры и функции: различия
В Python, как и в других языках программирования, есть особые правила для создания функций. Если их не соблюдать, то интерпретатор не сможет правильно обработать код и, скорее всего, выдаст ошибку.
Для объявления функции используют ключевое слово def (от англ. define — определить, обозначить). В общем виде объявление выглядит следующим образом:
def имя_функции (аргументы): тело_функции return результат
Здесь:
- имя_функции ― название, с помощью которого можно вызывать функцию в коде;
- аргументы ― значения, которые функция принимает на вход. Это поле может быть пустым;
- тело_функции ― набор инструкций, которые выполняются при вызове;
- результат ― значения, которые функция возвращает при завершении работы.
Важно соблюдать правила форматирования, включая скобки, отступы и двоеточия. Иначе программа вернёт ошибку.
Рассмотрим на примере функцию, которая находит сумму двух чисел и возвращает результат вычислений:
def sum(a, b): return a + b
Функцию в Python можно создать один раз, а после вызывать её в коде неограниченное количество раз. Это позволяет экономить время и сокращает количество строк в проекте.
Чтобы вызвать функцию, надо ввести её название и передать аргументы в скобках. В общем виде синтаксис вызова выглядит так:
имя_функции(аргументы)
Теперь вызовем функцию для нахождения суммы, которую мы создали ранее. Для этого введём название sum и в скобках передадим переменные a и b. Результат значения запишется в переменную c:
c = sum(a, b)
Теперь рассмотрим более сложный пример. Представим, что мы хотим написать программу, которая запрашивает результаты ЕГЭ по разным предметам, считает итог и сообщает, насколько хорошо ученик справился с экзаменами.
# Запрашиваем результаты ЕГЭ math = int(input()) russian = int(input()) informatics = int(input()) # Объявляем функцию, которая принимает результаты ЕГЭ, считает сумму и возвращает результат def passed (math, russian, informatics): total = math + russian + informatics if 120 <= total < 210: print('Хорошо') elif 210 <= total < 240: print('Очень хорошо') elif total >= 240: print('Отлично') else: print('Неудовлетворительно') return total # Вызываем функцию и выводим результат print(passed(math, russian, informatics)) # Вывод: 40 80 80 Хорошо 200
В Python область видимости определяет, где и какие переменные можно использовать внутри функции. Есть нескольких видов переменных.
Внутри функции можно объявить временные переменные, которые помогают в промежуточных вычислениях. Они существуют только внутри тела функции, их нельзя использовать в других местах проекта. Это и есть локальная область видимости.
В примере ниже переменная c объявлена внутри функции sum. Её можно использовать только внутри функции, если попробовать сделать это в другом месте, то Python выдаст ошибку:
def sum(a, b): c = a + b return c
Функции бывают вложенными, когда одна находится внутри другой как матрёшка. В таком случае у внутренней функции есть доступ к переменным, определённым во внешней. Наоборот, это правило не будет работать.
Напишем код счётчика, который подсчитывает количество вызовов функции. Используем для этого вложенную архитектуру:
def make_counter(): # Объявляем переменную count в объемлющей функции count = 0 def counter(): # Указываем, что count находится в объемлющей функции nonlocal count count += 1 return count return counter # Создаём счётчик call_counter = make_counter() # Пример использования счётчика print(call_counter()) # Вывод: 1 print(call_counter()) # Вывод: 2 print(call_counter()) # Вывод: 3
В этом примере вложенная функция использует переменную count для вычислений. Чтобы программа не приняла переменную за локальную, как в примере выше, используют ключевое слово nonlocal. Это полезно, если, как здесь, мы хотим обновить значение переменной только внутри вложенной функции.
Переменные, определённые вне функций, находятся в глобальной области видимости. Это значит, что они видны во всей программе и доступны всем функциям. Если надо изменить значение глобальной функции внутри функции, то необходимо использовать ключевое слово global.
Например, нам нужно написать программу для кондитерской, которая ведёт учёт изготовленных тортов. Каждый раз, когда мы продаём новые торты, мы будем изменять переменную cake:
# Глобальная переменная, которая обозначает количество сделанных тортов cake_count = 10 def modify_cake(): global cake_count # Изменяем значение глобальной переменной cake_count = 15 modify_cake() print(modify_cake) # Вывод: 15
В Python функции могут работать с разными типами аргументов. Это делает язык гибким и позволяет решать разные задачи.
Этот тип встречается чаще всего. Представьте книжный магазин, где уже есть разные секции: «История», «Литература», «Программирование». Сотрудники расставляют новинки по соответствующим секциям. Так будет легче найти нужную книгу.
Так и здесь: значения передаются в функцию в том порядке, в каком указано в функции. Иначе возникнет ошибка. Если функция принимает несколько аргументов, нужно разделить их запятой:
# Функция принимает два аргумента в таком порядке: секция и книгаdef search(section, book): print (‘Ваша книга: ‘, section, book) # Передаём данные в том же порядкеsearch (‘История’, ‘История государства Российского’)
Если использовать имена параметров при вызове функции, то можно передавать аргументы в произвольном порядке.
# Функция принимает два аргумента в таком порядке: секция и книга def search(section, book): print('Ваша книга: ', section, book) # Передаём данные в том же порядке search('История','История государства Российского')
Python позволяет определять функции, даже если мы не знаем, сколько аргументов она должна принимать:
- *args используют, когда неясно, сколько позиционных аргументов у нас есть. Звёздочка * перед args указывает на то, что все позиционные аргументы, переданные при вызове функции, должны быть собраны в кортеж tuple и присвоены args.
- **kwargs используют, чтобы передать именованные аргументы в виде словаря (dictionary), когда мы не знаем, сколько их у нас. Две звёздочки (**) перед kwargs означают, что все именованные аргументы должны быть собраны в словарь и присвоены kwargs.
Например, создадим функцию greet с использованием *args и **kwargs, которая будет приветствовать людей, используя их имена и дополнительную информацию о настроении.
def greet(greeting, *args, **kwargs): for name in args: message = f'{greeting}, {name}!' if 'mood' in kwargs: message += f 'Ты чувствуешь себя {kwargs['mood']}.' print(message) # Пример использования greet('Привет', 'Катя', 'Лена', 'Вика', mood= 'весело') greet('Здравствуйте', 'Саша, 'Таня')
Функция greet принимает приветствие, список имён *args и дополнительные параметры **kwargs. Проходит через каждое имя в args и формирует приветственное сообщение. Если в kwargs есть ключ mood, то в сообщение добавляется информация о настроении.
Иногда в функциях задают значения по умолчанию. Функция будет использовать их, если не указано, что нужно использовать другой аргумент.
Например, если бы книжный магазин был интернет-магазином, каждый посетитель получал бы одинаковое приветственное сообщение. Но авторизированные пользователи, то есть те, кто передал свои данные магазину, получают персональное приветствие.
def greet(name = 'Посетитель', message = 'Привет, '): print(message, name) # Вызов функции без указания значения по умолчанию greet() # Вывод: Привет, Посетитель # Вызов функции с изменённым значением по умолчанию greet('Артём') # Вывод: Привет, Артём
В этом примере функция greet принимает два аргумента: name и message. У аргумента message значение по умолчанию Привет, а у name ― посетитель. Если при вызове функции не указывается значение, то используется значение по умолчанию.
Один из частых вопросов, который волнует новичков в Python, — как передаются переменные в функции и методы: по значению или по ссылке.
- По значению — это когда в функцию передаётся копия значения переменной, а не сама переменная. А значит, если вы вносите изменения в этот аргумент при вызове функции, на оригинальную переменную это не влияет.
- По ссылке означает, что в функцию передаётся ссылка на тот же объект в памяти, который содержит переменную, и любые изменения, внесённые внутри функции в этот аргумент, будут отражаться на оригинальной переменной.
В Python аргументы передаются всегда по ссылке, но для неизменяемых типов данных, например чисел или строк, это может выглядеть как передача по значению. Потому что вы не можете изменить сам объект внутри функции.
Для изменяемых типов данных, например списков или словарей, изменения, внесённые внутри функции, отражаются на оригинальном объекте.
Пример:
def add_item(my_list, item): # Изменяем переданный список my_list.append(item) numbers = [1, 2, 3] add_item(numbers, 4) print(numbers) # Вывод: [1, 2, 3, 4]
Здесь видно, что мы внесли изменения в список, когда взяли его в качестве аргумента, и он теперь выглядит по-другому.
С помощью словаря (**kwargs) можно передавать произвольное количество именованных аргументов.
Например, переменную можно использовать, чтобы выводить персональную информацию для каждого пользователя интернет-магазина. У кого-то будут заполнены графы Ф. И. О., Возраст и Место жительства, а кто-то поленился указать город ― тогда можно вывести только ту информацию, что есть.
def print_person_info(**kwargs): print(f'Имя: {kwargs.get('name')}') print(f'Возраст: {kwargs.get('age')}') # Передача словаря в качестве именованных аргументов person_1 = {'name': 'Аня', 'age': 10} person_2 = {'name': 'Борис'} person_3 = {'age': 12} person_4 = {} print_person_info(**person_1)
В Python ключевое слово return используется для возврата значения из функции. Полученное значение можно дальше использовать в программе.
В примере ниже функция add принимает два аргумента a и b, складывает их и с помощью return возвращает сумму a + b:
def add(a, b): return a + b result = add(3, 5) print(result) # Вывод: 8
Если не использовать в функции ключевое слово return, она по умолчанию вернёт None.
Лямбда-функции (lambda-функции) ― это безымянные функции, которые могут быть определены в одной строке кода. Выше мы упомянули, что название функции используют, чтобы вызвать функцию повторно. Лямбда-функцию нельзя переиспользовать ― у неё нет имени, по которому её можно вызвать. Обычно их используют там, где требуется передать небольшую функцию в качестве аргумента.
Для определения лямбда-функций используют ключевое слово lambda. После него перечисляют параметры, а после двоеточия указывают выражение, результат которого функция вернёт. В общем плане это выглядит так:
lambda аргументы: выражение
В качестве примера рассмотрим лямбда-функцию, которая проверяет чётность числа и выводит True или False. В качестве параметра передаётся переменная num, а после двоеточия указано выражение num % 2 == 0:
is_even = lambda num: num % 2 == 0 print(is_even(4)) # Вывод: True print(is_even(7)) # Вывод: False
Слова «процедура» и «функция» в Python часто обозначают одно и то же — блок кода для выполнения определённой задачи. Однако есть различия.
- Процедура ― фрагмент кода, который выполняет определённую задачу или действие, но ничего не возвращает. Она может принимать аргументы, выполнять операции с этими аргументами и изменять состояние программы или выполнять некоторые действия без явного возврата значения.
Например, greet из примеров выше ― это процедура, которая принимает имя в качестве аргумента и выводит приветствие на экран. Она не возвращает никакого значения: её цель только выполнить действие.
- Функция ― тоже фрагмент кода, который выполняет определённую задачу или действие. Но она возвращает результат с помощью ключевого слова return.
В Python нет строгого различия между процедурами и функциями. В коде их определяют одним и тем же ключевым словом def.
- Функции в Python объявляют с помощью ключевого слова def, за которым следует имя функции, круглые скобки для аргументов и двоеточие. Тело функции пишется с отступом.
- Python поддерживает несколько видов аргументов в функциях, включая позиционные, именованные, аргументы со значением по умолчанию, а также переменное количество аргументов с помощью *args и **kwargs.
- Локальные переменные видны только внутри функции, глобальные доступны во всём коде, а вложенные функции имеют доступ к переменным друг друга.
- Функции могут возвращать значения с помощью ключевого слова return. Если оно отсутствует, то функция возвращает None.
- Помимо стандартных функций, Python поддерживает создание анонимных функций с помощью ключевого слова lambda, они могут содержать лишь одно выражение.
- В Python нет явного различия между функциями и процедурами.
Бесплатный курс: «Быстрый старт в Python»
Начать учиться
Понятие функции в Python
Несмотря на то, что формально функции мы еще не рассматривали, некоторое понятие о них мы уже имеем. Ведь ранее мы не только использовали готовые встроенные функции и различные методы
типов, но еще и определяли простейшие собственные функции. Теперь же настало время рассмотреть этот вопрос более подробно. Итак.
Функция в Python (от англ. function) –
это блок программного кода на языке Python, который определяется один раз и далее может быть использован многократно.
Каждая хорошо спроектированная функция по сути решает какую-то одну конкретную задачу, что дает возможность программистам разбивать сложную систему на достаточно простые и легко управляемые
части. При этом достаточно всего лишь один раз определить функцию и далее ее можно будет использовать практически в любом месте программы, зачастую заменяя довольно объемные избыточные копии
блоков одного и того же программного кода вызовами созданной ранее функции. Как следствие, при необходимости внесения изменений в код, делать это придется уже не во многих местах программы,
а только в одном – в теле самой функции (см. пример №1).
Таким образом функции можно смело назвать основными программными структурами языка Python, которые позволяют существенно уменьшить время и трудозатраты на
разработку приложений, а также их дальнейшее сопровождение за счет следующих преимуществ:
- дают возможность разбивать сложную систему на небольшие и легко управляемые части, каждая из которых может разрабатываться отдельно,
- обеспечивают многократное использование программного кода,
- уменьшают его избыточность,
- повышают читабельность исходного кода,
- упрощают его редактирование.
Создание и вызов функций в Python
Для создания функций в Python предназначена инструкция def, которая создает объект функции и связывает его с именем.
В общем виде инструкция имеет следующий формат:
def <Имя функции>(arg_1, arg_2, ..., arg_n):
<Тело функции>
return <Возвращаемое значение>
Как видим, строка заголовка инструкции определения функции начинается с ключевого слова def, затем через пробел указывается имя функции и следующие за ним
обязательные круглые скобки, внутри которых через запятую перечисляются передаваемые функции аргументы (их еще называют параметрами). Можно определять функции и без аргументов, но круглые
скобки при этом все равно нужно указывать. Завершается строка заголовка обязательным двоеточием. Далее следует тело функции, представляющее собой обычный блок программного кода, который будет
выполняться каждый раз при вызове функции. При этом, если тело функции состоит из одной простой инструкции, его можно записывать вслед за двоеточием в строке заголовка. В более сложных
случаях инструкции тела функции записываются с отступами в соответствии с общепринятыми правилами.
Помимо обычных инструкций в любом месте тела функции может содержаться необязательная инструкция return, которая предназначена для выхода из функции и
возврата результата вызывающей программе в месте вызова функции. В случае отсутствия инструкции return функция будет завершать свою работу по достижении
потоком управления конца тела функции. Однако технически функция все равно будет неявно возвращать результат в виде объекта None, который обычно просто
игнорируется.
Также для использования в функциях предназначена специальная инструкция yield, которая превращает
обычную функцию в функцию-генератор. Однако здесь мы заострять внимание на ней не будем, поскольку данный вопрос был подробно рассмотрен нами в предыдущем параграфе.
После того, как функция будет определена, ее можно начинать использовать, многократно вызывая практически в любое время и в любом месте скрипта. Осуществляются такие вызовы при помощи имени
функции и круглых скобок с передаваемыми в функцию значениями аргументов, которые также должны перечисляться через запятую (см. пример №1).
Код
Результат
pythonCodes
# Функция определяется один раз.
def pow_func(x, y):
# Возводим x в степень y.
p = x**y
# Возвращаем результат вызывающей программе.
return p
# После создания функцию можно вызывать.
res_1 = pow_func(3, 3)
# Получим 27, т.е. 3 в кубе.
print('pow_func(3, 3) ->', res_1, end='\n\n')
# При этом вызовы можно осуществлять многократно.
res_2 = pow_func(4, 2)
# Выведет 16, т.е. 4 в квадрате.
print('pow_func(4,2) ->', res_2)
pow_func(3, 3) -> 27
pow_func(4,2) -> 16
Пример №1. Создание и вызов функций в Python (часть 1).
Следует иметь в виду, что в отличие от некоторых других языков программирования в Python функции становятся доступными для использования только после того,
как до них дойдет поток выполнения программы. Если попытаться вызвать функцию до ее определения в коде программы, интерпретатор возбудит исключение. При этом создавать функции можно
практически везде, где могут появляться инструкции, включая условные инструкции и определения других функций (см. пример №2).
Код
Результат
pythonCodes
# Вызывать ф-цию можно только после определения.
# local variable func_1 referenced before assignment.
# func_1()
# Я ввел 1 (попробуйте ввести, например, -1).
n = int(input())
# Определяем ф-цию в зависимости от условия.
if n >= 0:
# Определяем 1-ю функцию.
def func_1(): print('func_1 определена!')
else:
# Определяем 2-ю функцию.
def func_2(): print('func_2 определена!')
# Вызов разрешен, т.к. func_1 уже определена.
# Выведет 'func_1 определена!'.
func_1()
# Вызов запрещен, т.к. func_2 определена не была.
# local variable 'func_2' referenced before assignment.
func_2()
func_1 определена!
local variable 'func_2' referenced before assignment
Пример №2. Создание и вызов функций в Python (часть 2).
Аргументы функций в Python
Как было сказано выше, в случаях, когда функция определяется с аргументами, их имена перечисляются через запятую в круглых скобках после имени определяемой функции. Далее, в моменты вызовов
функции, все определенные имена автоматически становятся ее локальными переменными, которым присваиваются переданные в функцию значения. При этом по умолчанию сопоставление аргументов
производится слева направо в соответствии с их позициями, а самих аргументов допускается передавать ровно столько, сколько имен было указано в заголовке функции при ее определении
(см. пример №3).
Код
Результат
pythonCodes
# Определяем и вызываем функцию без аргументов.
def func_1(): print('Ok')
# Выведет 'Ok'.
func_1()
# ...func_1() takes 0 positional arguments but 1 was given.
# func_1(57)
# Функция с тремя позиционными аргументами.
def func_2(a, b, c): return a + b + c
# При вызове должны быть переданы все 3 аргумента.
print(func_2(2, 33, 87))
# ...func_2() missing 1 required positional argument: c.
# func_2(4, 8)
Ok
122
Пример №3. Передача аргументов функциям (часть 1).
Как видим, несоответствие количества передаваемых аргументов при вызове функции с количеством указанных имен аргументов при ее определении однозначно приводит к ошибке. Поэтому в ситуациях,
когда точное количество передаваемых аргументов заранее неизвестно, следует определять и вызывать функции, используя один из доступных в Python специальных
режимов сопоставления аргументов, которые мы сейчас и перечислим.
-
def func(arg_1, arg_2, …): – рассмотренный нами стандартный случай определения функции. Во время вызова такая функция ожидает получить строго
определенное количество аргументов, сопоставление которых будет происходить либо по их позициям, либо по именам. При этом в случае использования в вызове функции именованных аргументов
их разрешается указывать в любом порядке, но только после того, как будут переданы все имеющиеся позиционные аргументы, которые в любом случае будут сопоставляться как и положено, т.е.
слева направо в соответствии с их позициями (см. пример №4).
Код
Результат
pythonCodes
# Определяем функцию с тремя позиционными аргументами.
def func(x, y, z): return x/y + z
# Передаем три позиционных аргумента.
# Выведет func(8, 2, 5) -> 9.0.
print('func(8, 2, 5) ->', func(8, 2, 5))
# Для некоторых арг-тов указываем имена, но сперва в любом
# случае должны идти позиционные арг-ты. Выведет 9.0.
print('func(x=8, y=2, z=5) ->', func(x=8, y=2, z=5))
print('func(y=2, x=8, z=5) ->', func(y=2, x=8, z=5))
print('func(z=5, x=8, y=2) ->', func(z=5, x=8, y=2))
print('func(8, y=2, z=5) ->', func(8, y=2, z=5))
print('func(8, z=5, y=2) ->', func(8, z=5, y=2))
print('func(8, 2, z=5) ->', func(8, 2, z=5))
# Именованные аргументы разрешается передавать только
# после позиционных. Так что везде получаем ошибки.
# print(func(x=8, 2, 5))
# print(func(8, y=2, 5))
# print(func(x=8, 2, z=5))
func(8, 2, 5) -> 9.0
func(x=8, y=2, z=5) -> 9.0
func(y=2, x=8, z=5) -> 9.0
func(z=5, x=8, y=2) -> 9.0
func(8, y=2, z=5) -> 9.0
func(8, z=5, y=2) -> 9.0
func(8, 2, z=5) -> 9.0
Пример №4. Стандартная передача аргументов функциям.
-
def func(arg_1, arg_2, …, arg_v_1=val_1, arg_v_2=val_2, …) – эта форма записи используется для определения функций, у которых
предусмотрены аргументы со значениями по умолчанию. Перечисляются такие аргументы после обычных позиционных аргументов (если они имеются), а их наличие позволяет
вызывать функцию без передачи им значений, т.к. в таком случае будут просто использованы значения по умолчанию. Во всем остальном для таких функций справедливы
правила стандартного режима передачи аргументов (см. пример №5).
Код
Результат
pythonCodes
# Укажем для одного аргумента значение по умолчанию.
def func(x, y=1): return x/y
# Везде получим 4.0, т.к. 8/2 = 4.0.
print('func(8, 2) ->', func(8, 2))
print('func(x=8, y=2) ->', func(x=8, y=2))
print('func(8, y=2) ->', func(8, y=2), end='\n\n')
# Ошибка, т.к. первыми должны передаваться
# позиционные аргументы и только потом именованные.
# print('func(x=8, 2) ->', func(x=8, 2))
# Аргумент со значением по умолчанию можно не указывать.
# Везде получим 2.0, т.к. 2/1 = 2.0.
print('func(2) ->', func(2))
print('func(x=2) ->', func(x=2), end='\n\n')
# ...missing 1 required positional argument: x.
# print(func(y=2))
# print(func())
func(8, 2) -> 4.0
func(x=8, y=2) -> 4.0
func(8, y=2) -> 4.0
func(2) -> 2.0
func(x=2) -> 2.0
Пример №5. Передача аргументов со значениями по умолчанию.
-
def func(arg_1, …, arg_n, *args) – при вызовах функции, определенной таким способом, все дополнительные аргументы будут объединяться в кортеж с
именем args. Если передаваемых аргументов будет меньше n, будет вызвана ошибка. Сама запись
*args должна идти после перечисления всех позиционных аргументов, включая и позиционные аргументы со значениями по умолчанию
(см. пример №6).
Код
Результат
pythonCodes
# 1-й аргумент будет присвоен x, а все
# остальные будут собраны в кортеж y.
def func(x, *y): return x + sum(y)
# Получим 5, т.к. 5 + sum(()) = 5.
print('func(5) ->', func(5))
# Получим 8, т.к. 5 + sum((3,)) = 8.
print('func(5, 3) ->', func(5, 3))
# Получим 15, т.к. 5 + sum((3, 7)) = 15.
print('func(5, 3, 7) ->', func(5, 3, 7))
# Ошибка, т.к. х не имеет значения по умолчанию.
# print(func())
func(5) -> 5
func(5, 3) -> 8
func(5, 3, 7) -> 15
Пример №6. Сбор аргументов функции в кортеж.
-
def func(arg_1, …, arg_n, *args, named_arg_1, …, named_arg_m) – здесь также все дополнительные аргументы будут объединяться в
кортеж с именем args, но при этом все аргументы named_arg_1, …, named_arg_m должны будут
передаваться функции только именованными. Если собирать аргументы в кортеж не требуется, но функция проектируется для именованных аргументов, разрешается использовать
синтаксис def func(arg_1, …, arg_n, *, named_arg_1, …, named_arg_m), где все аргументы после звездочки, опять же, должны будут
передаваться функции только именованными (см. пример №7).
Код
Результат
pythonCodes
# z определяем именованным аргументом.
def func(x, *y, z): return x + sum(y) + z
# Получим 15, т.к. 5 + sum((7,)) + 3 = 15.
print('func(5, 7, z=3) ->', func(5, 7, z=3))
# Получим ошибку, т.к. именованные аргументы
# должны передаваться только после позиционных!
# print(func(5, z=7, 3))
# ...missing 1 required keyword-only argument: 'z'.
print('func(5, 7, 3) ->', func(5, 7, 3))
func(5, 7, z=3) -> 15
main..func() missing 1 required keyword-only argument: 'z'
Пример №7. Определение функции с именованными аргументами.
-
def func(arg_1, …, arg_n, *args, named_arg_1, …, named_arg_m, **named_args) – это наиболее общая форма определения функции с позиционными и
именованными аргументами: при вызовах такой функции все дополнительные позиционные аргументы будут объединяться в кортеж с именем args, а все
дополнительные именованные аргументы будут объединяться в словарь с именем named_args (см. пример №8).
Код
Результат
pythonCodes
# Собираем именованные аргументы в словарь.
def func(x, *, y, **z): print(x, y, z)
# Выведет 1 2 {'a': 3, 'b': 4}.
func(1, y=2, a=3, b=4)
# Выведет 1 2 {'z': 3, 'f': 4}.
func(1, y=2, z=3, f=4)
# Выведет 1 2 {}.
func(1, y=2)
1 2 {'a': 3, 'b': 4}
1 2 {'z': 3, 'f': 4}
1 2 {}
Пример №8. Сбор именованных аргументов в словарь.
Следует помнить, что форма записи arg=val в определении функции означает, что данный аргумент определяется со значением по умолчанию, а в вызове функции
запись представляет собой передачу аргумента по имени. Кроме того, формы записей *args и **named_args также имеют разный
смысл для определения и вызова функции: в первом случае они собирают аргументы в кортеж и словарь, а во втором случае указывают интерпретатору на необходимость распаковки последовательностей
в отдельные аргументы и пары (см. пример №9).
Код
Результат
pythonCodes
# Собираем аргументы в последовательности.
def func(*x, **y): print(x, y)
# Вывело (1, 2) {'a': 1, 'b': 2}.
func(1, 2, a=1, b=2)
# А здесь последовательности будем распаковывать.
def func(x, y): print(x, y)
# Равносильно func(1, 2). Вывело 1, 2.
func(*[1, 2])
# Равносильно func(x=1, y=2). Вывело 1, 2.
func(**{'x': 1, 'y': 2})
# Ошибка, т.к. мы определили имена x и y.
# ...got an unexpected keyword argument a.
# func(**{'a': 1, 'b': 2})
(1, 2) {'a': 1, 'b': 2}
1 2
1 2
Пример №9. Передача аргументов функциям (часть 2).
При первом прочтении информация о возможных режимах сопоставления аргументов может показаться несколько запутанной, но это только на первый взгляд. На самом деле все оказывается значительно
проще в особенности, если придерживаться пары простых правил:
-
в заголовке определяемой функции аргументы должны указываться в следующем порядке: позиционные аргументы (arg_1, arg_2, …),
позиционные аргументы со значениями по умолчанию (arg_v_1=val_1, arg_v_2=val_2, …),
аргумент со звездочкой для сбора дополнительных позиционных аргументов в кортеж (*args), именованные аргументы
(named_arg_1, named_arg_2, …), именованные аргументы со значениями по умолчанию
(named_arg_1=val_1, named_arg_2=val_2, …) и аргумент с двумя звездочками для сбора дополнительных именованных аргументов в словарь
(**named_args); -
при вызове функции порядок передачи аргументов должен быть следующим: обычные позиционные аргументы (arg_1, arg_2, …),
позиционные аргументы в форме *args (будут распакованы интерпретатором в значения arg_1, arg_2, …),
обычные именованные аргументы (named_arg_1=val_1, named_arg_2=val_2, …) и в самом конце именованные аргументы в форме
**named_args (будут распакованы интерпретатором в пары named_arg_1=val_1, named_arg_2=val_2, …).
Области видимости в Python
Как мы знаем, все имена в Python становятся доступны для использования только после того, как им будет присвоено какое-нибудь значение. Так вот, место, где
переменной присваивается значение, как раз и определяет область ее видимости. Но поскольку значения переменным могут быть присвоены в трех разных местах текущего модуля, то и областей
видимости выделяют тоже три.
-
Если присваивание переменной выполняется внутри инструкции def, переменная становится локальной для этой функции. Это касается как аргументов функции,
так и переменных, созданных в теле функции. Локальные переменные доступны только внутри своей функции, т.е. в текущей локальной области видимости, и
недоступны за ее пределами, включая другие локальные области видимости невложенных в нее функций. -
Если присваивание производится в пределах объемлющей инструкции def, переменная становится локальной для данной объемлющей функции и нелокальной для
данной вложенной функции. В общем случае такая переменная будет доступна внутри любой вложенной функции при отсутствии в ней локальной переменной с таким же именем. -
Если присваивание производится на верхнем уровне модуля (файла скрипта) за пределами всех инструкций def, переменная становится глобальной для
всего файла. В общем случае глобальная переменная будет доступна внутри любой функции текущего модуля при отсутствии в ней локальной переменной с
таким же именем (см. пример №10).
Код
Результат
pythonCodes
# Инициализировали глобальную переменную.
glob_var = 'glob_var'
# Выведет glob_var in global.
print(glob_var, 'in global', end='\n\n')
# Объявляем объемлющую функцию.
def func_1(loc_var):
# Локальная переменная для func_1.
loc_var_1 = 'loc_var_1'
# Выведет glob_var in func_1.
print(glob_var, 'in func_1')
# Выведет loc_var in func_1.
print(loc_var, 'in func_1')
# Выведет loc_var_1 in func_1.
print(loc_var_1, 'in func_1', end='\n\n')
# Объявляем вложенную функцию.
def func_2():
# Локальная переменная для func_2.
loc_var_2 = 'loc_var_2'
# Выведет glob_var in func_2.
print(glob_var, 'in func_2')
# Выведет loc_var_2 in func_2.
print(loc_var_2, 'in func_2')
# Выводим нелокальные для func_2.
# Выведет loc_var in func_2.
print(loc_var, 'in func_2')
# Выведет loc_var_1 in func_2.
print(loc_var_1, 'in func_2')
# Вызываем вложенную функцию func_2.
func_2()
# Вызываем объемлющую функцию.
func_1('loc_var')
glob_var in global
glob_var in func_1
loc_var in func_1
loc_var_1 in func_1
glob_var in func_2
loc_var in func_2
loc_var_1 in func_2
loc_var_2 in func_2
Пример №10. Области видимости в Python (часть 1).
Как видим, глобальная переменная glob_var доступна и в объемлющей функции, и во вложенной. Точно также локальная для функции
func_1 переменная loc_var доступна в локальной области вложенной функции func_2 (для
нее она является нелокальной переменной). Но если мы попробуем использовать локальные переменные в объемлющей или глобальной области видимости, то наверняка получим ошибку, т.к. локальные
переменные доступны только в своей области видимости (см. пример №11).
Код
Результат
pythonCodes
# Объявляем объемлющую функцию.
def func_1():
# Локальная переменная для func_1.
loc_var_1 = 'loc_var_1'
# Выведет loc_var_1 in func_1.
print(loc_var_1, 'in func_1')
# name 'loc_var_2' is not defined.
# print(loc_var_2)
# Объявляем вложенную функцию.
def func_2():
# Локальная переменная для func_2.
loc_var_2 = 'loc_var_2'
# Выведет loc_var_2 in func_2.
print(loc_var_2, 'in func_2', end='\n\n')
# Вызываем вложенную функцию func_2.
func_2()
# Вызываем объемлющую функцию.
func_1()
# name loc_var_1 is not defined.
# print(loc_var_1)
# name loc_var_2 is not defined.
# print(loc_var_2)
loc_var_1 in func_1
loc_var_2 in func_2
Пример №11. Области видимости в Python (часть 2).
Наличие обособленных локальных областей видимости и приоритет в них локальных переменных над глобальными и нелокальными переменными с теми же именами позволяет избежать неоднозначности и
конфликта имен. Этому содействует и невозможность использования для функций из одного и того же пространства имен локальных переменных одной функции в области видимости другой
(см. пример №12).
Код
Результат
pythonCodes
# Инициализировали глобальную переменную.
glob_loc_var = 'glob_loc_var in global'
# Выведет glob_loc_var in global.
print(glob_loc_var, end='\n\n')
# Объявляем 1-ю функцию.
def func_1():
# Локальная имеет приоритет над глоб. переменной.
glob_loc_var = 'glob_loc_var in func_1'
# Выведет glob_loc_var in func_1.
print(glob_loc_var)
# Локальная переменная для func_1.
loc_var_1 = 'loc_var_1 in func_1'
# Выведет loc_var_1 in func_1.
print(loc_var_1, end='\n\n')
# name 'loc_var_2' is not defined.
# print(loc_var_2)
# Объявляем 2-ю функцию.
def func_2():
# Локальная имеет приоритет над глоб. переменной.
glob_loc_var = 'glob_loc_var in func_2'
# Выведет glob_loc_var in func_2.
print(glob_loc_var)
# Локальная переменная для func_2.
loc_var_2 = 'loc_var_2 in func_2'
# Выведет loc_var_2 in func_2.
print(loc_var_2)
# name 'loc_var_1' is not defined.
# print(loc_var_1)
# Вызываем функции.
func_1()
func_2()
# name loc_var_1 is not defined.
# print(loc_var_1)
# name loc_var_2 is not defined.
# print(loc_var_2)
glob_loc_var in global
glob_loc_var in func_1
loc_var_1 in func_1
glob_loc_var in func_2
loc_var_2 in func_2
Пример №12. Области видимости в Python (часть 3).
Для случаев, когда возникает необходимость использования внутри функций именно глобальных или нелокальных переменных вместо локальных с теми же именами, в
Python предусмотрены инструкции global и nonlocal. Все что нужно – это просто
перечислить после ключевых слов требуемые имена через запятую (см. пример №13).
Код
Результат
pythonCodes
# Инициализируем глобальную переменную.
glob_var = 'Исходное значение glob_var'
# Выведет: Исходное значение glob_var.
print(glob_var)
# Объявляем объемлющую функцию.
def func_1():
# Будем использовать глобальную переменную.
global glob_var
# Изменяем значение глобальной переменной.
glob_var = 'glob_var изменена в func_1'
# Создаем локальную переменную для func_1.
loc_var = 'Исходное значение loc_var'
# Выведет: Исходное значение loc_var.
print(loc_var)
# Объявляем вложенную функцию.
def func_2():
# Будем использовать нелокальную переменную.
nonlocal loc_var
# Изменяем значение нелокальной переменной.
loc_var = 'loc_var изменена в func_2'
# Вызываем вложенную функцию func_2.
func_2()
# Выведет: loc_var изменена в func_2.
print(loc_var)
# Вызываем объемлющую функцию.
func_1()
# Выведет: glob_var изменена в func_1.
print(glob_var)
Исходное значение glob_var
Исходное значение loc_var
loc_var изменена в func_2
glob_var изменена в func_1
Пример №13. Использование инструкций global и nonlocal.
Из всего выше сказанного следует простое правило поиска имен: когда внутри функции выполняется обращение к неизвестному имени, интерпретатор пытается сначала отыскать его в локальной области
видимости, затем в локальной области любой объемлющей инструкции def или в выражении lambda и далее в глобальной области
видимости. Если искомого имени обнаружено не будет, интерпретатор выведет сообщение об ошибке.
Строго говоря, выделяют еще и встроенную область видимости, имена из которой автоматически добавляются в пространство имен глобальной области видимости текущего модуля. Сюда входят, например,
имена встроенных типов и функций. Поэтому говоря о глобальной области видимости без дополнительных оговорок, мы будем подразумевать и встроенную область видимости.
В конце пункта хотелось бы добавить, что получить сведения о локальных и глобальных переменных можно при помощи следующих встроенных функций:
-
globals() – возвращает словарь со значениями переменных из текущей глобальной области видимости модуля, в котором ключами служат имена этих переменных.
Стоит заметить, что функция возвращает такой словарь даже в тех случаях, когда она вызывается из локальных областей видимости функций и методов (см. пример
№14). -
locals() – обновляет и возвращает словарь со значениями переменных, использующихся в текущей локальной области видимости функции или метода, в котором
ключами служат имена этих переменных. Следует иметь в виду, что значения этого словаря изменять не стоит, т.к. изменённые значения все равно могут быть проигнорированы интерпретатором.
Кроме того, при использовании этой функции в глобальной области видимости модуля она вернет словарь с глобальными переменными также, как и функция
globals().
Код
Результат
pythonCodes
# Инициализируем 1-ю глобальную переменную.
glob_var_1 = 0.1
# Объявляем объемлющую функцию.
def func_1():
# Локальная переменная для func_1.
loc_var_1 = 1
# Словарь доступных локальных переменных для func_1.
print('func_1 locals:', locals())
# Словарь глобальных переменных.
print('globals:', globals())
# Объявляем вложенную функцию.
def func_2():
# Локальная переменная для func_2.
loc_var_2 = 2
# Словарь локальных переменных для func_2.
print('func_2 locals:', locals())
# Обновляем словарь локальных переменных для func_1.
print('func_1 locals:', locals())
# Вызываем вложенную функцию func_2.
func_2()
# Вызываем объемлющую функцию.
func_1()
# Словарь глобальных переменных.
print('globals:', globals())
# Выведет тоже самое.
print('globals:', locals())
# Инициализируем 2-ю глобальную переменную.
glob_var_2 = 0.2
# Обновляем словарь глобальных переменных.
print('globals:', globals())
func_1 locals: {'loc_var_1': 1}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>}
func_1 locals: {'loc_var_1': 1, 'func_2': .func_1..func_2 at 0x00000204AF7A8700>}
func_2 locals: {'loc_var_2': 2}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>, 'glob_var_2': 0.2}
Пример №14. Использование функций globals() и locals().
Как видно из примера, обе функции отображают в словаре только те переменные, которые известны интерпретатору на момент их вызова. Поэтому, если переменные объявляются в коде скрипта ниже
вызовов этих функций, их в возвращаемых словарях не будет.
lambda-выражения или анонимные функции в Python
Поскольку в Python инструкции не могут использоваться внутри выражений, для этих целей в язык была введена дополнительная возможность в виде специальных
конструкций, называемых lambda-выражениями и позволяющих создавать объекты функций там, где интерпретатор ожидает встретить выражение. Эти конструкции еще
называют анонимными или безымянными функциями, т.к. в отличие от инструкций def они хоть и создают функции, но не связывают
их с именами.
В общем виде lambda-выражение состоит из ключевого слова lambda, за которым перечисляются один или более аргументов
и после двоеточия записывается выражение, использующее эти аргументы:
lambda arg_1, arg_2, ..., arg_n: <выражение>.
Здесь стоит заметить, что после двоеточия записывается именно выражение, а не блок инструкций. По сути такое выражение сродни тому, что мы помещаем в инструкцию
return при определении обычной функции. Это конечно же делает lambda-выражения менее универсальными по сравнению с
инструкциями def, но такая реализация предусмотрена намеренно, т.к. lambda-выражения предназначены для создания простых
функций, которые бы не усложняли выражения, в то время как инструкции def представляют собой инструменты для решения более сложных задач
(см. пример №14).
Код
Результат
pythonCodes
# Обычная инструкция def неявно сразу
# связывает объект функции с именем.
def func(x, y, z): return x + y + z
# Выведет 30.
print(func(5, 10, 15))
# Создаем объект анонимной ф-ции и
# явно присваиваем его переменной.
lambda_1 = lambda x, y, z: x + y + z
# Выведет 30.
print(lambda_1(5, 10, 15), end='\n\n')
# Используем значения по умолчанию.
lambda_2 = lambda x=8, y=4: x/y
# Выведет 2.0.
print('8/4 =', lambda_2())
# Выведет 3.0.
print('12/4 =', lambda_2(12), end='\n\n')
# Используем сбор аргументов в кортеж.
lambda_3 = lambda *nums: sum(nums)
# Выведет 5.
print('sum((2, 3)) =', lambda_3(2, 3), end='\n\n')
# Используем простейшую логику.
lambda_3 = lambda x, y: x/y if y != 0 else None
# Выведет 4.0.
print('20/5 =', lambda_3(20, 5))
# Выведет None.
print('20/0 =', lambda_3(20, 0))
# Ошибка: инструкции в теле lambda запрещены.
# lambda_4 = lambda x, y: if y != 0: x/y
30
30
8/4 = 2.0
12/4 = 3.0
sum((2, 3)) = 5
20/5 = 4.0
20/0 = None
Пример №14. Использование lambda-выражений (часть 1).
Как видим, lambda-выражения действительно очень похожи на обыкновенные функции: в них также действуют специальные режимы сопоставления аргументов,
имеется тело с кодом, а на выходе получается объект функции. Однако, повторимся, в теле lambda-выражений может находиться только одно единственное
выражение, в то время как использование даже простейших инструкций вообще запрещено. Кроме того, возвращаемый объект функции нужно либо сразу вызывать при помощи круглых скобок, либо
присваивать переменной явно. Зато lambda-выражения можно смело использовать внутри других выражений (см. пример №15).
Код
Результат
pythonCodes
# Список объектов функций.
li = [lambda x: x**2, lambda x: x**3]
# Выведет 9.
print('3*3 =', li[0](3))
# Выведет 27.
print('3*3*3 =', li[1](3), end='\n\n')
# Создание и вызов внутри выражения.
expr = 5 + (lambda x, y: x**y)(5, 2)
# Выведет 30.
print(expr)
# Вложенное lambda-выражение (но зачем усложнять?).
expr = (lambda x: 7 + (lambda a, b: a**b)(x, 2))(5)
# Выведет 32.
print(expr)
3*3 = 9
3*3*3 = 27
30
32
Пример №15. Использование lambda-выражений (часть 2).
В нашем примере вложенное lambda-выражение, как и положено вложенным функциям, имеет доступ к переменной x в объемлющем
lambda-выражении. Но выглядит код явно замысловато! Поэтому в интересах удобочитаемости кода лучше такие трюки не использовать.
Рекурсивные функции в Python
Рекурсивная функция – это функция, которая содержит код вызова самой себя в целях организации циклического
процесса.
Рекурсивные функции являются вполне обычными функциями со всеми присущими им особенностями. Разница лишь в том, что внутри тела такие функции содержат вызов самой себя. Звучит это
как-то странно и непонятно, в особенности для начинающих программистов, поэтому стоит сразу же обратиться к практике. В качестве примера, давайте рассмотрим использование рекурсивной функции
в ходе решения классической задачи нахождения факториала числа (см. пример №16).
Код
Результат
pythonCodes
# Обычное объявление функции.
def recursive_func(n):
# Условие завершения рекурсий.
if n == 1:
# Возвращаем 1.
return n
# Иначе
else:
# используем вызов самой себя,
# уменьшив значение аргумента на 1.
return n*recursive_func(n-1)
# Выводим 3!, т.е. 6.
print(recursive_func(3))
# Выводим 5!, т.е. 120.
print(recursive_func(5))
6
120
Пример №16. Рекурсивные функции в Python.
В нашем примере код рекурсивной функции содержит условную инструкцию if, которая останавливает рекурсии (вызовы самой себя) только в том случае, когда
значение переданного ей аргумента будет равно единице. Если же при текущем вызове функции значение аргумента будет больше единицы, в блоке else условной
инструкции if будет предпринята попытка вычисления выражения n*recursive_func(n-1), использующее вызов функцией самой
себя. При этом прежде, чем вернуть конечный результат в точку вызова функции в программе, наша рекурсивная функция будет вызывать себя саму до тех пор, пока в выражении для возврата не
останется ее незавершенных вызовов. Например, для n=4, наш факториал схематически будет формироваться следующим образом:
return 4*recursive_func(4-1) -> return 4*(return 3*recursive_func(3-1)) ->
return 4*(return 3*(return 2*recursive_func(2-1))) ->
return 4*(return 3*(return 2*(return 1))) -> return 4*(return 3*(return 2*1)) ->
return 4*(return 3*2*1) -> return 4*3*2*1.
Следует заметить, что в языке Python рекурсии используются не так часто, как в некоторых других языках программирования, поскольку в
Python особое значение придается простым процедурным инструкциям вроде цикла while, который более естественно подходит
для решения аналогичных задач. Тем не менее рекурсии могут быть весьма полезными для реализации обхода сложных структур с произвольной организацией данных, поэтому взять их на вооружение
однозначно стоит.
Декораторы функций в Python
Декоратор (от англ. decorator) – это
функция, которая используется в качестве «обертки» другой функции с целью изменения ее поведения или расширения функциональности без непосредственного изменения кода самой функции.
Декораторы могут быть весьма полезны для расширения возможностей функций из сторонних библиотек, код которых мы не можем изменять, а также для изменения поведения или расширения
функциональности методов класса (о классах мы поговорим чуть позже). Все что нам нужно, это написать собственный или использовать уже готовый декоратор с требуемой дополнительной
функциональностью, передав ему объект целевой функции или метода для декорирования.
В качестве примера давайте создадим собственный простейший декоратор и на его основе познакомимся с этим понятием поближе (см. пример №17).
Код
Результат
pythonCodes
# Объект ф-ции передается декоратору в качестве аргумента.
def decorator_func(func):
# Внутри декораторы содержат функцию-обертку, которая
# изменяет поведение декорируемой функции func.
def wrapper_func(arg):
# Добавляем подсказку.
print('Начало выполнения функции!')
# Вызываем саму декорируемую функцию.
print(func(arg))
# Добавляем еще одну подсказку.
print('Выполнение функции закончено!')
# Возвращаем объект измененной func.
return wrapper_func
# Определим обычную польз. функцию.
def my_func(arg):
# Просто возвращаем аргумент.
return arg
# Получаем объект измененной функции my_func.
my_func_1 = decorator_func(my_func)
# Вызываем измененную функцию.
my_func_1('Выполняюсь!')
print()
# Декорируем встроенную функцию sum.
my_func_2 = decorator_func(sum)
# Вызываем измененную функцию.
my_func_2([1, 2, 3])
Начало выполнения функции!
Выполняюсь!
Выполнение функции закончено!
Начало выполнения функции!
6
Выполнение функции закончено!
Пример №17. Декораторы функций (часть 1).
Итак, в нашем примере мы создали декоратор, который добавляет декорируемым функциям сообщения о старте и финише их выполнения, а также выводит возвращаемые функциями
значения на экран. При этом мы не вносили никаких изменений в код декорируемых функций. Все изменения и дополнения произошли внутри тела декоратора, в котором мы определили
специальную функцию-обертку, содержащую дополнительный код и вызов исходной версии функции. Далее, использовав объект функции-обертки в качестве возвращаемого значения декоратора и
присвоив вызов декоратора переменной, мы получили готовый к использованию объект усовершенствованной версии переданной декоратору в качестве аргумента функции.
Таким образом, декораторы функций в общем случае принимают на вход исходную версию какой-либо функции или метода, а возвращают обертку переданной функции или метода, но уже с дополнительной
функциональностью. При этом в случаях, когда исходная версия функции становится больше не нужна, возвращаемую декоратором обертку присваивают переменной с именем исходной функции, тем самым
полностью заменяя ее.
Поскольку в Python декораторы используются довольно часто, для упрощения процедуры получения декорированных объектов используется специальный синтаксис,
предусматривающий использование имени декоратора с префиксом в виде символа @ непосредственно перед строкой, содержащей заголовок определения декорируемой
функции или метода (см. пример №18).
Код
Результат
pythonCodes
# Объект ф-ции передается декоратору в качестве аргумента.
def decorator_func(func):
# Внутри декораторы содержат функцию-обертку, которая
# изменяет поведение декорируемой функции func.
def wrapper_func(arg):
# Добавляем подсказку.
print('Начало выполнения функции!')
# Вызываем саму декорируемую функцию.
print(func(arg))
# Добавляем еще одну подсказку.
print('Выполнение функции закончено!')
# Возвращаем объект измененной func.
return wrapper_func
# Декорируем определяемую функцию.
@decorator_func
# Определим обычную польз. функцию.
def my_func(arg):
# Просто возвращаем аргумент.
return arg
# Вызываем измененную функцию.
my_func('Выполняюсь!')
Начало выполнения функции!
Выполняюсь!
Выполнение функции закончено!
Пример №18. Декораторы функций (часть 2).
Благодаря наличию такого синтаксиса нам не пришлось использовать в примере более длинную инструкцию my_func = decorator_func(my_func), что сделало код
более коротким и читабельным.
Атрибуты функций, аннотации и документирование в Python
Поскольку функции в Python являются обычными объектами, мы можем без каких-либо сложностей получать базовый доступ к предопределенным атрибутам функций,
а также добавлять, изменять или удалять собственные атрибуты (см. пример №19).
Код
Результат
pythonCodes
# Объявляем функцию и получаем объект.
def func(n): print(n*2)
# <function main.<locals>.func at 0x0000025137AA4670>.
print(func, end='\n\n')
# Выводим ее встроенные атрибуты.
print(dir(func))
# Выводим имя функции.
print(func.__name__)
# Выводим размер функции в байтах.
print(func.__sizeof__())
# Создаем свой атрибут в виде свойства.
func.count = 0
# Выводим значение атрибута.
print(func.count)
# Используем атрибут в вызове функции.
# Выведет 0.
func(func.count)
# Изменяем значение нашего атрибута.
func.count += 1
# Выведет 1.
print(func.count)
# Выведет 2.
func(func.count)
<function main.<locals>.func at 0x0000025137AA4670>
['__annotations__', '__builtins__', '__call__',...
func
128
0
0
1
2
Пример №19. Атрибуты функций в Python.
Как видим, для получения списка всех предопределенных атрибутов функции нужно просто воспользоваться встроенной функцией dir. Если же необходим доступ к
конкретному атрибуту, следует указать его имя в формате func.attr, где func – имя функции, а
attr – имя атрибута. Что касается пользовательских атрибутов, то они создаются и изменяются простым присваиванием требуемого значения имени атрибута,
который также записывается после имени функции через точку в формате func.attr. Собственные атрибуты позволяют эффективно и просто хранить информацию о
состоянии непосредственно в объекте функции, отказавшись от использования других приемов, таких как применение глобальных или нелокальных переменных и классов. Огромным
плюсом при этом является то, что в отличие от нелокальных переменных, атрибуты функции доступны в любом месте программы, где доступна сама функция. Так что в некотором смысле их можно
рассматривать, как имитацию статических локальных переменных, имеющихся в некоторых других языках программирования, т.к. они тоже сохраняют свои значения между вызовами функции.
Начиная с версии Python 3.0 у объектов функций появился новый встроенный атрибут __annotations__, который предназначен
для хранения краткого описания (аннотаций) аргументов функции и ее возвращаемого значения. Аннотации совершенно необязательны, но если они присутствуют, то их всегда можно получить через
этот атрибут, воспользовавшись синтаксической конструкцией func.__annotations__ (см. пример №20).
Код
Результат
pythonCodes
# Объявляем аннотированную функцию.
def func(name: 'имя', age: int) -> str:
'''Строка документирования: func.__doc__'''
# Составляем ник пользователя.
nick = name + '_' + str(age)
# Возвращаем его.
return nick
# Выведет Петр_35.
print(func('Петр', 35))
# Строка документирования: func.__doc__.
print(func.__doc__)
# {'name': 'имя', 'age': <class 'int'>, 'return': <class 'str'>}.
print(func.__annotations__)
Петр_35
Строка документирования: func.__doc__
{'name': 'имя', 'age': <class 'int'>, 'return': <class 'str'>}
Пример №20. Аннотации и документирование функций в Python.
Как видно из примера, синтаксически аннотации функции находятся в заголовке инструкции def в виде произвольных выражений, которые ассоциируются с ее
аргументами и возвращаемым значением. Для аргументов аннотации указываются через двоеточие сразу после имени аргумента, а для возвращаемого значения – после пары символов
-> вслед за списком аргументов. Все аннотации, если конечно они присутствуют в объявлении функции, интерпретатор собирает в словарь, который затем
присоединяет к объекту созданной функции. Имена аргументов в этом словаре становятся ключами, а сами аннотации их значениями. При этом для возвращаемого значения аннотация сохраняется под
ключом return.
Cтоит обратить внимание на еще один важный момент: в аннотированных аргументах, которые используют значения по умолчанию, аннотация записывается перед значением по умолчанию в
формате def func(arg_1: annot_1 = val_1, arg_2: annot_2 = val_2, …) -> return_annot:.
Что касается строки документирования, то она должна записываться в начале тела функции в тройных кавычках. Интерпретатор автоматически присоединит документацию к объекту функции и она станет
доступна в качестве значения его предопределенного атрибута __doc__.
Концепции проектирования функций в Python
Теперь, когда мы получили достаточно ясное представление о функциях в Python, хотелось бы подытожить тему несколькими полезными рекомендациями.
-
Каждая хорошо спроектированная функция должна иметь одно единственное назначение и решать только одну простую задачу. Например, если функция позволяет одновременно перемножать и
складывать числа, то наверняка стоит подумать над тем, чтобы разбить ее на две отдельные и более простые функции. -
Каждая функция должна иметь относительно небольшой размер. Если ваша функция начинает занимать несколько экранов – это явный признак того, что было бы неплохо разбить ее
на несколько более простых частей. Функции с большой глубиной вложенности часто свидетельствует о промахах в проектировании, тем более в языке Python,
которому присущи краткость и лаконичность. -
Используйте для передачи значений функции аргументы, а для возврата результатов – инструкцию return. Функция должна быть максимально независимой от
того, что происходит за ее пределами. -
Старайтесь использовать глобальные переменные внутри тела функции только в том случае, когда это действительно необходимо. Это поможет избежать зависимости от внешних факторов и проблем
с согласованностью компонентов программ, что, как следствие, существенно облегчит их отладку в дальнейшем. -
Следите за тем, чтобы ваши функции не воздействовали на изменяемые аргументы, если вызывающая программа не предполагает этого. Ведь получая в качестве аргументов, например, списки или
словари, функции могут оказывать воздействие на части этих изменяемых типов данных. В результате такие функции, опять же, становятся слишком тесно связаны и зависимы от внешней среды, что
может сделать их уж слишком специфичными и неустойчивыми. -
Избегайте непосредственного изменения переменных вашими функциями в других модулях программы. Используйте для этих целей отдельные функции доступа, сведя прямую зависимость к минимуму, как
и в случае с глобальными переменными.
Краткие итоги параграфа
- Функция в программировании – это блок программного кода на этом языке, который определяется один раз и далее может быть использован многократно.
-
Функции позволяют существенно уменьшить время и трудозатраты на разработку приложений, а также их дальнейшее сопровождение за счет следующих преимуществ: дают возможность разбить
сложную систему на небольшие и легко управляемые части, каждая из которых может разрабатываться отдельно, обеспечивают многократное использование программного кода, уменьшают его
избыточность, повышают читабельность исходного кода, упрощают его редактирование. -
Для создания функций в Python предназначена инструкция def, которая создает объект функции и связывает его с именем.
В общем виде инструкция имеет следующий формат:def <Имя функции>(arg_1, arg_1,... arg_n): <Тело функции> return <Возвращаемое значение>Примером определения функции может служить инструкция def f(a, b): return a + b.
-
Инструкция return не является обязательной. При ее отсутствии функция будет завершать свою работу по достижении потоком управления конца тела функции.
При этом технически она все равно будет неявно возвращать результат в виде объекта None, однако он обычно просто игнорируется. -
Осуществляются вызовы функций только после их определения при помощи имени функции и круглых скобок с передаваемыми функции значениями аргументов, которые, если их несколько,
должны перечисляться через запятую. Например, для определенной выше функции вызов может иметь вид x = f(43, 85). -
В Python имеется целый ряд специальных режимов сопоставления аргументов, при использовании которых следует придерживаться следующих правил:
-
в заголовке определяемой функции аргументы должны указываться в следующем порядке: позиционные аргументы (arg_1, arg_2, …),
позиционные аргументы со значениями по умолчанию (arg_v_1=val_1, arg_v_2=val_2, …),
аргумент со звездочкой для сбора дополнительных позиционных аргументов в кортеж (*args), именованные аргументы
(named_arg_1, named_arg_2, …), именованные аргументы со значениями по умолчанию
(named_arg_1=val_1, named_arg_2=val_2, …) и аргумент с двумя звездочками для сбора дополнительных именованных аргументов в словарь
(**named_args); -
при вызове функции порядок передачи аргументов должен быть следующим: обычные позиционные аргументы (arg_1, arg_2, …), позиционные аргументы
в форме *args (будут распакованы интерпретатором в значения arg_1, arg_2, …), обычные именованные аргументы
(named_arg_1=val_1, named_arg_2=val_2, …) и в самом конце именованные аргументы в форме **named_args
(будут распакованы интерпретатором в пары named_arg_1=val_1, named_arg_2=val_2, …).
-
в заголовке определяемой функции аргументы должны указываться в следующем порядке: позиционные аргументы (arg_1, arg_2, …),
-
В пределах модуля имена могут находиться в трех основных областях видимости, которые определяются местом инициализации переменных.
-
Если присваивание переменной выполняется внутри инструкции def, переменная становится локальной для этой функции. Это касается как аргументов
функции, так и переменных, созданных в теле функции. Локальные переменные доступны только внутри своей функции, т.е. в текущей локальной области видимости, и недоступны за ее
пределами, включая другие локальные области видимости невложенных в нее функций. -
Если присваивание производится в пределах объемлющей инструкции def, переменная становится локальной для данной объемлющей функции и нелокальной
для данной вложенной функции. В общем случае такая переменная будет доступна внутри любой вложенной функции при отсутствии в ней локальной переменной с таким же именем. -
Если присваивание производится на верхнем уровне модуля (файла скрипта) за пределами всех инструкций def, переменная становится глобальной для
всего файла. В общем случае глобальная переменная будет доступна внутри любой функции текущего модуля при отсутствии в ней локальной переменной с таким же именем.
-
Если присваивание переменной выполняется внутри инструкции def, переменная становится локальной для этой функции. Это касается как аргументов
-
Получить информацию о глобальных и локальных переменных известных интерпретатору на данный момент можно при помощи встроенных функций globals() и
locals(), которые возвращают словари со значениями соответствующих переменных, имена которых используются в словарях в качестве ключей. -
Поиск имен интерпретатором осуществляется следующим образом: когда внутри функции выполняется обращение к неизвестному имени, интерпретатор пытается сначала отыскать его в локальной
области видимости, затем в локальной области любой объемлющей инструкции def или в выражении lambda и далее в
глобальной области видимости. Как только первое подходящее имя будет найдено, поиск завершается. Если искомого имени обнаружено не будет, интерпретатор выведет сообщение об ошибке. -
Для создания простых функций непосредственно внутри выражений в Python используются lambda-выражения, которые еще
называют анонимными или безымянными функциями, т.к. они хоть и создают функции, но не связывают их с именами. При этом важно помнить, что в теле анонимной функции может содержаться
только одно выражение и никаких инструкций. -
В Python допускается использование рекурсивных функций, которые позволяют осуществлять вызов самих себя до выполнения особого условия,
останавливающего дальнейшие рекурсии. Используются рекурсивные функции не так часто, как в некоторых других языках программирования, зато они могут оказаться весьма полезными
для реализации обхода структур с произвольной организацией данных. -
Также для Python характерно использование декораторов, т.е. специальных функций-оберток, которые позволяют изменять поведение и расширять
функциональность передаваемых им в качестве аргументов других функций и методов, что бывает весьма полезным для расширения возможностей функций из сторонних библиотек, код которых
мы не можем изменять, а также для изменения поведения или расширения функциональности методов классов. -
Поскольку функции в Python являются обычными объектами, у них имеются предопределенные атрибуты, а также могут добавляться, изменяться или удаляться
пользовательские атрибуты. В общем случае доступ к атрибутам можно получить при помощи имени атрибута, указанного через точку после имени функции. -
Начиная с версии Python 3.0 у объектов функций появился новый встроенный атрибут __annotations__, используемый
для хранения аннотаций к аргументам функции и возвращаемому значению. В общем виде синтаксис записи аннотаций выглядит так:
def func(arg_1: annot_1, … arg_k: annot_k = val_k, …) -> return_annot:. -
Помимо аннотаций в функции разрешается добавлять строки документирования. Такая строка должна записываться в начале тела функции в тройных кавычках. Интерпретатор
автоматически присоединит ее к объекту функции и далее созданная документация станет доступна в качестве значения его предопределенного атрибута
__doc__. -
В ходе проектирования функций следует придерживаться следующих основных правил: функция должна быть максимально короткой и выполнять одну простую задачу; значения в функцию должны
передаваться через аргументы, а результат возвращаться через инструкцию return; глобальные переменные должны использоваться внутри функции только
в том случае, когда это действительно необходимо; функции не должны воздействовать на изменяемые аргументы, если вызывающая программа не предполагает этого; изменение переменных в
других модулях следует осуществлять через специально созданные для этого функции доступа, избегая непосредственного их изменения.
Вопросы и задания для самоконтроля
1. Дайте определение функции в программировании. Перечислите основные преимущества использования функций.
Показать решение.
Ответ. Функция в программировании – это блок программного кода на этом языке, который определяется один
раз и далее может быть использован многократно.
Функции позволяют существенно уменьшить время и трудозатраты на разработку приложений, а также их дальнейшее сопровождение за счет следующих преимуществ:
- дают возможность разбить сложную систему на небольшие и легко управляемые части, каждая из которых может разрабатываться отдельно;
- обеспечивают многократное использование программного кода;
- уменьшают его избыточность;
- повышают читабельность исходного кода;
- упрощают его редактирование.
2. В какой момент времени интерпретатор Python создает функции? Разрешается ли вызывать функции до момента их определения?
Показать решение.
Ответ. Функция создается, когда поток выполнения программы достигает инструкции def и выполняет ее. Эта
инструкция создает объект функции и связывает его с именем функции. С этого момента функция становится доступной для вызовов: для этого нужно к имени функции добавить круглые скобки со
значениями аргументов внутри. Вызывать функцию до момента ее определения в Python запрещается. Стоит заметить, что в некоторых других языках программирования
такое возможно за счет того, что определения функций поднимаются вверх скриптов. Но в Python вызывать функцию до момента ее определения запрещается!
3. Когда выполняется непосредственно сам программный код, вложенный в тело функции?
Показать решение.
Ответ. Тело функции выполняется каждый раз при вызове уже определенной функции.
4. Что возвращает функция, в которой отсутствует инструкция return?
Показать решение.
Ответ. Инструкция return не является обязательной. При ее отсутствии функция будет завершать свою работу по
достижении потоком управления конца тела функции. При этом технически она все равно будет неявно возвращать результат в виде объекта None, однако он обычно
просто игнорируется.
5. Какой из представленных вариантов определения функции не содержит ошибок: dfn f(x, y): return x*y,
def f(x, y): return x*y или def f{x, y}: return x*y.
Показать решение.
Ответ. def f(x, y): return x*y
6. Каковы будут результаты вызовов функций в коде условия? Объясните свои ответы, а затем запустите скрипт и проверьте себя.
Показать решение.
Условие
pythonCodes
# 1.
def func_1(a, b=2, c=3): print(a, b, c)
func_1(1, 2)
# 2.
def func_2(a, b, c=3): print(a, b, c)
func_2(1, c=4, b=5)
# 3.
def func_3(a, *p_args): print(a, p_args)
func_3(1, 2, 3)
# 4.
def func_4(a, b, c=3): print(a, b, c)
func_4(2, *(4, 5))
# 5.
def func_5(a, **n_args): print(a, n_args)
func_5(1, c=3, b=2)
# 6.
def func_6(a, *, b, **n_args): print(a, b, n_args)
func_6(1, c=3, b=2)
Решение
Результат
pythonCodes
# Два аргумента передали по умолчанию.
def func_1(a, b=2, c=3): print(a, b, c)
# Выведет 1 2 3, т.к. позиционные
# аргументы сопоставляются по позициям.
func_1(1, 2)
# По умолчанию передали только 1 арг-т.
def func_2(a, b, c=3): print(a, b, c)
# Выведет 1 5 4, т.к. передаем арг-ты
# по именам хоть и не в том порядке.
func_2(1, c=4, b=5)
# Доп. поз-ные арг-ты собираем в кортеж.
def func_3(a, *p_args): print(a, p_args)
# Выведет 1 (2, 3), т.к. два дополнительных
# аргумента добавились в кортеж.
func_3(1, 2, 3)
# Имеем 1 позиционный арг-т по умолчанию.
def func_4(a, b, c=3): print(a, b, c)
# Выведет 2 4 5, т.к. кортеж распаковался
# в позиционные значения 4 и 5.
func_4(2, *(4, 5))
# Доп. именованные арг-ты собираем в словарь.
def func_5(a, **n_args): print(a, n_args)
# Выведет 1 {'c': 3, 'b': 2}, т.к. было передано
# 2 именованных аргумента хоть и не в том порядке.
func_5(1, c=3, b=2)
# После * определяем именованные аргументы.
def func_6(a, *, b, **n_args): print(a, b, n_args)
# 1 2 {'c': 3}, т.к. а - поз-ный арг-т, b - имен-ный,
# с - доп-ный именованный (он и добавился в словарь).
func_6(1, c=3, b=2)
1 2 3
1 5 4
1 (2, 3)
2 4 5
1 {'c': 3, 'b': 2}
1 2 {'c': 3}
7. Какие переменные в Python называются глобальными, а какие локальными? Доступны ли глобальные переменные внутри
функций, а локальные на верхнем уровне модуля?
Показать решение.
Ответ. Переменные объявленные за пределами всех инструкций def на верхнем уровне модуля (файла скрипта),
называются глобальными. В общем случае глобальная переменная будет доступна внутри любой функции текущего модуля при отсутствии в ней локальной переменной с таким же именем. Переменные
объявленные внутри функции называются ее локальными переменными. Локальные переменные доступны только внутри своей функции, т.е. в текущей локальной области видимости, и недоступны за ее
пределами, включая другие локальные области видимости невложенных в нее функций.
8. Что будет выведено на экран фрагментами кода условия? Объясните свои ответы, а затем запустите скрипт и проверьте себя.
Показать решение.
Условие
pythonCodes
# 1.
x_1 = 1
def func_1(): print(x_1)
func_1()
# 2.
x_2 = 2
def func_2(): x_2 = 22
func_2()
print(x_2)
# 3.
x_3 = 3
def func_3():
x_3 = 33
print(x_3)
func_3()
print(x_3)
# 4.
x_4 = 4
def func_4():
global x_4
x_4 = 44
func_4()
print(x_4)
# 5.
x_5 = 5
def func_5():
x_5 = 55
def nested_5(): print(x_5)
nested_5()
func_5()
print(x_5)
# 6.
x_6 = 6
def func_6():
x_6 = 66
print(x_6)
def nested_6():
nonlocal x_6
x_6 = 666
nested_6()
print(x_6)
func_6()
print(x_6)
Решение
Результат
pythonCodes
# Глобальная переменная.
x_1 = 1
# Глоб. пер-е доступны везде.
def func_1(): print(x_1)
# Выведет 1.
func_1()
# Глобальная переменная.
x_2 = 2
# Лок-ная имеет приоритет внутри ф-ции.
def func_2(): x_2 = 22
# Ничего не выводит.
func_2()
# Выведет значение глоб. пер-ной, т.е. 2.
print(x_2)
# Глобальная переменная.
x_3 = 3
def func_3():
# Локальная переменная.
x_3 = 33
# Лок-ная имеет приоритет внутри ф-ции.
print(x_3)
# Выведет значение лок. пер-ной, т.е. 33.
func_3()
# Вне ф-ции лок. пер-ная недоступна.
# Выведет значение глоб. пер-ной, т.е. 3.
print(x_3)
# Глобальная переменная.
x_4 = 4
# Выведет новое значение глоб. пер-ной, т.е. 44.
print(x_4)
def func_4():
# Использ. глоб. пер-ную.
global x_4
# Значение присваивается глоб. пер-ной.
x_4 = 44
# Вызов изменяет значение глоб. пер-ной.
func_4()
# В глоб. области лок-ные пер-ные недоступны.
# Выведет новое значение глоб. пер-ной, т.е. 44.
print(x_4)
# Глобальная переменная.
x_5 = 5
def func_5():
# Локальная переменная для func_5.
x_5 = 55
# Локальные переменные объемлющей ф-ции
# доступны во всех вложенных функциях.
def nested_5(): print(x_5)
# Выведет 55 при вызове func_5().
nested_5()
# Выведет 55.
func_5()
# В глоб. области лок-ные пер-ные недоступны.
# Выведет значение глоб. пер-ной, т.е. 5.
print(x_5)
# Глобальная переменная.
x_6 = 6
def func_6():
# Локальная переменная для func_6.
# Нелокальная переменная для nested_6.
x_6 = 66
# Выведет 66 при вызове func_6().
print(x_6)
def nested_6():
# Использ. нелок-ную. пер-ную.
nonlocal x_6
# Присваиваем значение нелок-ной пер-ной.
x_6 = 666
# Вызов изменяет нелок-ную пер-ную.
nested_6()
# Выведет 666 при вызове func_6().
print(x_6)
# Выведет 666.
func_6()
# В глоб. области лок-ные пер-ные недоступны.
# Выведет значение глоб. пер-ной, т.е. 6.
print(x_6)
1
2
33
3
4
44
55
5
66
666
6
9. Сосчитайте локальные переменные функции func(x). Сколько локальных переменных будет отображено в словаре
loc_vars при первом и втором вызовах функции locals() и почему?
Показать решение.
Условие
pythonCodes
def func(x):
y = 5
z = 10
loc_vars = locals()
sum = x + y + z
loc_vars = locals()
return sum
print(func(20))
Ответ. Всего в локальной области видимости функции func(x) имеется пять локальных переменных. Все они
будут перечислены в словаре, который возвращает второй вызов функции locals(). Однако после первого вызова функции в переменной
loc_vars будет содержаться словарь лишь с тремя локальными переменными x, y и
z, т.к. только они будут известны интерпретатору на момент вызова функции. Сама локальная переменная loc_vars становится
известной интерпретатору уже после вызова функции locals() в момент присваивания ей значения (инструкция присваивания имеет правую ассоциативность).
Решение
Результат
pythonCodes
# Объявляем функцию.
def func(x):
# Объявляем локальные переменные.
y = 5
z = 10
# Получаем словарь доступных локальных переменных.
loc_vars = locals()
print('loc_vars:', loc_vars)
# Объявляем еще одну локальную переменную.
sum = x + y + z
# Обновляем словарь локальных переменных для func_1.
loc_vars = locals()
print('loc_vars:', loc_vars)
return sum
# Вызываем функцию.
print(func(20))
loc_vars: {'x': 20, 'y': 5, 'z': 10}
loc_vars: {'x': 20, 'y': 5, 'z': 10, 'loc_vars': {...}, 'sum': 35}
35
10. Перечислите основные отличия lambda-выражения от инструкции def.
Показать решение.
Ответ. В отличие от обычной инструкции объявления функции def лямбда-выражение обладает следующими
особенностями: создает объект функции, но не связывает его с именем; является выражением и может использоваться внутри выражений; внутри тела может содержать только одно выражение и
никаких инструкций.
11. Что представляют из себя рекурсивные функции?
Показать решение.
Ответ. Рекурсивные функции – это функции, которые содержат код вызова самой
себя в целях организации циклического процесса. Во всем остальном они представляют собой вполне обычные функции со всеми присущими им особенностями.
12. Каков синтаксис использования одной функции в качестве декоратора другой?
Показать решение.
Ответ. Необходимо написать в строке, предшествующей строке с заголовком определяемой декорируемой функции, имя декоратора с префиксом в виде
символа @.
Решение
Результат
pythonCodes
# Это наш декоратор.
def decorator_func(func):
# Внутри декораторы содержат функцию-обертку, которая
# изменяет поведение декорируемой функции func.
def wrapper_func(arg):
# Добавляем подсказку.
print('Начало выполнения функции!')
# Вызываем саму декорируемую функцию.
print(func(arg))
# Добавляем еще одну подсказку.
print('Выполнение функции закончено!')
# Возвращаем объект измененной func.
return wrapper_func
# Декорируем определяемую функцию.
@decorator_func
# Определим обычную польз. функцию.
def my_func(arg):
# Просто возвращаем аргумент.
return arg
# Вызываем измененную функцию.
my_func('Выполняюсь!')
Начало выполнения функции!
Выполняюсь!
Выполнение функции закончено!
13. Каких наиболее общих рекомендаций следует придерживаться в ходе проектирования функций?
Показать решение.
Ответ. В ходе проектирования функций следует придерживаться следующих основных правил:
функция должна быть максимально короткой и выполнять одну простую задачу;
значения в функцию должны передаваться через аргументы, а результат возвращаться через инструкцию return;
глобальные переменные должны использоваться внутри функции только в том случае, когда это действительно необходимо;
функции не должны воздействовать на изменяемые аргументы, если вызывающая программа не предполагает этого;
изменение переменных в других модулях следует осуществлять через специально созданные для этого функции доступа, избегая непосредственного их изменения.
14. Дополнительные тесты по теме расположены в разделе
«Функции» нашего сборника тестов по основам
Пайтона.
15. Дополнительные упражнения и задачи по теме расположены в разделе
«Функции»
нашего сборника задач и упражнений.
Быстрый переход к другим страницам
Функции¶
Что такое функция¶
В общем случае функцией можно назвать набор связанных инструкций, которые выполняют определенную задачу. Функции во всех языках программирования помогают:
- структурировать код и улучшить читаемость;
- переиспользовать код;
- уменьшать количество мест, в которых можно ошибиться, при копировании и вставке кода.
Таким образом, функция — это инструмент композиции кода. Однажды определив набор инструкций, можно многократно использовать его, в том числе и как составную часть других функций.
В Python функции можно разделить на три типа:
- встроенные (список built-in функций);
- именованные (определенные пользователем при помощи
def); - анонимные (
lambda-функции).
Все функции являются объектами типа function.
Мы уже использовали встроенные функции, например:
print()– вывод данных на экран;str()– создание объектов строкового типа;type()– определение типа объекта.
Ими можно пользоваться как черным ящиком, который принимает что-то на вход и творит свою магию. О том, что готовые функции ожидают получить, написано в документации вместе с описанием принципа работы.
Совет
После разбора лекции советуем открыть документацию print(), например, и разобраться с подробностями работы.
Очевидно, стандартные функции дают лишь базовый инструментарий. Реализовать более сложную логику можно самостоятельно.
Пример¶
Представим, что необходимо находить количество корней квадратного уравнения — это легко можно реализовать на Python!
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Именованные функции¶
Определение¶
Определение функции позволяет создать объект функции. После определения к функции можно будет обратиться по заданному имени и получить результат её работы.
В Python существует особый синтаксис определения именованных функций:
- в начале — ключевое слово
def; -
название функции
существуют правила и рекомендации по именованию функций:
-
правила:
- название не может начинаться с числа;
- можно использовать нижнее подчеркивание
_, цифры и буквы;
-
рекомендации:
snake_case: в названии только буквы нижнего регистра, слова разделяются_;- название функции должно быть уникальным;
-
-
круглые скобки и, опционально, параметры внутри (о них ниже);
- двоеточие, переход на новую строку;
- тело функции, выделенное отступом — набор инструкций, который «выполняет работу».
Код в Python организован в блоки и именно отступы дают понять, где у блоков начало и конец. Блоки могут быть вложенными. Все тело функции должно располагаться минимум в одном отступе от начала строк.
|
Напишем простейшую функцию, которая будет печатать две фразы:
|
Вызов¶
После определения функции появляется возможность к ней обращаться (вызывать). Делается это просто: указывается имя функции, круглые скобки и, опционально, аргументы в них.
|
Лекция про функции!
Тело кончилось
Возвращаемое значение¶
Все функции в Python имеют одно и только одно возвращаемое значение. Если в теле функции отсутствует ключевое слово return, возвращаемое значение всегда будет None.
|
Возможно ли вернуть несколько значений?
Это возможно, если упаковать несколько значений в одно значение-контейнер. В Python контейнеры могут быть следующих типов:
- список (например,
[1, 2, 3]) - кортеж (
(1, 2, 3)) - словарь (
{"key_one": 1, "key_two": 2, "key_three": 3}) - объект класса (см. раздел объекты и классы)
Таким образом, возвращается одно значение, а содержать в себе оно может несколько других.
Возвращение значения означает, что его можно использовать вне функции, например, присвоить полученное значение переменной. Давайте посмотрим, что возвращает print_theme() и add_two():
|
Лекция про функции!
Тело кончилось
True
Как видите, несмотря на отсутствие return в коде функции, она действительно возвращает None.
Теперь посмотрим на add_two(), где возвращаемое значение задано нами:
|
Множественные return-ы¶
В теле функции можно указать return несколько раз. Выражения в Python вычисляются по очереди, и возвращаемое значение функции определяется первым вычисленным выражением, содержащем return (а если return отсутствует в теле функции, возвращается None).
Покажем это на примере функции, умножающей нечетные числа на 2 и делящей четные числа пополам.
|
Выражение в строке 3 будет выполнено в случае, если выполняется условие из строки 2. При этом происходит выход из функции, и следующие выражения в ее теле уже не исполнятся при этом вызове. Поэтому нет необходимости писать else:
|
Функция change_num_2 эквивалентна change_num, но содержит лишний уровень вложенности. Знание о том, что после первого return происходит выход из функции, позволяет избежать лишней вложенности.
Пространства имен и области видимости¶
Чуть выше была определена add_two(), внутри которой инициализировалась переменная result. Значение этой переменной нельзя получить, обратившись к ней вне функции.
|
name 'result' is not defined
Потому что функции обладают своим пространством имен: переменные, которые определены внутри, не видны извне. Однако, функция может получать доступ к переменным, которые определены снаружи. Давайте опишем чуть более формально.
В Python объектам можно давать имя и обращаться к ним по нему. Эти имена организованы в пространства имен или namespace. Инициализация переменной добавляет в namespace название объекта. Namespace — набор имен определенных на текущий момент объектов. Представьте себе dict, где ключом является строка, а значением — ссылка на объект. Область видимости определяет, переменные из каких пространств имен сейчас доступны. Разберем, как и где Python ищет объекты, к которым обращаются (этот процесс называется name resolution).
Namespace и области видимости можно разделить на несколько типов:
- локальные (Local) — локальные переменные функции, данная область существует до тех пор, пока функция не завершит работу после вызова;
- локальные пространства могут быть вложены друг в друга, в таком случае область уровня \(N\) (Enclosing) содержит «более глубокую» \(N + 1\). Поиск имени, вызванного на уровне \(N\), начинается с локального namespace этого уровня и продвигается «наружу», то есть на уровни выше;
- глобальные (Global) — все объекты, определенные на уровне скрипта;
- встроенные (Built-in) — namespace содержит все встроенные объекты (функции, исключения и т. д.), поиск в нем осуществляется последним.
Если имя не было найдено даже на уровне Built-in, будет выброшено исключение NameError.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Совет
Хотим заметить, что нужно быть аккуратными при использовании вложенных функций и следить за тем, где и какие переменные определены. В рамках курса не советуем так делать, чтобы не запутаться. Единственное исключение — декораторы, описанные ниже.
Замыкания¶
Замыкание (Closure) — это функция, объявленная в теле внешней функции, и использующая значения из области видимости внешней функции.
Замыкания удобно использовать для динамического создания функций.
|
Каррирование¶
Каррирование (Currying) — это превращение функции от нескольких аргументов в последовательность функций от одного аргумента.
В Python реализован менее строгий аналог каррирования. С помощью функции partial можно получить функцию от меньшего числа аргументов, но не обязательно одного. Рассмотрим пример.
|
Первым аргументом в partial передается функция. А далее через запятую можно передать от \(0\) до \(N\) аргументов, которые требуется зафиксировать, где \(N\) — это число аргументов исходной функции. Возвращаемое значение partial — это функция от \(N — x\) аргументов, где \(x\) — это число аргументов, переданных в partial (и таким образом зафиксированных). Передавать в partial только функцию и \(0\) аргументов можно, но не имеет смысла (получится функция от \(N\) аргументов, которая и была изначально). Если же зафиксировать все \(N\) аргументов, то на выходе получится функция от нуля аргументов (если это была чистая функция, результат будет эквивалентен константе).
Важно заметить, что partial сохраняет порядок аргументов. Поэтому при определении функции лучше в начале ставить аргументы, значения которых реже будут меняться.
Совет
Легко увидеть аналогию с примером замыкания, и действительно, в некоторых случаях использование partial дает тот же результат, но проще и потому предпочтительнее.
Чистые функции¶
Чистой функцией (pure function) называют функцию, одновременно обладающую следующими свойствами:
- является детерминированной;
- не имеет побочных эффектов.
Детерминированность означает, что возвращаемое значение всегда одинаково для одних и тех же значений аргументов.
Отсутствие побочных эффектов означает, что вызов функции не изменит ничего за пределами своей локальной области видимости. Сохранение возвращаемого функцией значения в переменную происходит за рамками функции, поэтому не нарушает ее чистоты.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
- 4
- 4
- 6
- 4
Какие из вызванных выше функций являются чистыми?
Функция fn1 не имеет побочных эффектов, но она не детерминирована потому что зависит от external_dict. Поэтому она не чистая.
Функция fn2 детерминирована, но она имеет побочный эффект — изменяет содержимое external_dict. Поэтому она не чистая.
Функция fn3 детерминирована и не имеет побочных эффектов, поэтому она чистая.
Функция print тоже была вызвана. Является ли она чистой? Вывод на печать — это побочный эффект, поэтому нет.
Вызов fn2 изменил содержимое external_dict, поэтому второй вызов fn1 дал результат, отличный от первого вызова.
Подумайте, какие значения будут напечатаны?
Ответ можно посмотреть нажав на + справа возле print
Чистые функции гораздо проще понять при чтении кода и гораздо проще отлаживать, поэтому по возможности стоит все функции делать чистыми.
Параметры¶
Наша функция add_two() или, например, type() ожидают, что на вход будут переданы какие-то аргументы для успешной работы, а вот print() можно вызвать и с ними, и с пустыми скобками. В начале лекции был представлен скелет функции, сейчас разберем, что же находится в скобках.
Для начала немного формализма:
- при определении функции в скобках пишутся параметры, которые функция может принять;
- при вызове функции в скобках указываются аргументы, которые задают значения параметров внутри функции.
То есть происходит отображение: аргументы, с которыми вызывается функция \(\Rightarrow\) значения параметров.
Переменные с названиями параметров могут быть использованы внутри тела функции, как будто их значения известны.
Позиционные параметры¶
Позиционные параметры выглядят как перечисленные внутри скобок названия переменных, используемых внутри функции:
|
Данный тип параметров характеризуется следующим:
- идут первыми после открытых скобок, все именованные строго после них;
- важен порядок: отображение аргументов в параметры будет последовательным;
- при вызове функции все аргументы, которые ожидает функция, должны быть переданы (иначе откуда Python возьмет значения? вот именно!)
Разберем пример, который суммирует два числа:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
А что, если количество входных переменных очень большое или вы заранее не знаете, сколько аргументов будет передано при вызове? Например, вам нужно сложить не 2 числа, а 102? В Python есть специальный синтаксис со звездочками. После позиционных аргументов можно указать list, элементами которого станут неограниченное количество переданных позиционных аргументов.
Синтаксис: имя_функции([поз_0, ..., поз_N,] [*поз_список]): ...; [] в данном случае обозначают необязательность.
|
Совет
Лучше передавать все в списках или векторах (о которых будет позже)
Именованные параметры¶
Данные параметры имеют значения по умолчанию.
Синтаксис: имя_функции([поз_0, ..., поз_N,] [*поз_список], [им_0=значение_0, ..., им_K=значение_K], [**им_словарь]): ...
Характеризуются следующим:
- в определении идут строго после позиционных параметров;
- в определении дано значение по умолчанию через
=после имени; - при вызове необязательно указывать значения, тогда будет использовано значение по умолчанию.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Приветствую, Саша
Здорова, Игорь
Привет, Вася
Аналогично позиционным аргументам, если необходимо передать множество именованных параметров, используется синтаксис со звездочками, но в данном случае их две, а не одна. Если такой синтаксис использован, все переданные именованные аргументы, кроме определенных явно, попадут в dict, указанный после **:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Что это за комментарий в тройных кавычках внутри функции?
Это один из общепринятых способов написания docstring — документации по функции.
Списки как значения по умолчанию¶
Рассмотрим следующую функцию:
|
Второй вызов функции привел к результату, отличающемуся от первого вызова. Почему так произошло? Значение ls по умолчанию мы объявили равным [1]. Оба вызова используют значение по умолчанию. Но при втором вызове в данном случае произошло обращение к тому же списку, который был создан в качестве значения по умолчанию при первом вызове. Как это можно проверить?
В Python определена встроенная функция id. Она возвращает целочисленный идентификатор объекта (уникальный и постоянный, пока объект существует). Используем ее, чтобы посмотреть идентификаторы списков.
|
123928376604123
[1, 2]
123928376604123
[1, 2, 2]
113928976643254
[3, 2]
123928376604123
[1, 2, 2, 2]
Значения идентификаторов могут быть другие. Важно, что в первом, втором и четвертом вызове эти значения одинаковы и отличаются от третьего вызова, у которого значения списка по умолчанию не используется. Такое поведение сохраняется и для пустого списка как значения по умолчанию, и для непустого, как в примере выше.
Одним из решений может быть следующее:
|
Обратите внимание, что с set-ом ситуация другая.
|
140214028693696
{2}
140214028693696
{2}
129928985405920
{2, 3}
140214028693696
{2}
Анонимные функции¶
Функции, определенные при помощи def, имеют название, по которому их можно вызвать, но также существуют и анонимные или неименованные функции. Такие функции могут быть определены при помощи оператора lambda. Он позволяет задать входные и выходные данные. Самостоятельно можете почитать документацию.
# синтаксис анонимных функций простой
lambda [арг_0, ..., арг_N]: выражение
В определении выражение — это возвращаемое значение анонимной функции, например:
|
<function __main__.<lambda>(num, div=2)>
В примере выше lambda-функции были только созданы в моменте, но не вызваны. Можно сказать, что мы их определили (как с def), но не воспользовались и сразу потеряли к ним доступ.
Для удобства можно присвоить объект функции переменной, и по этому имени к ней обращаться:
|
В отличии от именованной, анонимная функция может иметь только одно выражение.
Данные функции стоит применять, когда нужна одноразовая функция или требуется отложить вычисления. Пример с одноразовой функцией:
|
Здесь используется встроенная функция map. Она получает на вход функцию и список элементов, вызывает функцию для каждого элемента, результат такого вызова сохраняет в список и возвращает список результатов. В примере выше на вход подан список из трех элементов и анонимная функция, которая возводит аргумент в квадрат.
А вот пример с отложенными вычислениями:
|
В данном случае на первой строке вычисления уже определены, но еще не сделаны. И только на второй строке, где происходит вызов, вычисляется выражение из тела lambda-функции. Обратите внимание, что lambda-функция может вовсе не иметь аргументов, как и именованная.
Декораторы¶
Функции в Python являются объектами первого класса. Кроме прочего, это значит, что их можно передавать в другие функции в качестве аргументов, и отдавать в качестве возвращаемых значений. Благодаря этому возможно создавать декораторы. Декоратор — это функция, расширяющая возможности другой функции.
Пример¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Выше была определена функция many_arg_sum(), давайте засекать, сколько она работает:
|
Прошло 1.9073486328125e-06 секунд
Отлично, но можно добавить так называемый синтаксический сахар: вместо присваивания значения над определением функции можно указать специальный символ @, после которого указывается название декоратора:
|
Прошло 4.0531158447265625e-06 секунд
Использование синтаксического сахара с @ и указанием имени декоратора над функцией аналогично вызову
|
Генераторы¶
Генераторы в Python — это особый класс функций с интересными возможностями. Они позволяют приостанавливать или продолжать работу. Обычные функции вычисляют значение и возвращают его, но генераторы возвращают итератор, который возвращает поток значений и по такому потоку можно проходить пошагово, получая доступ к одному значению с каждой из итераций.
Вот простейший пример функции-генератора:
|
<generator object generate_numbers at 0x7f982bf68e50>
Любая функция, содержащая ключевое слово yield, является функцией-генератором, это обнаруживается компилятором байт-кода Python, который в результате специально компилирует функцию. Когда вызывается функция-генератор, она не возвращает ни одного значения, вместо этого возвращается объект генератора, который поддерживает протокол итератора.
Инструкция yield¶
Инструкция yield является частью генератора и заменяет ключевое слово return. Когда программа доходит до yield, то функция переходит в состояние ожидания и продолжает работу с того же места при повторном вызове.
Ниже представлены две функции (генерируют последовательность в обратном порядке), одна с оператором return, а другая функция-генератор с оператором yield. Принимают на вход некое значение n, в примере выше — 5, и возвращают последовательность чисел от 4 до 0.
| Функция с оператором return | |
|---|---|
1 2 3 4 5 6 7 8 9 |
|
[4, 3, 2, 1, 0]
[4, 3, 2, 1, 0]
Функция get_subsequence возвращает последовательность в виде списка. Такая функция хранит промежуточное состояние своей работы в виде списка. В примере выше такое состояние хранится в списке result, который и будет возвращён после окончания работы цикла while с помощью оператора return. Список будет возвращён в то место, откуда произошёл вызов функции, при этом важно понимать, что оператор return возвращает не только результат работы функции, но и управление ходом выполнения программы в то место, в котором был произведён вызов функции get_subsequence. При повторном вызове функции get_subsequence генерация чисел происходит заново.
| Функция-генератор с оператором yield | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
4
3
2
1
0
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-38-25b3de807c8c> in <module>
11 print(next(gen))
12 print(next(gen))
---> 13 print(next(gen))
StopIteration:
Генератор generate_subsequence на первый взгляд делает тоже самое, генерирует последовательность чисел в обратном порядке, только возвращает он такую последовательность частями и происходит это благодаря оператору yield, который не возвращает контроль над ходом выполнения программы в то место, в котором был произведён вызов функции-генератора, а передаёт контроль на время. Для того, чтобы получить очередной элемент сгенерированный функцией-генератором необходимо использовать встроенную функцию next, таким образом исполнение функции на время приостанавливается, до тех пор, пока снова не будет использована функция next. Локальная переменная внутри генератора неявным образом сохраняет своё состояние.
Обратите внимание
Eсли генератор вернёт все значения из сгенерированной последовательности и если будет выполнено ещё одно обращение к объекту генератора gen с помощью функции next — будет возвращено исключение StopIteration, как видно на 13-й строке примера выше. Генератор не смог вернуть ещё одно значение поскольку больше таких значений не осталось в сгенерированной последовательности.
Для того, чтобы получить все значения из функции-генератора можно воспользоваться циклом (для примера с помощью спискового включения (list comprehension)), например так:
|
Главное преимущество генераторов в том, что не тратятся вычислительные ресурсы на хранение ни промежуточного ни итогового результата. По этой причине генераторы удобно использовать при обработке больших последовательностей данных.
Что узнали из лекции¶
- что такое функции и зачем их применять
-
как определить функцию (инструкция
def)def название_функции(параметры): тело функции <return возвращаемые_значения> -
отличия позиционных параметров от именованных:
- порядок указания — сначала позиционные, потом именованные
- значения по умолчанию у именованных позволяют не указывать их при вызове
- синтаксис со звездочками для получения заранее неизвестного числа позиционных (
*some_list) и именованных (**some_dict) аргументов- в
*some_listстоит одна*: эта конструкция для получения неограниченного количества позиционных/неименованных аргументов - в
**some_dictдве*: все именованные аргументы, явно не указанные среди параметров, попадут туда
- в
- что может возвращать функция при помощи
returnreturnобозначает выход из функции и передачу «наружу» результата работы- в
returnможно перечислять несколько возвращаемых значений через запятую - в теле функции можно указать несколько
return, всегда сработает не больше одного
- чистые функции детерминированы и лишены побочных эффектов
- чистые функции проще понять и отлаживать, поэтому они предпочтительнее
- замыкания и каррирование позволяют создавать функции динамически, для каррирования можно использовать
partial - что такое декораторы и как они работают, щепотку синтаксического сахара с
@- использование декоратора эквивалентно сохранению результата вызова функции-декоратора с аргументом в виде оборачиваемой функции
stupid_power = time_decorator(stupid_power)
- использование декоратора эквивалентно сохранению результата вызова функции-декоратора с аргументом в виде оборачиваемой функции
- что такое генераторы и как они работают с использованием оператора
yield
Пройдите тест, узнайте какой профессии подходите
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы
Введение в функции
Функции в Python являются одним из ключевых элементов языка, позволяя структурировать код и делать его более читаемым и повторно используемым. Они помогают разбивать большие задачи на более мелкие, управляемые части, что облегчает разработку и отладку программ. В этой статье мы рассмотрим, как определять и вызывать функции в Python, а также разберем основные понятия, такие как аргументы, параметры и возвращаемые значения.
Функции играют важную роль в программировании, так как они позволяют создавать модульный код, который легко тестировать и поддерживать. Представьте, что вам нужно выполнить одну и ту же операцию в нескольких местах вашего кода. Вместо того чтобы дублировать код, вы можете определить функцию и вызывать её в нужных местах. Это не только сокращает количество кода, но и делает его более понятным и удобным для чтения.
Кроме того, функции помогают улучшить структуру программы, разделяя её на логические блоки. Это особенно полезно при работе над большими проектами, где важно поддерживать порядок и ясность. В Python функции могут быть определены в любом месте программы, что позволяет гибко организовывать код и использовать функции в различных контекстах.
Определение функции
Для определения функции в Python используется ключевое слово def, за которым следует имя функции и круглые скобки. Внутри скобок можно указать параметры функции. Тело функции начинается с двоеточия и должно быть отступлено на один уровень.
Пример определения функции:
В этом примере мы определили функцию greet, которая выводит на экран сообщение «Привет, мир!». Обратите внимание на структуру определения функции: ключевое слово def, имя функции, круглые скобки и двоеточие. Тело функции должно быть отступлено на один уровень, что указывает на его принадлежность к функции.
Важно выбирать понятные и описательные имена для функций, чтобы они отражали их назначение. Это делает код более читаемым и понятным для других разработчиков. Например, имя функции greet ясно указывает на то, что функция предназначена для приветствия.
Вызов функции
После того как функция определена, её можно вызвать, используя её имя и круглые скобки. Если функция принимает параметры, их нужно передать в скобках.
Пример вызова функции:
Этот вызов функции greet выведет на экран сообщение «Привет, мир!». Вызов функции осуществляется с помощью её имени и круглых скобок. Если функция не принимает параметры, скобки остаются пустыми.
Функции могут быть вызваны в любом месте программы после их определения. Это позволяет использовать функции для выполнения различных задач в разных частях кода. Вызов функции может быть включен в другие выражения и конструкции, такие как циклы и условные операторы.
Аргументы и параметры
Функции могут принимать входные данные, называемые аргументами. Аргументы передаются в функцию при её вызове, а параметры определяются при объявлении функции.
Пример функции с параметрами:
В этом примере функция greet принимает один параметр name. При вызове функции мы можем передать ей аргумент:
Этот вызов функции выведет на экран сообщение «Привет, Алексей!». Аргументы позволяют передавать данные в функцию, что делает её более гибкой и универсальной. Параметры функции указываются в круглых скобках при её определении и могут быть использованы внутри тела функции.
Множественные параметры
Функции могут принимать несколько параметров, разделённых запятыми.
Пример функции с несколькими параметрами:
В этом примере функция add принимает два параметра a и b и возвращает их сумму.
Вызов функции с несколькими аргументами:
Функции с несколькими параметрами позволяют выполнять более сложные операции и обрабатывать несколько входных данных. Параметры могут быть любого типа, включая числа, строки, списки и другие объекты.
Возвращаемые значения и ключевое слово return
Функции могут возвращать значения с помощью ключевого слова return. Это позволяет функции передавать результат своей работы обратно в место вызова.
Пример функции, возвращающей значение:
В этом примере функция square принимает один параметр x и возвращает его квадрат.
Вызов функции и использование возвращаемого значения:
Возвращаемые значения позволяют функциям быть более полезными и многоразовыми. Функция может выполнить вычисления или обработку данных и вернуть результат, который затем может быть использован в других частях программы.
Возвращение нескольких значений
Функции в Python могут возвращать несколько значений, используя кортежи.
Пример функции, возвращающей несколько значений:
В этом примере функция get_full_name возвращает полное имя и инициалы.
Вызов функции и использование возвращаемых значений:
Возвращение нескольких значений позволяет функциям быть более гибкими и полезными. Кортежи позволяют упаковывать несколько значений в одну структуру данных и возвращать их из функции. Это особенно полезно, когда функция должна возвращать несколько связанных результатов.
Заключение
Функции являются мощным инструментом в Python, позволяющим структурировать код и делать его более читаемым и повторно используемым. В этой статье мы рассмотрели, как определять и вызывать функции, а также разобрали основные понятия, такие как аргументы, параметры и возвращаемые значения. Надеемся, что эта информация поможет вам лучше понять и использовать функции в ваших программах.
Функции позволяют создавать модульный и организованный код, который легко поддерживать и расширять. Они помогают разбивать сложные задачи на более управляемые части, что облегчает разработку и отладку программ. Использование функций делает код более понятным и удобным для чтения, что особенно важно при работе в команде.
Изучение функций является важным шагом в освоении Python и программирования в целом. Понимание того, как определять, вызывать и использовать функции, поможет вам создавать более эффективные и гибкие программы. Надеемся, что эта статья предоставила вам полезную информацию и вдохновила на дальнейшее изучение функций в Python.
