{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Операторы потока управления\n", "\n", "В этой главе мы познакомимся с условным ветвлением и циклами в `python`.\n", "\n", "## Об отступах\n", "\n", "Как и во многих других языках программирования исходных код `python` исполняется сверху вниз, но операторы потока управления (такие как условное ветвление и циклы) позволяют проектировать менее линейные программы. Для того, чтобы научиться применять такие операторы в `python`, необходимо держать в голове, что [отступы](https://ru.wikipedia.org/wiki/%D0%9E%D1%82%D1%81%D1%82%D1%83%D0%BF_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)) (`indent`) в `python` имеют значения. \n", "\n", "Во многих языках программирования отступы нередко используются для обозначения программных блоков, например, тела функции, цикла или условного оператора. Ниже приводится два варианта одно и той же функция для вычисления суммы положительных элементов массива на языке `C/C++`, но во втором из них расставлены отступы, а в первом нет. \n", "\n", "````{panels}\n", "С отступами\n", "^^^\n", "```{code} c++\n", "double sum_positive(double* a, int n) {\n", "double s = 0;\n", "for (int i = 0; i < n; ++i) {\n", "if (a[i] > 0)\n", "s += a[i];\n", "}\n", "double s;\n", "}\n", "```\n", "---\n", "Без отступов\n", "^^^\n", "```{code} c++\n", "double sum_positive(double* a, int n) {\n", " double s = 0;\n", " for (int i = 0; i < n; ++i) {\n", " if (a[i] > 0)\n", " s += a[i];\n", " }\n", " double s;\n", "}\n", "```\n", "````\n", "Отступы не влияют на ход исполнения программы в `C/C++` и с точки зрения компилятора эти две функции абсолютно эквивалентны, но хороший программист расставляет их в любом случае. Это позволяет проще ориентироваться в программе и значительно повышает её читабельность. Например, к какому `if` относится `else` в следующем примере?\n", "\n", "```c++\n", "if(x > 0)\n", " if(y > 0)\n", " std::cout << \"if\";\n", "else\n", " std::cout << \"else\";\n", "``` \n", "При первом взгляде, кажется, что блок `else` должен выполниться, если `x <= 0`, т.е. `else` относится к внешнему оператору `if`. На самом деле это не так! Если расставлять отступы корректно, то такой путаницы будет гораздо меньше. \n", "\n", "```c++\n", "if(x > 0)\n", " if(y > 0)\n", " std::cout << \"if\";\n", " else\n", " std::cout << \"else\";\n", "``` \n", "\n", "Если в `C/C++` отступы оставляются на усмотрение программиста, то в `python` они являются обязательными. Создатель `python` Гвидо Ван Россум при проектировании языка решил вынудить программистов расставлять отступы правильно. Не следующий этим требованиям код, не запустится вообще или произведет не те действия, которые от него ожидал программист. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Составные операторы в `python`\n", "\n", "Операторы потока управления в `python` являются составными. Каждый такой оператор состоит из заголовка и тела (блок инструкций). Во многих других языках программирования для обозначения тела таких операторов используются скобки. Например, в `C/C++` за это отвечают фигурные скобки. В `python` же для этих целей используется отступ: инструкции в теле составного оператора вносятся с одинаковым дополнительным отступом вправо относительно заголовка этого оператора.\n", "\n", "``` \n", "инструкция снаружи составного оператора\n", "заголовок составного оператора:\n", " первая инструкция внутри составного оператора \n", " вторая инструкция внутри составного оператора\n", " ...\n", " последняя инструкция внутри составного оператора\n", "инструкция снаружи составного оператора\n", "```\n", "\n", "```{note}\n", "В конце заголовка составного оператора обязательно ставится символ двоеточия \"`:`\".\n", "```\n", "\n", "Интерпретатор `python` понимает, что относится к телу составного оператора, на основе отступов. \n", "- Пока он встречает постоянный отступ вправо после заголовка составного оператора, он считает, что эти инструкции относятся к нему. \n", "- Как только отступ возвращается на прежний уровень, интерпретатор понимает, что он покинул тело составного оператора.\n", "\n", "```{note}\n", "В `python` нет требований к тому, какого конкретно размера должен быть этот отступ. Важно лишь, чтобы он был постоянен в пределах каждого составного оператора. Чаще всего в качестве отступа используют 4 пробела. \n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Условное ветвление. `if`\n", "\n", "Начнем с оператора условного ветвления. Ниже представлен код `C/C++` и `python`, который решает задачу [fizz buzz](https://ru.wikipedia.org/wiki/Fizz_buzz).\n", "\n", "````{panels}\n", "`C/C++`\n", "^^^\n", "```c++\n", "if (i % 15 == 0)\n", " std::cout << \"FizzBuzz, \";\n", "else if (i % 3 == 0)\n", " std::cout << \"Fizz, \";\n", "else if (i % 5 == 0)\n", " std::cout << \"Buzz, \";\n", "else\n", " std::cout << i << \", \";\n", "```\n", "---\n", "`python`\n", "^^^\n", "``` python\n", "if i % 15 == 0:\n", " print('FizzBuzz, ')\n", "elif i % 3 == 0:\n", " print('Fizz, ')\n", "elif i % 5 == 0:\n", " print('Buzz, ')\n", "else:\n", " print(f'{i}, ')\n", "```\n", "````\n", "\n", "1. Заголовок оператора условного состоит из ключевого слова `if` и условия. Синтаксическая разница с `C/C++` заключается в том, что \n", " - условие необязательно помещать (и обычно не помещают) в круглые скобки;\n", " - после условия обязательно ставить двоеточие. \n", "2. После заголовка следует тело, т.е. блок инструкций, который выполняется если условие истинно. Синтаксическая разница с `C/C++` заключается в том, что \n", " - в `c++` для обозначения этого блока используются фигурные скобки, отступ этого блока расставляется программистом по его усмотрению; \n", " - в `python` для обозначения этого блока обязательно используется отступ.\n", "3. После тела опционально можно использовать ключевые слова `else` и/или `elif`, последнее из которых является сокращением от `else if`. После каждого из таких опциональных ветвлений `else`/`elif` должен следовать свой блок инструкций, который также обозначается отступом вправо. \n", "\n", "Итого, оператор условного ветвление имеет приблизительно следующий вид:\n", "```python\n", "if условие:\n", " первая инструкция блока if\n", " ...\n", " последняя инструкция блока if\n", "elif условие:\n", " первая инструкция блока elif\n", " ...\n", " последняя инструкция блока elif\n", "else: \n", " первая инструкция блока else\n", " ...\n", " последняя инструкция блока else\n", "```\n", "\n", "```{note}\n", "Блоков `elif` может быть произвольное количество, в том числе и ни одного.\n", "```\n", "\n", "Если вычисление выражения после ключевого слова `if` или `elif` даёт `True`, то соответствующий блок инструкций выполняется, если `False`, то этот блок инструкций игнорируется и выполняется блок инструкций `else`, если таковой указан. Если же вычисление этого выражение даёт небулевое значение, то `python` пытается его к таковому привести. Так, например, нулевые числа любого типа приводятся к `False`, а все остальные к `True`, что позволяет проверять на нулевое значение следующим образом. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Результат генерации: 0\n", "Решка\n", "Решка\n" ] } ], "source": [ "from random import randint\n", "\n", "\n", "random_value = randint(0, 1) # 0 или 1\n", "print(f\"Результат генерации: {random_value}\")\n", "\n", "if random_value == 1:\n", " print(\"Орёл\")\n", "else:\n", " print(\"Решка\")\n", "\n", "# или так\n", "\n", "if random_value:\n", " print(\"Орёл\")\n", "else:\n", " print(\"Решка\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "При этом в `python` второй вариант считается предпочтительнее. \n", "\n", "Пустые коллекции приводятся к `False`, а все непустые к `True`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Список пуст\n" ] } ], "source": [ "l = []\n", "if l:\n", " print(\"Список не пуст\")\n", "else:\n", " print(\"Список пуст\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Составные условия можно записывать с выражениями, включающими логические операции `and` и `or`. \n", "- В выражении `x and y` сначала вычисляется значение `x` и только если оно истинно (`True`), то вычисляется `y`;\n", "- В выражении `x or y` сначала вычисляется значение `x` и только если оно ложно (`False`), то вычисляется `y`;\n", "\n", "Это позволяет записать выражения вида:\n", "```python\n", "if x != 0 and y / x > 0:\n", " print(\"do something\")\n", "```\n", "или\n", "```python\n", "if x == 0 or y / x > 0:\n", " print(\"do something\")\n", "```\n", "\n", "```{note}\n", "Скобки не обязательны, т.к. приоритет `and` и `or` ниже, чем у `>`, `!=` и `==`.\n", "```\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Цикл `while`\n", "\n", "Пример цикла `while` в `C/C++`.\n", "\n", "```c++\n", "int i = 1;\n", "while (i < 1000){\n", " std::cout << i;\n", " i *= 2;\n", "}\n", "```\n", "\n", "В `python` тот же самый эффект достигается следующим кодом.\n", "```python\n", "i = 1\n", "while i < 1000:\n", " print(i)\n", " i *= 2\n", "```\n", "\n", "Цикл `while` тоже является составным оператором из заголовка вида `while условие:` и тела цикла. На каждой итерации цикла условие проверяется заново, и если оно истинно, то выполняются инструкции в теле оператора, которое обозначается постоянным отступом вправо. \n", "```python\n", "while условие:\n", " первая инструкция тела цикла\n", " ...\n", " последняя инструкция тела цикла\n", "```\n", "\n", "```{note}\n", "В `python` нет аналогам операторам инкремента `++` и декремента `--`, зато все остальные операторы `+=`, `*=`, `/=` дают схожий эффект.\n", "```\n", "\n", "Ключевые слова `break` и `continue` работают точно также, как и в `C/C++`:\n", "- `break` немедленно покидает цикл;\n", "- `continue` прекращает исполнение тела цикла на данной итерации и сразу заходит на следующую." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1, 2, 3, skip, 5, 6, 7, end\n" ] } ], "source": [ "i = 0\n", "while True:\n", " i += 1\n", " \n", " if i == 4:\n", " print(\"skip\", end=\", \")\n", " continue\n", " elif i == 8:\n", " print(\"end\")\n", " break\n", "\n", " print(i, end=\", \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Цикл `for`\n", "\n", "Цикл `for` в `python` совсем не похож на обычный цикл `for` в `C/C++`, но имеет много сходства с добавленным в `C++11` [range-based for](https://en.cppreference.com/w/cpp/language/range-for). \n", "\n", "Убедиться в этом можно сравнив следующее. \n", "\n", "`C/C++` код доступный по ссылке выше:\n", "```c++\n", "std::vector v = {0, 1, 2, 3, 4, 5};\n", "for (auto i : v) // compiler uses type inference to determine the right type\n", " std::cout << i << ' ';\n", "```\n", "и аналогичный код на python:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 2 3 4 5 " ] } ], "source": [ "v = [0, 1, 2, 3, 4, 5]\n", "for i in v:\n", " print(i, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Цикл `for` больше похож на цикл `foreach`, т.к. для его работы необходим итерируемый объект, по элементам которого пробегается цикл. В примере выше в качестве такого объекта выступает список `v`. \n", "\n", "Цикл `for` тоже является составным оператором, который имеет приблизительно следующий вид.\n", "```python\n", "for переменная in итерируемый_объект:\n", " первая инструкция тела цикла\n", " ...\n", " последняя инструкция тела цикла\n", "```\n", "Цикл `for` пробегается по элементам итерируемого объекта, указанного после ключевого слова `in` в заголовке цикла и перед двоеточием. При этом на каждой итерации текущий элемент доступен под именем, указанным в заголовке цикла после ключевого слова `for` и перед ключевым словом `in`. \n", "\n", "## Диапазон. `range`\n", "\n", "Рассмотрим другой пример итерируемого объекта в `python`, который чаще всего используется для итерации по нему в цикле `for`.\n", "\n", "Очень распространен случай, когда необходимо выполнить какие-то действия несколько раз. Для этих и некоторых других целей в `python` есть тип `range` (*диапазоны*).\n", "\n", "### Один аргумент\n", "\n", "`range(n)` создаёт последовательность целых чисел от 0 включая до `n` не включая. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 2 3 4 " ] } ], "source": [ "for i in range(5): \n", " print(i, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Это позволяет, например, заменить все циклы из `C/C++` вида \n", "```c++\n", "for(int i=0; i\n", "Вычисление максимума в python\n", "\n", "1. Примените её к списку символов\n", " ```python\n", " print(my_max(['a', 'b', 'A', 'B']))\n", " ```\n", "2. Сравните результат её работы с встроенной функцией `max()`\n", " ```python\n", " print(max([1, 10, 50, 200]))\n", " print(max([200, 50, 10, 1]))\n", " print(max([]))\n", " ```\n", "\n", "```{note}\n", "Функция `max()` возвращает максимальный элемент списка (любого итерируемого объекта) или наибольшее значение из двух и более аргументов. \n", "Функция `min()` работает аналогично.\n", "```\n", "\n", "\n", "\n", "\n" ] } ], "metadata": { "interpreter": { "hash": "eea3ef54c04064aa17c056cc2578c78db8b44278034b77b7225a3166c34cea02" }, "kernelspec": { "display_name": "Python 3.8.10 ('venv': venv)", "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.10" } }, "nbformat": 4, "nbformat_minor": 2 }