Основы функций

Содержание
Введение
def
Пример
Hello World!
return
Параметры и аргументы
type hints
Типы аргументов
Аргументы по умолчанию
Изменяемые vs. неизменяемые
Импорт функции
Области видимости
Встроенные функции
Вызываемые объекты
Похожие статьи

Введение

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

funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
decorators ::= decorator+
decorator ::= "@" assignment_expression NEWLINE
parameter_list ::= defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]] | parameter_list_no_posonly
parameter_list_no_posonly ::= defparameter ("," defparameter)* ["," [parameter_list_starargs]] | parameter_list_starargs
parameter_list_starargs ::= "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]] | "**" parameter [","]
parameter ::= identifier [":" expression]
defparameter ::= parameter ["=" expression]
funcname ::= identifier

О чём говорит первая строка этой таблицы:

  1. Функция определяется с помощью ключевого слова def
  2. Перед def может быть объявлен декоратор

def

Ключевое слово def используется для определения новых функций.

Оно связывает объект функции с именем

Выполняется в runtime

Общий синтаксис следующий:

def имя_функции(): какой-то код

Пример использования

Рассмотрим файл functions_demo.py

Создадим функцию lazy, которая ничего не делает.

Чтобы её объявить используется слово def затем название lazy поставьте круглые скобки () и двоеточие.

В нашем случае получается:

# functions_demo.py def lazy(): pass

pass означает просто продолжать код ничего не предпринимая.

# functions_demo.py - это комментарий. Он не выполняется.

Функцию не обязательно писать в одну строку. Лучше после двоеточия перейти на новую строку и сделать отступ из четырёх пробелов.

# functions_demo.py def lazy(): pass

Никогда не видел в проектах, но если поставить перед () пробел - это не будет синтаксической ошибкой

def lazy (): pass

Наша функция создана, иначе говоря объявлена. Это значит, что она существует где-то в коде и может что-то сделать.

Но не делает. Чтобы функция что-то делала её нужно вызвать. В нашем случае достаточно написать её имя и круглые скобки.

# functions_demo.py def lazy(): pass lazy()

python functions_demo.py

Если никаких ошибок не допущено вывод будет пустым.

Hello World!

Объявим и вызовем функцию, которая пишет Hello World!

def hello(): print("Hello World!") hello()

python functions_demo.py

Hello World!

return

Заменим в функции hello() print() на return.

Рассмотрим скрипт return_demo.py

# return_demo.py def hello(): return "Hello World!" hello()

python return_demo.py

Теперь на экран ничего не выводится. Чтобы повторить функционал hello() нужно добавить вызов print()

# return_demo.py def hello(): return "Hello World!" text = hello() print(text)

python return_demo.py

Hello World!

Можно обойтись и без переменной text

# print(hello())

РЕКЛАМА от Яндекса. Может быть недоступна в вашем регионе

Конец рекламы. Если там пусто считайте это рекламой моей телеги

Параметры и аргументы

Обратим внимание на то, что в предыдущих примерах мы вызвали функции lazy() и hello() оставляя скобки пустыми.

Допустим нужно сделать фунцию hello() более гибкой - то есть выводить на экран не только Hello World! но и например: Hello Earth!, Hello Everyone!, Hello Mars! и так далее.

Чтобы решить эту задачу надо сделать так, чтобы Hello и ! выводились как прежде, а в середину вставлялось любое слово, которое нам нужно.

Это делается с помощью передачи в функцию аргумента.

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

Аргументу нужно придумать имя. Важно, чтобы оно не совпадало с одним из зарезервированных имён.

Выберем имя x

def hello(x): print("Hello World!") hello("Saturn")

python param_demo.py

Hello World!

Мы передали аргумент, но нигде не использовали его в функции. Ошибки не произошло, но на экране вместо Saturn по-прежнему World.

Обыно IDE сигнализирует о том, что параметр никак не задействован.

Изменим код так, чтобы переданный аргумент использовался.

def hello(x): print("Hello", x + "!") hello("World") hello("Natalia")

Hello World! Hello Natalia!

Уже несколько раз были использваны такие термины как аргумент и параметр.

