Функции в 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'}

В итоге порядок аргументов будет следующим:

  1. Обязательные позиционные аргументы
  2. Необязательные позиционные аргументы (*args)
  3. Необязательные аргументы - ключевые слова (**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 есть две функции для доступа к содержимому пространств имен:

  1. locals() - возвращает словарь с именами локального пространства
    имен
  2. 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

python