Python: Создание (определение) функций
С помощью определения собственных функций писать и поддерживать программы проще. Они позволяют объединять составные операции в одну. Поэтому в этом уроке поговорим, как создавать собственные функции.
Допустим, мы хотим отправить письма на сайте — это достаточно сложный процесс, который включает взаимодействие с внешними системами. Но если определить функцию, вся сложность скроется за одной простой функцией:
# Гипотетический пример # Место откуда берется функция from emails import send email = 'support@hexlet.io' title = 'Помогите' body = 'Я написал историю успеха, как я могу получить скидку?' # Один маленький вызов — много логики внутри send(email, title, body)
Внутри себя этот вызов выполняет много логики: соединяется с почтовым сервером, формирует правильный запрос на основе заголовка и тела сообщения, а затем все отправляет, не забыв закрыть соединение.
Создадим нашу первую функцию. Ее задача — вывести на экран приветствие:
# Определение функции # Определение не вызывает и не выполняет функцию # Мы лишь говорим, что теперь такая функция существует def show_greeting(): # Внутри тела отступ четыре пробела text = 'Hello, Hexlet!' print(text) # Вызов функции show_greeting() # => 'Hello, Hexlet!'
В отличие от обычных данных, функции выполняют действия. Поэтому их имена нужно указывать через глаголы: «построить что-то», «нарисовать что-то», «открыть что-то».
Описание, которое находится ниже имени функции с отступом, называется телом функции. Внутри тела можно описывать любой код. Это как небольшая самостоятельная программа — набор произвольных инструкций.
Тело выполняется в тот момент, когда запускается функция. При этом каждый вызов функции запускает тело независимо от других вызовов.
Тело функции может быть пустым, тогда внутри него используется ключевое слово pass :
# Минимальное определение функции def noop(): pass noop()
У понятия «создать функцию» много синонимов: «реализовать», «определить» и даже «заимплементить». Они часто встречаются на практике. Создавая свою функцию, вы облегчите сложные операции и сделаете разработку проще.
Задание
Реализуйте функцию с именем print_motto() , которая выведет на экран фразу Winter is coming.
print_motto() # => Winter is coming
В задачах, в которых нужно реализовать функцию, эту функцию вызывать не нужно. Вызывать функцию будут автоматизированные тесты, которые проверяют его работоспособность. Пример с вызовом выше показан только для того, чтобы вы понимали, как ваша функция будет использоваться.
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Полезное
Функции
Рассмотрим простейшую функцию, которая ничего не делает.
Чтобы её объявить используйте слово def затем придумайте название — например lazy поставьте круглые скобки () и двоеточие
Общий синтаксис следующий
def имя_функции (): какой-то код
В нашем случае получается
pass означает просто продолжать код ничего не предпринимая. Он применяется далеко не во всех функциях.
Наша функция создана, иначе говоря объявлена. Это значит, что она существует где-то в коде и может что-то сделать.
Но не делает. Чтобы функция что-то делала её нужно вызвать. В нашем случае достаточно написать её имя и круглые скобки.
Если никаких ошибок не допущено вывод будет пустым
def
Ключевое слово def используется для определения новых функций.
Оно связывает объект функции с именем
Hello World!
Объявим и вызовем функцию, которая пишет Hello World!
def hello (): print(«Hello World!») hello()
Функцию не обязательно писать в одну строку. Лучше после двоеточия перейти на новую строку и сделать отступ из четырёх пробелов.
def hello (): print(«Hello World!») hello()
Напишем функцию, которая складывает два числа.
def sum ( first , second ): print (first + second) sum(3,4)
type hints
В соответствии с PEP 484 рекомендуется указывать ожидаемые типы данных как для параметров, так и для возвращаемого значения
def sum (first: float , second: float ) -> float : print (first + second) sum ( 3.0 , 4.6 )
Никаких ограничений эти подсказки не накладывают. Можно спокойно выполнить
Подсказки нужны скорее для сторонней проверки, каким-либо инструментом.
Параметры и аргументы
Параметры и аргументы это почти одно и то же, но термин параметры применяют во время объявления функции а аргументы в момент вызова.
В некоторых источниках вместо термина параметры используется термин формальные аргументы (formal arguments)
Проще всего понять на примере
def add (a, b): return a+b add( 2 , 3 )
a и b это параметры, а 2 и 3 это аргументы
Типы аргументов
Аргументы могут передаваться как позиционные (positional) и как именованные (keyword).
Позиционные аргументы
Позиционные аргументы сопоставляются с параметрами (parameter или formal argument) в соответсвии с порядком следования.
Пример передачи двух позиционных аргументов
Предположим, функция sub() вычитает из первого аргумента второй.
sub(2, 3) = -1, sub(3, 2) = 1. Порядок имеет значение
Именованные аргументы
Именованные аргументы сопоставляются с параметрами по их имени.
Пример передачи двух именованных аргументов
a = div ( divident = 4 , divisor = 2 )
Предположим, функция div() делит divident (делимое) на divisor (делитель). Порядок в котом передаются именованные аргументы не важен, значения будут присвоены по совпадении имён.
Способ, которым передаётся определённый аргумент, определяется при вызове функции.
Один и тот же аргумент может быть передан как позиционный и как именованный.
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()
Сперва нужно передавать позиционные аргументы, затем именованные
# Вызов 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= «|» )
Значения по умолчанию
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:0 6 2022 Wed Jun 8 11:16:0 4 2022
Как видно из вывода в терминал — время уже ушло вперёд — 11:16:0 6 а функция как возвращала значение по умолчанию созданное в 11:16:0 4 так и продолжает это делать.
Рассмотрим ещё один пример. Теперь в качестве изменяемого объекта возьмём список
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’]return
Рассмотрим скрипт test_return.py
def test_return (): return «A» v = test_return() print (v)
Всё выглядит хорошо, функция вернула строку, мы видим её в терминале.
Но что будет, если эту функцию импортировать, а не вызвать в том же скрипте
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)
Области видимости
Рассмотрим четыре стандартные области видимости
Эта четвёрка описывается акронимом LEGB
Области видимости не имеют прямой связи с блоками кода.
Например, цикл for, хотя и нуждется в дополнительном отступе, не вводит новых областей видимости.
count = 0 def show_count (): print (count) def set_count (c): count = c show_count() # 0 set_count( 5 ) show_count() # 0
count = 0 def set_count (c): global count count = c show_count() # 0 set_count( 5 ) show_count() # 0
Продолжить изучение областей видимости функции можно в статье о локальных функциях замыкания: области видимости
Вызываемые объекты
Рассмотрим файл 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
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 » ) 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