Основы функций
Введение
Много интересного про функции можно прочитать в официальной документации
| 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 |
О чём говорит первая строка этой таблицы:
- Функция определяется с помощью ключевого слова def
- Перед 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.
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
Встроенные функции и теория
| all() |
| any() |
| chr() |
| copy() |
| dir() |
| enumerate() |
| filter() |
| id() |
| hash() |
| isinstance() |
| iter() |
| map() |
| next() |
| ord() |
| random() |
| sorted() |
| type() |
| uuid() |
| unichr() |
| zip() |
Автор статьи: Андрей Олегович