Функции в python
Функция - именованный фрагмент кода, отделенный от других.
Определение функции
Пример функции, которая ничего не делает:
>>> def do_nothing():
pass
Python требует использовать выражение pass для демонстрации того, что функция ничего не делает.
Вызов функции с помощью скобок
>>> do_nothing()
>>>
>>> print(do_nothing())
None
# Или:
>>> def make_a_sound():
print('quack')
>>> make_a_sound()
quack
Usage in if
:
>>> def agree():
return True
>>> if agree():
print('Splendid!')
else:
print('That was unexpected.')
Splendid!
P.S. с помощью return
можно возвращать несколько значений в виде кортежа:
>>> def get_user():
name = 'John'
surname = 'Doe'
age = 44
return name, surname, age
>>> get_user()
('John', 'Doe', 44)
Аргументы и параметры
Значения вне функции называются аргументами, а значения внутри - параметрами.
>>> def echo(anything):
return anything + ' ' + anything
>>> echo('Rumplestiltskin')
'Rumplestiltskin Rumplestiltskin'
Значения, передаваемые в функцию при вызове, называются аргументами. Когда вызываешь функцию с аргументами, значения этих аргументов копируются в соответствующие параметры внутри функций.
None
None
- специальное значение в Python - появляется, если функция ничего не возвращает.
Не является булевымFalse
.
>>> thing = None
>>> if thing:
print("It's some thing")
else:
print("It's no thing")
It's no thing
Чтобы отличить None
от булева значения False
, юзай оператор is
:
>>> thing = None
>>> if thing is None:
print("It's nothing")
else:
print("It's something")
It's nothing
В чем тогда разница
None потребуется, чтобы различать отсутствующие значения и пустые.
int
нули 0
, float
нули 0.0
, пустые строки ''
, списки []
, кортежи (,)
,
словари {}
и множества set()
равны False
, но это не то же самое, что None
.
Такая функция выводит на экран, является ли ее аргумент None
, True
или False
:
>>> def whatis(thing):
if thing is None:
print(thing, "is None")
elif thing:
print(thing, "is True")
else:
print(thing, "is False")
>>> whatis(None)
None is None
>>> whatis(True)
True is True
>>> whatis(False)
False is False
>>> whatis(0)
0 is False
>>> whatis(0.0)
0.0 is False
>>> whatis('')
is False
>>> whatis("")
is False
>>> whatis('''''')
is False
>>> whatis(())
() is False
>>> whatis([])
[] is False
>>> whatis({})
{} is False
>>> whatis(set())
set() is False
>>> whatis(0.00001)
1e-05 is True
>>> whatis([0])
[0] is True
>>> whatis([''])
[''] is True
>>> whatis(' ')
is True
Аргументы
Пример чисто позиционных аргументов:
>>> def menu(wine, entree, dessert):
return {'wine': wine, 'entree': entree, 'dessert': dessert}
>>> menu('chardonnay', 'chicken', 'cake')
{'wine': 'chardonnay', 'entree': 'chicken', 'dessert': 'cake'}
Минус в том, что нужно в правильном порядке передавать аргументы.
Можно использовать такой подход, не парясь о порядке передаваемых аргументов:
>>> menu(entree='beef', dessert='bagel', wine='bordeaux')
{'dessert': 'bagel', 'wine': 'bordeaux', 'entree': 'beef'}
Либо комбинируя:
>>> menu('frontenac', dessert='flan', entree='fish')
{'entree': 'fish', 'dessert': 'flan', 'wine': 'frontenac'}
Дефолтное значение параметра
Дефолтное значение параметров высчитывается, когда функция определяется, а не выполняется.
Дефолтное значение указывается при определении функции:
>>> def menu(wine, entree, dessert='pudding'):
return {'wine': wine, 'entree': entree, 'dessert': dessert}
# Если не передать аргумент - будет использоваться дефолтное значение:
>>> menu('chardonnay', 'chicken')
{'wine': 'chardonnay', 'entree': 'chicken', 'dessert': 'pudding'}
# Можно и передать, чтобы заменить дефолтное значение на переданное:
>>> menu('dunkelfelder', 'duck', 'doughnut')
{'wine': 'dunkelfelder', 'entree': 'duck', 'dessert': 'doughnut'}
Нельзя использовать изменяемый тип данных в качестве значения параметра по умолчанию (списки, словари и т.д.). На примере багованной функции будет показано “почему нельзя?”, которая, по идее, каждый раз должна запускаться с новым пустым списком result, добавлять в него аргумент arg, а затем выводить на экран список, состоящий из одного элемента. Однако в этой функции есть баг: список пуст только при первом вызове. Во второй раз список result будет содержать элемент, оставшийся после предыдущего вызова:
>>> def buggy(arg, result=[]):
result.append(arg)
print(result)
>>> buggy('a')
['a']
>>> buggy('b') # ожидаем увидеть ['b']
['a', 'b']
Рабочий вариант:
>>> def works(arg):
result = []
result.append(arg)
return result
>>> works('a')
['a']
>>> works('b')
['b']
Решить проблему можно, передав в функцию что-либо еще, указывающее на то,
что вызов является первым:
>>> def nonbuggy(arg, result=None):
if result is None:
result = []
result.append(arg)
print(result)
>>> nonbuggy('a')
['a']
>>> nonbuggy('b')
['b']
(btw, популярный вопрос на собесах Python-разработчиков)
*args
- аргументы в кортеж
Если символ
*
будет использован внутри функции с параметром (*args
), произвольное кол-во позиционных аргументов сгруппируется в один кортеж.
Аналог$@
/$*
в bash. (silly🤪 аналог)
Не обязательно называть кортежargs
(как и**kwargs
), но это распространенная идиома в Python, поэтому не выебываемся.
>>> def print_args(*args):
print('Positional argument tuple:', args)
# Вызов без аргументов:
>>> print_args()
Positional argument tuple: ()
# Вызов с аргументами:
>>> print_args(3, 2, 1, 'wait!', 'uh ')
Positional argument tuple: (3, 2, 1, 'wait!', 'uh ')
>>> args = (2,5,7,'x')
>>> print_args(args)
Positional tuple: ((2, 5, 7, 'x'),)
>>> print_args(*args)
Positional tuple: (2, 5, 7, 'x')
Более распространенный пример:
>>> def print_more(required1, required2, *args):
print('Need this one:', required1)
print('Need this one too:', required2)
print('All the rest:', args)
>>> print_more('cap', 'gloves', 'scarf', 'monocle', 'mustache wax')
Need this one: cap
Need this one too: gloves
All the rest: ('scarf', 'monocle', 'mustache wax') # остальные аргументы
Символ *
можно использовать только при описании функции и ее вызове:
>>> *args
File "<input>", line 1
SyntaxError: can't use starred expression here
**kwargs
- аргументы в словарь
Можно юзать два астериска (**
), чтобы сгруппировать аргументы - ключевые слова в словарь, где имена аргументов станут ключами, а их значения - соответствующими значениями в словаре:
>>> def print_kwargs(**kwargs):
print('Keyword arguments:', kwargs)
# Вызов без аргументов:
>>> print_kwargs()
Keyword arguments: {}
# Вызов с аргументами:
>>> print_kwargs(wine='merlot', entree='mutton', dessert='macaroon')
Keyword arguments: {'dessert': 'macaroon', 'wine': 'merlot', 'entree': 'mutton'}
В итоге порядок аргументов будет следующим:
- Обязательные позиционные аргументы
- Необязательные позиционные аргументы (
*args
) - Необязательные аргументы - ключевые слова (
**kwargs
)
Синтаксис **
можно применять только при вызове функции или ее определении:
>>> **kwparams
File "<input>", line 1
**kwparams
^^
SyntaxError: invalid syntax
Аргументы, передаваемые только по ключевым словам
Python 3 позволяет указывать аргументы, которые передаются только по ключевым словам. Их нужно предоставлять в формате имя=значение
, а не позиционно, как значение. Знак *
в определении функции говорит о том, что параметры start
и end
нужно предоставлять как именованные аргументы, если не хочешь использовать их значения по умолчанию:
>>> def print_data(data, *, start=0, end=100):
for value in (data[start:end]):
print(value)
>>> data = ['a', 'b', 'c', 'd', 'e', 'f']
>>> print_data(data)
a
b
c
d
e
f
>>> print_data(data, start=4)
e
f
>>> print_data(data, end=2)
a
b
Изменяемые и неизменяемые аргументы
Если аргумент изменяемые, то его значение можно изменить изнутри функции с помощью соответствующего параметра:
>>> outside = ['one', 'fine', 'day']
>>> def mangle(arg):
arg[1] = 'terrible!'
>>> mangle(outside)
>>> outside
['one', 'terrible!', 'day']
Лучше избегать таких ситуаций.
Документация функции __doc__
Можно (нужно) прикрепить документацию к определению функции, включив в начало ее тела строку, которая и называется строкой документации:
>>> def echo(anything):
'echo returns its input argument'
return anything
>>> help(echo)
Help on function echo in module __main__:
echo(anything)
echo returns its input argument
Многострочная документация:
>>> def print_if_true(thing, check):
'''
Prints the first argument if a second argument is true.
The operation is:
1. Check whether the *second* argument is true.
1. If it is, print the *first* argument.
'''
if check:
print(thing)
# Другой способ вывода документации:
>>> print(print_if_true.__doc__)
Prints the first argument if a second argument is true.
The operation is:
1. Check whether the *second* argument is true.
2. If it is, print the *first* argument.
dunder
Строка doc является внутренним именем строки документации как переменной внутри функции. Двойное нижнее подчеркивание (на сленге - dunder (double under)) часто используется для именования внутренних переменных Python, поскольку программисты, скорее всего, не будут использовать подобные имена переменных.
Функция - тоже объект первого класса
В Python все является объектом (числа, строки, кортежи, списки, словари и даже функции).
Функции Python - это объекты первого класса ⇒ их можно присваивать переменным, хранить в структурах данных, передавать в качестве аргументов другим функциям и даже возвращать в качестве значений из других функций.
>>> def answer():
print(42)
>>> answer()
42
>>> def run_something(func):
func()
>>> run_something(answer)
42
Хитрость (?):
def add_num(num, nums=None):
nums = nums or []
# хитрая конструкция, которая позволит упростить ввод:
# if not nums:
# nums = []
# Первый вариант будет выбран, если nums не равен None,
# иначе будет создан и записан пустой список
nums.append(num)
print(nums)
Роль круглых скобок
Функция
answer()
была передана в качестве аргумента функцииrun_something()
без круглых скобок (run_something(answer)
). В Python круглые скобки означают “вызови эту функцию”. Если скобок нет, Python относится к функции как к любому другому объекту. Это происходит потому, что функция тоже является объектом.
btw, можно сделать то же самое с *args
и **kwargs
:
>>> def sum_args(*args):
return sum(args)
>>> def run_with_positional_args(func, *args):
return func(*args)
run_with_positional_args(sum_args, 1, 2, 3, 4)
10
Внутренние функции
Можно определить функцию внутри другой функции:
>>> def outer(a, b):
def inner(c, d):
return c + d
return inner(a, b)
>>> outer(4, 7)
11
# Или:
>>> def knights(saying):
def inner(quote):
return "We are the knights who say: '%s'" % quote
return inner(saying)
>>> knights('Ni!')
"We are the knights who say: 'Ni!'"
Замыкания
Замыкание - функция, которая динамически генерируется другой функцией.
Обе они могут изменяться и запоминать значения переменных, которые были созданы вне функции.
>>> def knights2(saying):
def inner2():
return "We are the knights who say: '%s'" % saying
return inner2
Здесь:
inner2()
использует внешний параметр saying directly, вместо того чтобы получить его как аргументknights2()
возвращает имя функции inner2, вместо того чтобы вызывать ее
Функция inner2()
знает значение переменой saying, которое было передано в функцию, и запоминает его. Строка inner2
возвращает эту особую копию функции inner2
(но не вызывает ее). Получается замыкание: динамически созданная функция, которая запоминает, откуда она появилась.
Наглядно:
>>> a = knights2('Duck')
>>> b = knights2('Hasenpfeffer')
>>> type(a)
<class 'function'>
>>> type(b)
<class 'function'>
# Это функции + замыкания
>>> a
<function knights2.<locals>.inner2 at 0x10193e158>
>>> b
<function knights2.<locals>.inner2 at 0x10193e1e0>
# Вызов:
>>> a()
"We are the knights who say: 'Duck'"
>>> b()
"We are the knights who say: 'Hasenpfeffer'"
Пространства имен (namespaces) и область определения
Программы на Python имеют различные пространства имен (namespaces) - разделы, внутри которых определенное имя уникально и не связано с таким же именем в других пространствах имен.
Каждая функция определяет собственное пространство имен. Если определить переменную с именем var в основной программе и другую переменную var в отдельной функции, они будут ссылаться на разные значения.
Но получить доступ к именам в других namespaces можно разными способами.
В основной программе определяется глобальный namespace, поэтому переменные, находящиеся в нем, являются глобальными.
Значение глобальной переменной можно спокойно получить внутри функции:
>>> animal = 'fruitbat'
>>> def print_global():
print('inside print_global:', animal)
>>> print('at the top level:', animal)
at the top level: fruitbat
>>> print_global()
inside print_global: fruitbat
Но получить и изменить значение глобальной переменной внутри функции не получится:
>>> def change_and_print_global():
print('inside change_and_print_global:', animal)
animal = 'wombat'
print('after the change:', animal)
>>> animal = 'fruitbat'
>>> change_and_print_global()
Traceback (most recent call last):
File "<input>", line 1, in <module>
change_and_print_global()
File "<input>", line 2, in change_and_print_global
print('inside change_and_print_global:', animal)
^^^^^^
UnboundLocalError: cannot access local variable 'animal' where it is not associated with a value
Если просто изменить - изменится другая переменная с тем же именем, находящаяся внутри функции:
>>> def change_local():
animal = 'wombat'
print('inside change_local:', animal, id(animal))
>>> change_local()
inside change_local: wombat 4330406160
>>> animal
'fruitbat'
>>> id(animal)
4330390832
Чтобы получить доступ конкретно к глобальной переменной, нужно явно юзать ключевое слово global:
>>> animal = 'fruitbat'
>>> def change_and_print_global():
global animal
animal = 'wombat'
print('inside change_and_print_global:', animal)
>>> animal
'fruitbat'
>>> change_and_print_global()
inside change_and_print_global: wombat
>>> animal
'wombat'
Что, если не указать ключевое слово
global
Если не использовать ключевое слово
global
внутри функции, Python задействует локальное пространство имен ⇒ переменная будет локальной и пропадет после того, как функция завершит работу.
В Python есть две функции для доступа к содержимому пространств имен:
locals()
- возвращает словарь с именами локального пространства
именglobals()
- возвращает словарь, содержащий имена глобального пространства
имен.
>>> animal = 'fruitbat'
>>> def change_local():
animal = 'wombat' # локальная переменная
print('locals:', locals())
>>> animal
'fruitbat'
>>> change_local()
locals: {'animal': 'wombat'}
>>> print('globals:', globals())
globals: {'animal': 'fruitbat',
'__doc__': None,
'change_local': <function change_it at 0x1006c0170>,
'__package__': None,
'__name__': '__main__',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__builtins__': <module 'builtins'>}
>>> animal
'fruitbat'
Использование _
и __
в именах
Имена, которые начинаются с двух нижних подчеркиваний
__
и заканчиваются ими, зарезервированы для использования внутри Python, поэтому не надо их юзать в именах своих переменных.
btw, основной программе присвоено спец. имя__main__
Демонстрация этих dunder переменных (🤷♂️):
>>> def amazing():
'''This is the amazing function.
Want to see it again?'''
print('This function is named:', amazing.__name__)
print('And its docstring is:', amazing.__doc__)
>>> amazing()
This function is named: amazing
And its docstring is: This is the amazing function.
Want to see it again?
Рекурсия
Рекурсия, in this case - функция, вызывающая сама себя.
Бесконечная рекурсия, естественно, не нужна, поэтому даже Python для такого случая генерирует исключение:
>>> def dive():
return dive()
>>> dive()
Traceback (most recent call last):
File "<input>", line 1, in <module>
dive()
File "<input>", line 2, in dive
return dive()
^^^^^^
File "<input>", line 2, in dive
return dive()
^^^^^^
File "<input>", line 2, in dive
return dive()
^^^^^^
[Previous line repeated 980 more times]
RecursionError: maximum recursion depth exceeded
Но рекурсия может быть полезна для работы с вложенными списками и т.д.:
>>> def flatten(lol):
for item in lol:
if isinstance(item, list):
for subitem in flatten(item):
yield subitem
else:
yield item
>>> lol = [1, 2, [3,4,5], [6,[7,8,9], []]]
>>> flatten(lol)
<generator object flatten at 0x10509a750>
>>> list(flatten(lol))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
btw, в Python 3.3 появилось выражение yield from, которая позволяет генератору передавать часть работы другому генератору ⇒ можно упростить функцию flatten()
выше:
>>> def flatten(lol):
for item in lol:
if isinstance(item, list):
yield from flatten(item)
else:
yield item
>>> lol = [1, 2, [3,4,5], [6,[7,8,9], []]]
>>> list(flatten(lol))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
С помощью рекурсии можно написать функцию, которая находит факториал от числа (nerdy ass tasks):
>>> def factorial(num):
if num == 1:
return 1
fact_num_minus_1 = factorial(num - 1)
return (num * fact_num_minus_1)
>>> factorial(6)
720
Или реализовать другой поиск элемента в сложном словаре:
site = {
'html': {
'head': {
'title': 'Мой сайт'
},
'body': {
'h2': 'Здесь будет мой заголовок',
'div': 'Тут, наверное, какой-то блок',
'p': 'А вот здесь новый абзац'
}
}
}
>>> def find_key(structure, key):
if key in structure:
return structure[key]
for sub_structure in structure.values():
if isinstance(sub_structure, dict):
result = find_key(sub_structure, key)
if result:
break
else:
result = None
return result
>>> find_key(site, 'html')
{'head': {'title': 'Мой сайт'}, 'body': {'h2': 'Здесь будет мой заголовок', 'div': 'Тут, наверное, какой-то блок', 'p': 'А вот здесь новый абзац'}}
>>> find_key(site, 'div')
'Тут, наверное, какой-то блок'
>>> find_key(site, 'p')
'А вот здесь новый абзац'
Соус: Книга “Простой Python” → Глава 9. “Функции”
Анонимные lambda функции
Генераторы в python
Декораторы в python
Асинхронные функции в python