В этих понятиях стоит разобраться подробнее.

Параметры vs. аргументы

Параметры и аргументы это почти одно и то же, но термин параметры применяют во время объявления функции а аргументы в момент вызова.

В некоторых источниках вместо термина параметры используется термин формальные аргументы (formal arguments)

Проще всего понять на примере

Напишем функцию, которая складывает два числа и выводит результат на экран.

def sum(first, second): print(first + second) sum(3, 4)

python functions_demo.py

7

У функции sum() два параметра first и second. Аргументы, которые она получила это 3 и 4.

Для закрепления рассмотрим функцию add(), которая просто возвращает два числа без вывода.

def add(a, b): return a + b add(3, 4)



У функции add() a и b это параметры, а 3 и 4 это аргументы.

Параметрами функции являются a и b.

В функцию add() было передано два аргумента: 3 и 4.

РЕКЛАМА хостинга Beget, которым я пользуюсь более десяти лет

Изображение баннера

Конец рекламы хостинга Beget, который я всем рекомендую.

type hints

В соответствии с PEP 484 рекомендуется указывать ожидаемые типы данных как для параметров, так и для возвращаемого значения

# typehints.py def sum(first: float, second: float) -> float: return(first + second) print(sum(3.0, 4.6))

python typehints.py

7.6

Подробнее с подсказками о типах вы можете познакомиться в статье Type Hints

Типы аргументов

Аргументы могут передаваться как позиционные (positional) и как именованные (keyword).

Позиционные аргументы

Позиционные аргументы сопоставляются с параметрами (parameter или formal argument) в соответсвии с порядком следования.

Рассмотрим функцию sub(), которая вычитает из первого аргумента второй.

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

a = sub(2, 3)

Функция sub()

sub(2, 3) = -1

sub(3, 2) = 1. Порядок имеет значение

Именованные аргументы

Именованные аргументы сопоставляются с параметрами по их имени.

Пусть функция sub() выглядит следующим образом:

def sub(minuend, subtrahend): difference = minuend - subtrahend return difference

Пример передачи двух именованных аргументов

a = sub(minuend=4, subtrahend=5)

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

# Оба аргумента позиционные print(sub(48, 8)) # Первый позиционный, второй именованный print(sub(48, subtrahend=8)) # Оба именованные print(sub(minuend=48, subtrahend=8)) # Оба именованные в другом порядке print(sub(subtrahend=8, minuend=48))

40 40 40 40

Нельзя повторно передавать уже переданный порядковый элемент как именованный в надежде что интерпретатор разберётся и перейдёт к следующему порядковому элементу.

В примере ниже я передаю 4 как позиционный аргумент. Он рассматривается интерпретатором как minuend. Затем я ещё раз передаю minuend уже как именованный аргумент с надеждой, что интерпретатор поймет, что это настоящий minuend и переопределит 4 как subtrahend. Интерпретатор этим заниматься не будет.

def sub(minuend, subtrahend): difference = minuend - subtrahend return difference print(sub(4, minuend=8))

Traceback (most recent call last): File "C:\A\functions_demo.py", line 7, in <module> print(sub(4, minuend=8)) ^^^^^^^^^^^^^^^^^ TypeError: sub() got multiple values for argument 'minuend'

Нельзя передавать именованные аргументы перед позиционными

def sub(minuend, subtrahend): difference = minuend - subtrahend return difference print(sub(minuend=48, 8))

File "C:\A\functions_demo.py", line 7 print(sub(minuend=48, 8)) ^ SyntaxError: positional argument follows keyword argument

Не во все функции можно передавать именованные аргументы. В статье про *args, **kwargs вы можете найти пример функции sub(), в котором нет именованных параметров.

Предположим, функция div() делит divident (делимое) на divisor (делитель). Порядок в котом передаются именованные аргументы не важен, значения будут присвоены по совпадении имён.

a = div(divident=4, divisor=2)

Способ, которым передаётся определённый аргумент, определяется при вызове функции.

Один и тот же аргумент может быть передан как позиционный и как именованный.

