You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

742 lines
29 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"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<ipython-input-5-459911d1e387>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-20-b35f2869d543>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-20-b35f2869d543>\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": [
"<class 'tuple'>\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": [
"<class 'tuple'>\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": [
"<class 'dict'>\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": [
"<class 'dict'>\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
}