Привет. В этом видео мы с вами поговорим о функциях и о том, как с ними работать в языке Python. Скорее всего, вы уже встречались с функциями в других языках программирования и знаете, что функция — это просто блок кода, который можно переиспользовать несколько раз в разных местах программы. Мы можем вызывать функцию с какими-то аргументами и получать значения обратно. Чтобы определить функцию в языке Python, нужно использовать литерал def и с помощью отступа определить блок кода функции. В данном случае мы определяем функцию get_seconds, которая просто возвращает количество секунд на данный момент. Обратите внимание, функцию я определяю с помощью нижнего подчеркивания по PEP8. А дальше у функции может идти документационная строка, которая описывает то, что, собственно, внутри функции происходит. Чтобы вернуть значения из функции, мы используем return, в данном случае возвращаем количество секунд, а можем не писать return, и тогда по умолчанию вернется None. Чтобы вызвать функцию, нужно использовать круглые скобочки, и если нужно, передать туда параметры. Чтобы получить документационную строку, можно обратиться к атрибуту doc, а имя функции получается с помощью атрибута name. Чаще всего функция определяется именно с параметрами, потому что нам важно передать какие-то значения внутри функции и работать уже с ними. Мы определяем функцию split_tags, которая принимает какой-то tag_string, в данном случае это строка с тегами текущего курса, например, это python, coursera и mooc. Мы хотим разбить эту строку по запятым и вернуть список тегов. Эта функция, собственно, это и делает. Мы передаем в нашу функцию строку и получаем обратно список. Если мы попытаемся вызвать эту функцию без параметров, у нас выпадет ошибка TypeError, потому что функция ожидает параметр и не знает, что делать, если его нет. Как вы могли заметить, мы не указываем явно, какого типа параметры функция ожидает, потому что Python — это динамический язык. Если вы уже сталкивались со статически типизированными языками вроде C, вы могли видеть, что, например, в C типы аннотируются, мы явно указываем, какого типа должен быть параметр функции и какого типа возвращаемые значения. В Python'е последних версий появилась возможность аннотировать типы, и делается это с помощью двоеточия в случае параметров, и вот такой вот стрелочкой, если мы хотим указать, какого типа возвращаемое значение должно вернуться из функции. Однако, что интересно, если мы передадим даже параметры других типов, как в данном случае, то у нас код все равно исполняется, потому что Python — это динамический язык, и аннотация типов призвана помочь программисту или его IDE отловить какие-то ошибки. Тем не менее код все равно исполняется. Если вы считаете это необходимым, можете использовать аннотацию типов. Как же передаются параметры в наши функции? Например, в статических языках, опять же, вроде C, проводится очень четкое различие между передачей по ссылке и по значению. В Python'е каждая переменная является связью имени с объектом в памяти. И именно это имя, именно эта ссылка на объект передается в функцию. Таким образом, если мы определим функцию extender, которая принимает какой-то source_list, то есть исходный list, и новый list и пытается расширить source_list с помощью extend_list'а, то есть просто добавить в конец все его элементы, мы вот определяем values и пытаемся расширить values с помощью [4, 5, 6], то окажется, что values у нас изменится. Что же произошло? Наша ссылка на объект в памяти попала в extender. И так как list является изменяемым объектом, мы можем изменить этот list, и объект в памяти изменился. Наш values в глобальном scope поменял свое значение. Если мы попытаемся как-то изменить неизменяемое значение, в данном случае tuple, то у нас ничего не получится, потому что мы передаем ссылку на объект в памяти, но объект является неизменяемым. В данном случае у нас user_info осталось неизменным. Однако стоит быть внимательным с изменением каких-то глобальных переменных внутри функции. Это является плохим тоном, и не стоит так программировать, потому что часто бывает не очевидно, если вы вызываете какую-то функцию, а объект в глобальном scope изменяется. Используйте возвращаемое значение и не путайте других программистов. В Python'е также существуют именованные аргументы, которые иногда бывают полезны. Например, мы можем определить функцию say, которая принимает два аргумента — greeting и name, просто приветствует какого-то человека. Однако мы можем также передать эти параметры в другом порядке, проименовав их с помощью name и greeting. Таким образом, мы передаем вначале имя, а потом greeting, тем не менее у нас всё работает точно так же. Немножко про область видимости. Важно понимать, что переменные, объявленные вне области видимости функции, нельзя изменять. В данном случае у нас есть глобальная переменная result, и мы пытаемся прибавить к ней внутри функции какое-то значение. Мы пытаемся к result прибавить единичку при вызове функции increment. У нас ничего не получается, падает ошибка, потому что мы не можем внутри функции изменять объекты из глобальной области видимости. И очень важно это соблюдать опять же по той же самой причине. Если мы внутри функции изменяем какие-то глобальные объекты, очень часто это не очевидно. У нас есть глобальная переменная, и вызов каких-то функций меняет ее значение. Совершенно непонятно, что это происходит и часто приводит к запутанному коду. Существует в Python'е возможность изменять глобальные переменные с помощью global, например, или non local, но я не рекомендую вам использовать эти особенности. Существует также возможность использовать аргументы по умолчанию вашей функции. Часто у вас бывают какие-то аргументы, которые можно передавать, а можно не передавать. И у них, у этих аргументов, могут быть какие-то дефолтные значения. В данном случаем мы можем приветствовать человека, если передано имя, а можем просто вывести какую-то дефолтную фразу. Однако, стоит быть внимательным с аргументами по умолчанию, если мы используем в качестве аргументов по умолчанию изменяемые значения. Обратите внимание на пример. У нас есть функция append_one, и в качестве дефолтного значения, значения по умолчанию iterable, у нас используется список. Собственно append_one просто прибавляет единичку к переданному списку или дефолтному списку. То есть у нас возвращается либо наш исходный список плюс один, либо просто список из единички. По крайней мере так мы ожидаем. Если мы передадим в append_one список из единицы, нам вернется две единички, что мы и ожидаем. Однако что же произойдет, если мы вызовем append_one два раза? Вначале, как мы и ожидаем, к нам вернется единичка, потому что мы взяли наше дефолтное значение, добавили единичку в список и вернули. Однако если мы вызовем во второй раз, окажется, что у нас уже две единички, хотя мы явно ожидаем одну. Чтобы понять, почему это так, можно посмотреть на дефолтное значение нашей функции, и окажется, что там уже содержатся эти самые две единички. Почему так происходит? При определении функции, когда интерпретатор Python'а бежит по вашему файлу с кодом, определяется связь между именем функции и дефолтными значениями. Таким образом, у каждой функции появляется словарь, появляется tuple какой-то с дефолтными значениями. И именно в эти переменные каждый раз и происходит запись. Таким образом, если дефолтные значения являются изменяемыми, в них можно записывать, потому что это обычные переменные. Что же нужно делать в таком случае? Нужно определять дефолтные значения как None. И если нам не передан какой-то параметр, мы просто создаем новый список на лету. Можно это делать двумя механизмами. Очень красивой особенностью Python'а является возможность определения функции, которая принимает разные количества аргументов. Мы можем определить функцию printer, которая принимает разное количество аргументов. Может быть один, два, три, четыре, сотня аргументов. В данном случае все аргументы записываются в tuple args. И мы видим, что это действительно tuple, если мы выведем тип. И мы можем, например, эти аргументы как-то потом использовать, можем просто по ним как-то пробежаться и вывести их. Точно так же можно разворачивать наш список в аргументах. Мы можем определить наш список с Джоном, Биллом и Эми и передать наш список как аргументы в нашу функцию. Таким образом, первым аргументом станет Джон, вторым — Билл, и третьим — Эми. Точно так же это работает в случае со словарями, в данном случае мы можем определить функцию printer, которая принимает разное количество именованных аргументов. Собственно, все записывается в kwargs, и таким образом kwargs у нас останется dict'ом. Если мы передадим два именованных аргумента, a = 10 и b = 11, то у нас получится словарь, и мы можем потом этот словарь как-то использовать, используя параметры и, например, просто их выводя на экран. Точно так же мы можем разыменовывать, разворачивать эти словари в обратную сторону. Таким образом, если у нас есть словарь, мы можем передавать значения из этого словаря как аргументы, именованные, в нашу функцию. Таким образом, когда мы используем две звездочки при вызове функции, у нас первым параметром становится user_id, а вторым параметром становится feedback. Это используется практически везде и позволяет вам определять очень гибкие функции, которые принимают различное количество аргументов — именованных и позиционных. Увидимся в следующем видео.