{ "cells": [ { "cell_type": "markdown", "id": "d8cf2624", "metadata": {}, "source": [ "# Функции #" ] }, { "cell_type": "markdown", "id": "f57336b3", "metadata": {}, "source": [ "Привет. На этой лекции мы с вами поговорим о функциях и о том, как с ними работать в языке Python.\n", "\n", "Скорее всего, вы уже встречались с функциями в других языках программирования и знаете, что функция - это просто блок кода, который можно переиспользовать несколько раз в разных местах программы.\n", "\n", "Мы можем вызывать функцию с какими-то аргументами и получать значения обратно.\n", "\n", "Чтобы определить функцию в языке Python, нужно использовать литерал `def` и с помощью отступа определить блок кода функции. В данном случае мы определяем функцию `get_seconds`, которая просто возвращает количество секунд на данный момент." ] }, { "cell_type": "code", "execution_count": 1, "id": "8cb5198b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "32" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from datetime import datetime\n", "\n", "def get_seconds():\n", " \"\"\"Return current seconds\"\"\"\n", " return datetime.now().second\n", "\n", "get_seconds()" ] }, { "cell_type": "markdown", "id": "11105ce4", "metadata": {}, "source": [ "Обратите внимание, функцию я определяю с помощью нижнего подчеркивания по `PEP8`. А дальше у функции может идти документационная строка, которая описывает то, что, собственно, внутри функции происходит. Чтобы вернуть значения из функции, мы используем `return`, в данном случае возвращаем количество секунд, а можем не писать `return`, и тогда по умолчанию вернется `None`." ] }, { "cell_type": "markdown", "id": "b58ccdf2", "metadata": {}, "source": [ "Чтобы вызвать функцию, нужно использовать круглые скобочки, и если нужно, передать туда параметры. Чтобы получить документационную строку, можно обратиться к атрибуту `__doc__`, а имя функции получается с помощью атрибута `__name__`." ] }, { "cell_type": "code", "execution_count": 2, "id": "9a6d8edf", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Return current seconds'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_seconds.__doc__" ] }, { "cell_type": "code", "execution_count": 3, "id": "b148cbf7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'get_seconds'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_seconds.__name__" ] }, { "cell_type": "markdown", "id": "e378fc9d", "metadata": {}, "source": [ "Чаще всего функция определяется именно с параметрами, потому что нам важно передать какие-то значения внутри функции и работать уже с ними. Мы определяем функцию `split_tags`, которая принимает какой-то `tag_string`, в данном случае это строка с тегами, например, это `python`, `perl` и `pascal`." ] }, { "cell_type": "code", "execution_count": 4, "id": "5ad43014", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['python', 'perl', 'pascal']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def split_tags(tag_string):\n", " tag_list = []\n", " \n", " for tag in tag_string.split(\",\"):\n", " tag_list.append(tag.strip())\n", " \n", " return tag_list\n", "\n", "split_tags(\"python, perl, pascal\")" ] }, { "cell_type": "markdown", "id": "a17c8dec", "metadata": {}, "source": [ "Мы хотим разбить эту строку по запятым и вернуть список тегов. Эта функция, собственно, это и делает. Мы передаем в нашу функцию строку и получаем обратно список. Если мы попытаемся вызвать эту функцию без параметров, у нас выпадет ошибка `TypeError`, потому что функция ожидает параметр и не знает, что делать, если его нет." ] }, { "cell_type": "code", "execution_count": 5, "id": "cc8143dd", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "split_tags() missing 1 required positional argument: 'tag_string'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0msplit_tags\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: split_tags() missing 1 required positional argument: 'tag_string'" ] } ], "source": [ "split_tags()" ] }, { "cell_type": "markdown", "id": "96584d83", "metadata": {}, "source": [ "Как вы могли заметить, мы не указываем явно, какого типа параметры функция ожидает, потому что Python - это динамический язык.\n", "\n", "Если вы уже сталкивались со статически типизированными языками вроде C, вы могли видеть, что, например, в C типы аннотируются, мы явно указываем, какого типа должен быть параметр функции и какого типа возвращаемые значения.\n", "\n", "[Aннотация типов](Aннотация%20типов.ipynb)" ] }, { "cell_type": "markdown", "id": "6edf6fa1", "metadata": {}, "source": [ "Как же передаются параметры в наши функции? Например, в статических языках, опять же, вроде C, проводится очень четкое различие между передачей по ссылке и по значению.\n", "\n", "В Python'е каждая переменная является связью имени с объектом в памяти. И именно это имя, именно эта ссылка на объект передается в функцию." ] }, { "cell_type": "markdown", "id": "a080fbf3", "metadata": {}, "source": [ "Таким образом, если мы определим функцию `extender`, которая принимает какой-то `source_list`, то есть исходный `list`, и новый `list` и пытается расширить `source_list` с помощью `extend_list`'а, то есть просто добавить в конец все его элементы, мы вот определяем `values` и пытаемся расширить `values` с помощью `[4, 5, 6]`, то окажется, что `values` у нас изменится." ] }, { "cell_type": "code", "execution_count": 12, "id": "65c8b6ed", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 4, 5, 6]\n" ] } ], "source": [ "def extender(source_list, extend_list):\n", " source_list.extend(extend_list)\n", " \n", "values = [1, 2, 3]\n", "extender(values, [4, 5, 6])\n", "\n", "print(values)" ] }, { "cell_type": "markdown", "id": "883ff05c", "metadata": {}, "source": [ "Что же произошло? Наша ссылка на объект в памяти попала в `extender`. И так как `list` является изменяемым объектом, мы можем изменить этот `list`, и объект в памяти изменился. Наш `values` в глобальном `scope` поменял свое значение. Если мы попытаемся как-то изменить неизменяемое значение, в данном случае `tuple`, то у нас ничего не получится, потому что мы передаем ссылку на объект в памяти, но объект является неизменяемым." ] }, { "cell_type": "code", "execution_count": 17, "id": "2f508089", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Guido', '31/01')\n" ] } ], "source": [ "def replacer(source_tuple, replace_with):\n", " source_tuple = replace_with\n", " \n", "user_info = (\"Guido\", \"31/01\")\n", "replacer(user_info, (\"Larry\", \"27/09\"))\n", "\n", "print(user_info)" ] }, { "cell_type": "markdown", "id": "06ae74a9", "metadata": {}, "source": [ "В данном случае у нас `user_info` осталось неизменным. Однако стоит быть внимательным с изменением каких-то глобальных переменных внутри функции. Это является плохим тоном, и не стоит так программировать, потому что часто бывает не очевидно, если вы вызываете какую-то функцию, а объект в глобальном `scope` изменяется. Используйте возвращаемое значение и не путайте других программистов." ] }, { "cell_type": "markdown", "id": "0ff0ab4f", "metadata": {}, "source": [ "В Python'е также существуют именованные аргументы, которые иногда бывают полезны. Например, мы можем определить функцию `say`, которая принимает два аргумента — `greeting` и `name`, просто приветствует какого-то человека.\n", "\n", "Однако мы можем также передать эти параметры в другом порядке, проименовав их с помощью `name` и `greeting`. Таким образом, мы передаем вначале имя, а потом `greeting`, тем не менее у нас всё работает точно так же." ] }, { "cell_type": "code", "execution_count": 18, "id": "2580a8b3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello Kitty!\n", "Hello Kitty!\n" ] } ], "source": [ "def say(greeting, name):\n", " print(f\"{greeting} {name}!\")\n", " \n", "say(\"Hello\", \"Kitty\")\n", "say(name=\"Kitty\", greeting=\"Hello\")" ] }, { "cell_type": "markdown", "id": "0bbd0c13", "metadata": {}, "source": [ "Немножко про область видимости. Важно понимать, что переменные, объявленные вне области видимости функции, нельзя изменять. В данном случае у нас есть глобальная переменная `result`, и мы пытаемся прибавить к ней внутри функции какое-то значение. Мы пытаемся к `result` прибавить единичку при вызове функции `increment`. У нас ничего не получается, падает ошибка, потому что мы не можем внутри функции изменять объекты из глобальной области видимости." ] }, { "cell_type": "code", "execution_count": 20, "id": "1b46f831", "metadata": {}, "outputs": [ { "ename": "UnboundLocalError", "evalue": "local variable 'result' referenced before assignment", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mUnboundLocalError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mincrement\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36mincrement\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mincrement\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mresult\u001b[0m \u001b[1;33m+=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mUnboundLocalError\u001b[0m: local variable 'result' referenced before assignment" ] } ], "source": [ "result = 0\n", "\n", "def increment():\n", " result += 1\n", " return result\n", "\n", "print(increment())" ] }, { "cell_type": "markdown", "id": "cc5fa177", "metadata": {}, "source": [ "И очень важно это соблюдать опять же по той же самой причине. Если мы внутри функции изменяем какие-то глобальные объекты, очень часто это не очевидно. У нас есть глобальная переменная, и вызов каких-то функций меняет ее значение. Совершенно непонятно, что это происходит и часто приводит к запутанному коду." ] }, { "cell_type": "markdown", "id": "5a903105", "metadata": {}, "source": [ "Существует в Python'е возможность изменять глобальные переменные с помощью `global`, например, или `non local`, но я не рекомендую вам использовать эти особенности." ] }, { "cell_type": "code", "execution_count": 29, "id": "f60a05d7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ], "source": [ "result = 0\n", "\n", "def increment():\n", " global result\n", " \n", " result += 1\n", " return result\n", "\n", "print(increment())" ] }, { "cell_type": "markdown", "id": "4fe3001d", "metadata": {}, "source": [ "Существует также возможность использовать аргументы по умолчанию вашей функции. Часто у вас бывают какие-то аргументы, которые можно передавать, а можно не передавать. И у них, у этих аргументов, могут быть какие-то дефолтные значения. В данном случаем мы можем приветствовать человека, если передано имя, а можем просто вывести какую-то дефолтную фразу." ] }, { "cell_type": "code", "execution_count": 30, "id": "28ec1b28", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, it's me...\n" ] } ], "source": [ "def greeting(name=\"it's me...\"):\n", " print(f\"Hello, {name}\")\n", " \n", "greeting()" ] }, { "cell_type": "markdown", "id": "be47e975", "metadata": {}, "source": [ "Однако, стоит быть внимательным с аргументами по умолчанию, если мы используем в качестве аргументов по умолчанию изменяемые значения. Обратите внимание на пример.\n", "\n", "У нас есть функция `append_one`, и в качестве дефолтного значения, значения по умолчанию `iterable`, у нас используется список.\n", "\n", "Собственно `append_one` просто прибавляет единичку к переданному списку или дефолтному списку. То есть у нас возвращается либо наш исходный список плюс один, либо просто список из единички. По крайней мере так мы ожидаем." ] }, { "cell_type": "code", "execution_count": 31, "id": "4723d072", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 1]\n" ] } ], "source": [ "def append_one(iterable=[]):\n", " iterable.append(1)\n", " return iterable\n", "\n", "print(append_one([1]))" ] }, { "cell_type": "markdown", "id": "abbde05d", "metadata": {}, "source": [ "Если мы передадим в `append_one` список из единицы, нам вернется две единички, что мы и ожидаем. Однако что же произойдет, если мы вызовем `append_one` два раза?" ] }, { "cell_type": "code", "execution_count": 32, "id": "84b2ef43", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1]\n", "[1, 1]\n" ] } ], "source": [ "print(append_one())\n", "print(append_one())" ] }, { "cell_type": "markdown", "id": "db6f038f", "metadata": {}, "source": [ "Вначале, как мы и ожидаем, к нам вернется единичка, потому что мы взяли наше дефолтное значение, добавили единичку в список и вернули. Однако если мы вызовем во второй раз, окажется, что у нас уже две единички, хотя мы явно ожидаем одну.\n", "\n", "Чтобы понять, почему это так, можно посмотреть на дефолтное значение нашей функции, и окажется, что там уже содержатся эти самые две единички. Почему так происходит?\n", "\n", "При определении функции, когда интерпретатор Python'а бежит по вашему файлу с кодом, определяется связь между именем функции и дефолтными значениями. Таким образом, у каждой функции появляется словарь, появляется `tuple` какой-то с дефолтными значениями. И именно в эти переменные каждый раз и происходит запись. Таким образом, если дефолтные значения являются изменяемыми, в них можно записывать, потому что это обычные переменные." ] }, { "cell_type": "code", "execution_count": 33, "id": "88a2eab2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "([1, 1],)\n" ] } ], "source": [ "print(append_one.__defaults__)" ] }, { "cell_type": "markdown", "id": "da7c8a62", "metadata": {}, "source": [ "Что же нужно делать в таком случае? Нужно определять дефолтные значения как `None`. И если нам не передан какой-то параметр, мы просто создаем новый список на лету. Можно это делать двумя механизмами." ] }, { "cell_type": "code", "execution_count": 34, "id": "166799cb", "metadata": {}, "outputs": [], "source": [ "def function(iterable=None):\n", " if iterable is None:\n", " iterable = []\n", " \n", "def function(iterable=None):\n", " iterable = iterable or []" ] }, { "cell_type": "markdown", "id": "fe32e50c", "metadata": {}, "source": [ "Очень красивой особенностью Python'а является возможность определения функции, которая принимает разные количества аргументов.\n", "\n", "Мы можем определить функцию `printer`, которая принимает разное количество аргументов. Может быть один, два, три, четыре, сотня аргументов.\n", "\n", "В данном случае все аргументы записываются в `tuple args`. И мы видим, что это действительно `tuple`, если мы выведем тип. И мы можем, например, эти аргументы как-то потом использовать, можем просто по ним как-то пробежаться и вывести их." ] }, { "cell_type": "code", "execution_count": 35, "id": "5a5112e7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "1\n", "2\n", "3\n", "4\n", "5\n" ] } ], "source": [ "def printer(*args):\n", " print(type(args))\n", " \n", " for argument in args:\n", " print(argument)\n", "\n", "printer(1, 2, 3, 4, 5)" ] }, { "cell_type": "markdown", "id": "2552db47", "metadata": {}, "source": [ "Точно так же можно разворачивать наш список в аргументах. Мы можем определить наш список с Джоном, Биллом и Эми и передать наш список как аргументы в нашу функцию." ] }, { "cell_type": "code", "execution_count": 36, "id": "b458e59b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "John\n", "Bill\n", "Amy\n" ] } ], "source": [ "name_list = [\"John\", \"Bill\", \"Amy\"]\n", "printer(*name_list)" ] }, { "cell_type": "markdown", "id": "6a2299cf", "metadata": {}, "source": [ "Таким образом, первым аргументом станет Джон, вторым — Билл, и третьим — Эми.\n", "\n", "Это так называемая распаковка списка. Есть еще подобные способы использования:" ] }, { "cell_type": "code", "execution_count": 40, "id": "21a58d1d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 2 [3, 4, 5]\n" ] } ], "source": [ "a, b, *others = [1, 2, 3, 4, 5]\n", "\n", "print(a, b, others)" ] }, { "cell_type": "code", "execution_count": 41, "id": "1bdc2fa4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 2 [3, 4] 5\n" ] } ], "source": [ "a, b, *others, c = [1, 2, 3, 4, 5]\n", "\n", "print(a, b, others, c)" ] }, { "cell_type": "markdown", "id": "cf3a0381", "metadata": {}, "source": [ "Точно так же это работает в случае со словарями, в данном случае мы можем определить функцию `printer`, которая принимает разное количество именованных аргументов." ] }, { "cell_type": "code", "execution_count": 37, "id": "8ade7f94", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "a 10\n", "b 11\n" ] } ], "source": [ "def printer(**kwargs):\n", " print(type(kwargs))\n", " for key, value in kwargs.items():\n", " print(f\"{key} {value}\")\n", " \n", "printer(a=10, b=11)" ] }, { "cell_type": "markdown", "id": "5a455966", "metadata": {}, "source": [ "Собственно, все записывается в `kwargs`, и таким образом `kwargs` у нас останется `dict`'ом. Если мы передадим два именованных аргумента, `a = 10` и `b = 11`, то у нас получится словарь, и мы можем потом этот словарь как-то использовать, используя параметры и, например, просто их выводя на экран.\n", "\n", "Точно так же мы можем разыменовывать, разворачивать эти словари в обратную сторону. Таким образом, если у нас есть словарь, мы можем передавать значения из этого словаря как аргументы, именованные, в нашу функцию. Таким образом, когда мы используем две звездочки при вызове функции, у нас первым параметром становится `user_id`, а вторым параметром становится `feedback`. Это используется практически везде и позволяет вам определять очень гибкие функции, которые принимают различное количество аргументов — именованных и позиционных." ] }, { "cell_type": "code", "execution_count": 38, "id": "d4989f77", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "user_id 117\n", "feedback {'subject': 'Registration fields', 'message': 'There is no country for old men'}\n" ] } ], "source": [ "payload = {\n", " \"user_id\": 117,\n", " \"feedback\": {\n", " \"subject\": \"Registration fields\",\n", " \"message\": \"There is no country for old men\"\n", " }\n", "}\n", "\n", "printer(**payload)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 5 }