def main(): print(div()) print(div(9, 3)) print(div(divisor=5, divident=25)) def div(divident=2, divisor=1): return divident/divisor if __name__ == "__main__": main()

2.0 3.0 5.0

Сперва нужно передавать позиционные аргументы, затем именованные

# Вызов print(div(divisor=5, 25)) # Приведёт к ошибке

print(div(divisor=5, 25)) ^ SyntaxError: positional argument follows keyword argument

Ещё один пример здесь

Про передачу заранее неопределёного количества аргументов читайте статью *args **kwargs

РЕКЛАМА от Яндекса. Может быть недоступна в вашем регионе

Конец рекламы. Если там пусто считайте это рекламой моей телеги

Аргументы по умолчанию

Аргументы, у которых есть значения по умолчанию, должны быть переданы после аргументов, у которых их нет.

# Arguments with default values must come # after those without default values. def banner(message, border="-"): line = border * len(message) print(line) print(message) print(line) banner("www.HeiHei.ru") # will use default "-" banner("www.TopBicycle.ru", "*") banner("www.KickBrains.ru", border="|")

python default_argumet_values.py

------------- www.HeiHei.ru ------------- ***************** www.TopBicycle.ru ***************** ||||||||||||||||| www.KickBrains.ru |||||||||||||||||

Изменяемые vs. неизменяемые

Mutable vs Immutable значения по умолчанию

В качестве значений по умолчанию лучше использовать неизменяемые (immutable) объекты.

Если и использовать изменяемые объекты, то нужно понимать, что они получают значения один раз - когда интерпретатор проходит объявление функции (def)

Пример

import time print("time.ctime()", time.ctime()) def show_default(arg=time.ctime()): print(arg) show_default() time.sleep(2) print("time.ctime()", time.ctime()) show_default()

time.ctime() Wed Jun 8 11:16:04 2022 Wed Jun 8 11:16:04 2022 time.ctime() Wed Jun 8 11:16:06 2022 Wed Jun 8 11:16:04 2022

Как видно из вывода в терминал - время уже ушло вперёд - 11:16:06 а функция как возвращала значение по умолчанию созданное в 11:16:04 так и продолжает это делать.

Рассмотрим ещё один пример. Теперь в качестве изменяемого объекта возьмём список

def add_spam(menu=[]): menu.append("spam") return menu breakfast = ['bacon', 'eggs'] print(add_spam(breakfast)) lunch = ['borsh'] print(add_spam(lunch)) print(add_spam()) print(add_spam()) print(add_spam())

['bacon', 'eggs', 'spam'] ['borsh', 'spam'] ['spam'] ['spam', 'spam'] ['spam', 'spam', 'spam']

В первых двух случаях (завтрак и обед) функция работает как задумано. Но, если вызвать её без аргументов создасться пустой список и он не будет пересоздан - в него просто добавляются новые и новые элементы.

Решить эту проблему можно сделав аргумент по умолчанию неизменяемым

def add_spam(menu=None): if menu is None: menu = [] menu.append("spam") return menu breakfast = ['bacon', 'eggs'] print(add_spam(breakfast)) lunch = ['borsh'] print(add_spam(lunch)) print(add_spam()) print(add_spam()) print(add_spam())

['bacon', 'eggs', 'spam'] ['borsh', 'spam'] ['spam'] ['spam'] ['spam']

Импорт из другого модуля

Рассмотрим скрипт test_return.py

def test_return(): return "A" v = test_return() print(v)

python test_return.py

A

Всё выглядит хорошо, функция вернула строку, мы видим её в терминале.

Теперь разберёмся как использовать эту функцию в другом модуле, а не вызвать в том же скрипте

tree return_ex

return_ex ├── parent_test_return.py └── test_return.py 0 directories, 2 files

# test_return def test_return(): return "A"

# parent_test_return from test_return import test_return v = test_return() print(v)

python parent_test_return.py

A

Области видимости

Рассмотрим четыре стандартные области видимости

Local: Внутри текущей функции
Enclosing: Внутри функции, вызывающей текущую
Global: Внутри модуля
Built-in: Встроенные в Python функции

Эта четвёрка описывается акронимом LEGB

