Skip to content

Функции в python

Функция - именованный фрагмент кода, отделенный от других.

Определение функции

Пример функции, которая ничего не делает:

Python
>>> def do_nothing():
        pass

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

Вызов функции с помощью скобок

Python
>>> do_nothing()
>>>
>>> print(do_nothing())
None

# Или:
>>> def make_a_sound():
        print('quack')

>>> make_a_sound()
quack

Usage in if:

Python
>>> def agree():
        return True

>>> if agree():
        print('Splendid!')
    else:
        print('That was unexpected.')

Splendid!

P.S. с помощью return можно возвращать несколько значений в виде кортежа:

Python
>>> def get_user():
        name = 'John'
        surname = 'Doe'
        age = 44
        return name, surname, age

>>> get_user()
('John', 'Doe', 44)

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

Значения вне функции называются аргументами, а значения внутри - параметрами.

Python
>>> def echo(anything):
        return anything + ' ' + anything

>>> echo('Rumplestiltskin')
'Rumplestiltskin Rumplestiltskin'

Значения, передаваемые в функцию при вызове, называются аргументами. Когда вызываешь функцию с аргументами, значения этих аргументов копируются в соответствующие параметры внутри функций.

None

None - специальное значение в Python - появляется, если функция ничего не возвращает.
Не является булевым False.

Python
>>> thing = None
>>> if thing:
        print("It's some thing")
    else:
        print("It's no thing")

It's no thing

Чтобы отличить None от булева значения False, юзай оператор is:

Python
>>> 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:

Python
>>> 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


Аргументы

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

Python
>>> def menu(wine, entree, dessert):
        return {'wine': wine, 'entree': entree, 'dessert': dessert}

>>> menu('chardonnay', 'chicken', 'cake')
{'wine': 'chardonnay', 'entree': 'chicken', 'dessert': 'cake'}

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

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

Python
>>> menu(entree='beef', dessert='bagel', wine='bordeaux')
{'dessert': 'bagel', 'wine': 'bordeaux', 'entree': 'beef'}

Либо комбинируя:

Python
>>> menu('frontenac', dessert='flan', entree='fish')
{'entree': 'fish', 'dessert': 'flan', 'wine': 'frontenac'}

Дефолтное значение параметра

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

Дефолтное значение указывается при определении функции:

Python
>>> 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 будет содержать элемент, оставшийся после предыдущего вызова:

Python
>>> def buggy(arg, result=[]):
        result.append(arg)
        print(result)

>>> buggy('a')
['a']
>>> buggy('b')  # ожидаем увидеть ['b']
['a', 'b']

Рабочий вариант:

Python
>>> def works(arg):
        result = []
        result.append(arg)
        return result

>>> works('a')
['a']
>>> works('b')
['b']

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

Python
>>> 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, поэтому не выебываемся.

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')

Более распространенный пример:

Python
>>> 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')  # остальные аргументы

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

Python
>>> *args
  File "<input>", line 1
SyntaxError: can't use starred expression here

**kwargs - аргументы в словарь

Можно юзать два астериска (**), чтобы сгруппировать аргументы - ключевые слова в словарь, где имена аргументов станут ключами, а их значения - соответствующими значениями в словаре:

Python
>>> 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)

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

Python
>>> **kwparams
  File "<input>", line 1
    **kwparams
    ^^
SyntaxError: invalid syntax

Аргументы, передаваемые только по ключевым словам

Python 3 позволяет указывать аргументы, которые передаются только по ключевым словам. Их нужно предоставлять в формате имя=значение, а не позиционно, как значение. Знак * в определении функции говорит о том, что параметры start и end нужно предоставлять как именованные аргументы, если не хочешь использовать их значения по умолчанию:

Python
>>> 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

Изменяемые и неизменяемые аргументы

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

Python
>>> outside = ['one', 'fine', 'day']
>>> def mangle(arg):
        arg[1] = 'terrible!'

>>> mangle(outside)
>>> outside
['one', 'terrible!', 'day']

Лучше избегать таких ситуаций.


Документация функции __doc__

Можно (нужно) прикрепить документацию к определению функции, включив в начало ее тела строку, которая и называется строкой документации:

Python
>>> 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

Многострочная документация:

Python
>>> 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 - это объекты первого класса => их можно присваивать переменным, хранить в структурах данных, передавать в качестве аргументов другим функциям и даже возвращать в качестве значений из других функций.

Python
>>> def answer():
        print(42)

>>> answer()
42

>>> def run_something(func):
        func()

>>> run_something(answer)
42

Хитрость (?):

Python
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:

Python
>>> 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

Внутренние функции

Можно определить функцию внутри другой функции:

Python
>>> 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!'"

Замыкания

Замыкание - функция, которая динамически генерируется другой функцией.
Обе они могут изменяться и запоминать значения переменных, которые были созданы вне функции.

Python
>>> 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 (но не вызывает ее). Получается замыкание: динамически созданная функция, которая запоминает, откуда она появилась.

Наглядно:

Python
>>> 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, поэтому переменные, находящиеся в нем, являются глобальными.

Значение глобальной переменной можно спокойно получить внутри функции:

Python
>>> 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

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

Python
>>> 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

Если просто изменить - изменится другая переменная с тем же именем, находящаяся внутри функции:

Python
>>> 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:

Python
>>> 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() - возвращает словарь, содержащий имена глобального пространства
имен.

Python
>>> 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 переменных (🤷‍♂️):

Python
>>> 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 для такого случая генерирует исключение:

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

Но рекурсия может быть полезна для работы с вложенными списками и т.д.:

Python
>>> 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() выше:

Python
>>> 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):

Python
>>> def factorial(num):
        if num == 1:
            return 1
        fact_num_minus_1 = factorial(num - 1)
        return (num * fact_num_minus_1)

>>> factorial(6)
720

Или реализовать другой поиск элемента в сложном словаре:

Python
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]]

python