Области видимости не имеют прямой связи с блоками кода.

Например, цикл for, хотя и нуждется в дополнительном отступе, не вводит новых областей видимости.

count = 0 def show_count(): print(count) def set_count(c): count = c show_count() # 0 set_count(5) show_count() # 0

0 0

count = 0 def set_count(c): global count count = c show_count() # 0 set_count(5) show_count() # 0

0 5

Продолжить изучение областей видимости функции можно в статье о локальных функциях замыкания: области видимости

Вызываемые объекты

Рассмотрим файл callables.py

def is_even(x): return x % 2 == 0 print(callable(is_even)) # True is_odd = lambda x: x % 2 == 1 print(callable(is_odd)) # True # Classes are callable print(callable(list)) # True # Methods are callable print(callable(list.append)) # True class CallMe: def __call__(self): print("Called!") my_call_me = CallMe() print(callable(my_call_me)) # True # Strings are not callable print(callable("This is no callable")) # False

python callables.py

True True True True True False

from functools import reduce import operator print(operator.add(8, 9)) print(reduce(operator.add, [1, 2, 3, 4, 5])) numbers = [1, 2, 3, 4, 5] accumulator = operator.add(numbers[0], numbers[1]) for item in numbers[2:]: accumulator = operator.add(accumulator, item) print(accumulator) def mul(x, y): print(f"mul {x} {y}") return x * y print(reduce(mul, range(1, 10))) # reduce(mul, []) # TypeErr reduce(mul, [1]) # returns element without calling reduce print(reduce(mul, [1])) # 1 # Initial value is added as a first accumulated value values = [1, 2, 3]

17 15 15 mul 1 2 mul 2 3 mul 6 4 mul 24 5 mul 120 6 mul 720 7 mul 5040 8 mul 40320 9 362880 1 6 0 6

Счетчик вызовов

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

# apps.py def slow_app(): slow_app.calls += 1 print(f"Function called {slow_app.calls} times") slow_app.calls = 0 if __name__ == "__main__": for _ in range(3): slow_app()

Function called 1 times Function called 2 times Function called 3 times

Если мы импортируем эту функцию в другой модуль, счётчик по-прежнему будет работать.

# main.py from apps import slow_app if __name__ == "__main__": for _ in range(5): slow_app()

Function called 1 times Function called 2 times Function called 3 times Function called 4 times Function called 5 times

Изучим подробнее что происходит

# main.py import sys from apps import slow_app mod = sys.modules["apps"] print(mod) # У модуля apps будет атрибут slow_app print(dir(mod)) # У slow_app будет атрибут calls print(dir(mod.slow_app)) if __name__ == "__main__": for _ in range(2): slow_app()

<module 'apps' from 'C:\A\apps.py'> ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'slow_app'] ['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__getstate__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__type_params__', 'calls'] Function called 1 times Function called 2 times

Интерпретатор при загрузке модуля пробегает по всему коду и подхватывает аттрибут calls.

Затем наш модуль хранится в sys.modules[] и доступен для импорта.

В следующем примере мы попытаемся импортировать модуль два раза и убедимся что просто получаем две ссылки на один и тот же модуль.

# main.py import sys from apps import slow_app from apps import slow_app as sa mod = sys.modules["apps"] print(mod.slow_app.calls) if __name__ == "__main__": for _ in range(2): slow_app() print(mod.slow_app.calls) sa() print(mod.slow_app.calls)

0 Function called 1 times 1 Function called 2 times 2 Function called 3 times 3 Function called 4 times 4

Встроенные функции и теория

Встроенные функции Python
all()
any()
chr()
copy()
dir()
enumerate()
filter()
id()
hash()
isinstance()
iter()
map()
next()
ord()
random()
sorted()
type()
uuid()
unichr()
zip()

Автор статьи: Андрей Олегович

Похожие статьи
*args **kwargs
sys.argv: аргументы командной строки
Лямбда функции
Функции первого класса
Замыкания
Декораторы
Кэширование
if, elif, else
Методы
Итерация
Генераторы
Генераторы: Maintaining State
Встроенные фукнции: all()zip()

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: