{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Операции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В данной лекции рассказывается о том, какие операции (арифметические, логические и другие) можно выполнять над переменными и константами (литералами) в Python, какие типы они должны иметь, чтобы операцию можно было выполнить, а также какие преобразования осуществляет интерпретатор, если в одной операции используются значения разных типов. Не все операции языка Python описываются в этой лекции - некоторые будут рассмотрены позднее, когда мы познакомим читателя с аспектами языка, в рамках которых они используются." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Содержание лекции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* [Определение операции](#Определение-операции)\n", "* [Арифметические операции](#Арифметические-операции)\n", "* [Битовые операции](#Битовые-операции)\n", "* [Операции сравнения](#Операции-сравнения)\n", "* [Логические операции](#Логические-операции)\n", "* [Строковые операции](#Строковые-операции)\n", " * [Получение срезов строк](#Получение-срезов-строк)\n", "* [Неявные преобразования типов](#Неявные-преобразования-типов)\n", "* [Вопросы для самоконтроля](#Вопросы-для-самоконтроля)\n", "* [Задание](#Задание)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Определение операции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Операция** - это некоторое действие над переменными и константами в языке программирования, зачастую аналогичное соответствующей математической операции. Операция принимает на вход один или несколько аргументов, называемых также **операндами**, и возвращает некоторый результат. Например, в операции `a + 1` операндами являются переменная `a` и константа `1`. Результатом, возвращаемым данной операцией, будет их сумма." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По количеству принимаемых аргументов операции в языке программирования Python делятся на две группы:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. **унарные** - имеют один аргумент, например операция \"минус\", возвращающая аргумент, взятый с противоположным знаком\n", "2. **бинарные** - имеют два аргумента, например операция сложения, возвращающая сумму двух аргументов" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для каждой операции определены типы данных, которые могут быть использованы для ее операндов. Если операция вызывается с переменной или константой неподдерживаемого типа, интерпретатор генерирует исключение `TypeError`. Ниже, когда будут подробно рассматриваться имеющиеся в Python операции, мы будем обращать ваше внимание на то, какие типы могут иметь их операнды. Как правило, эти ограничения естественны и интуитивно понятны. Рассмотрим такой пример:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unsupported operand type(s) for /: 'str' and 'str'", "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[0;32m 1\u001b[0m \u001b[0ms1\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m'hello'\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0ms2\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m'world'\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0ms1\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0ms2\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for /: 'str' and 'str'" ] } ], "source": [ "s1 = 'hello'\n", "s2 = 'world'\n", "s1 / s2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этом примере мы пытаемся выполнить операцию `/` (деление) над переменными с типами `str`. Очевидно, что деление строки на строку лишено смысла, поэтому интерпретатор отказывается выполнять такой код и генерирует исключение." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По сути после выполнения операции (например, `a + 1`), в том месте, где она была в программе, вместо нее оказывается некоторая переменная, создаваемая самим интерпретатором и содержащая результат операции. Эта внутренняя переменная, как и любая другая, имеет свой тип данных, который определяется исходя из следующего:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. какая операция была вызвана\n", "2. какие типы данных были у ее операндов" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для каждой конкретной операции, рассматриваемой ниже, мы расскажем, какой тип данных имеет ее результат. При этом помните о функции `type`, с помощью которой вы сами можете легко получить эту информацию:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "int" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 1\n", "b = 2\n", "type(a + b) # посмотрим, какой тип имеет результат сложения двух целых чисел" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Арифметические операции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Рассматриваемые в данном разделе операции могут применяться для операндов с числовыми типами данных, например `int`, `float`, `complex` и `Decimal`. Для большинства из представленных операций результат будет иметь тот же тип данных, что и тип операндов, участвующих в ней. Исключением является операция деления `/`, при выполнении которой с целочисленными операндами (тип `int`) результат будет иметь тип `float`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Синтаксис | Описание |\n", "|-----------------------------------------------|-------------------------------------------|----------------------------------|\n", "|
Сложение
|
x + y
|Складывает число x и число y |\n", "|
Вычитание
|
x - y
|Вычитает из числа x число y |\n", "|
Умножение
|
x * y
|Умножает число x на число y |\n", "|
Деление
|
x / y
|Делит число x на число y |\n", "|
Целочисленное деление
|
x // y
|Делит число x на число y, отбрасывая дробную часть |\n", "|
Получение остатка (mod)
|
x % y
|Возвращает остаток от деления числа x на число y |\n", "|
Возведение в степень
|
x ** y
|Возводит число x в степень y |\n", "|
Минус
|
-x
|Изменяет знак числа x|" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "С помощью этих операций можно записывать арифметические выражения любой сложности. Очевидно, что для таких составных выражений интерпретатору нужно решить, в каком порядке выполнять операции. Для этого в Python каждая операция (не только арифметическая, но и любая другая) имеет такое свойство, как **приоритет**. Если в одном составном выражении встречаются различные операции, то интерпретатор выполняет их в порядке уменьшения приоритета (вначале более приоритетные, затем менее). При этом, если на каком-то уровне приоритета есть несколько операций, то интерпретатор выполняет их слева направо (за редким исключением, см. пример про возведение в степень далее)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для арифметических операций приоритеты установлены следующим образом (упорядочено от большего приоритета к меньшему):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. возведение в степень `**`\n", "2. минус `-`\n", "3. умножение/деление `*`, `/`, `//`, `%`\n", "4. сложение/вычитание `+`, `-`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Попробуйте самостоятельно разобраться, в какой последовательности выполнялись операции в следующем выражении:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10.0" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 1 - 3 / 3 + 3 * 4 + -2 ** 2 / 2 \n", "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "С помощью обычных скобок `()` можно указать интерпретатору, в какой последовательности выполнять операции (так же, как это делается в математике):" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(7, 9)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 1 + 2 * 3\n", "b = (1 + 2) * 3\n", "a, b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видите, значение переменной `a` высчитывалось исходя из приоритетов операций (поэтому умножение выполнилось раньше сложения), а в выражении для переменной `b` мы явно указали, что вначале должно выполниться сложение. Как и в математике, выражения в скобках можно вкладывать друг в друга, при этом вычисляются они от самых внутренних скобок к наружним." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Скобки `()` следует использовать всегда, когда выражение является достаточно сложным, содержащим несколько различных операций - это упрощает чтение исходного кода, так как не нужно вспоминать, какой приоритет имеет каждая операция, входящая в выражение. Давайте с их помощью перепишем первый пример из этого раздела, сохранив порядок выполнения операций тем же:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 1 - (3 / 3) + (3 * 4) + ((-(2 ** 2)) / 2)\n", "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Наиболее сложный момент в этом выражении заключается в вычислении `-2 ** 2`. Поторопившись, можно подумать, что это будет 4 (минус 2 в квадрате), однако внимательно посмотрев на приоритеты операций, становится понятно, что операция `-` выполняется после возведения в степень, поэтому вначале 2 возводится в квадрат, а затем берется минус от результата. Обратите внимание, насколько понятнее стало выражение после того, как мы расставили в нем скобки." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для всех бинарных арифметических операций существуют *комбинированные инструкции присваивания*, имеющие вид \"x *op*= y\", где вместо *op* стоит конкретная операция. Комбинированные присваивания являются просто более короткой формой записи выражений вида \"x = x *op* y\":" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(11, 18)" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 10\n", "a += 1 # эквивалентно a = a + 1\n", "\n", "b = 3\n", "c = 5\n", "b *= c + 1 # эквивалентно b = b * (c + 1)\n", "\n", "a, b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В заключение отметим особенность операции возведения в степень `**`: если их несколько в одном выражении, то они выполняются не слева направо, а наоборот, справа налево:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(7, 256)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 2 + 2 + 3 # эквивалентно (2 + 2) + 3\n", "b = 2 ** 2 ** 3 # эквивалентно 2 ** (2 ** 3)\n", "a, b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Битовые операции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как известно, вся информация в компьютере хранится в [двоичном виде](https://ru.wikipedia.org/wiki/%D0%94%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F), т.е. представляет из себя набор битов, каждый из которых может принимать одно из двух значений: 0 или 1. Такой способ представления информации был выбран не случайно - электронные схемы при работе с двоичными данными должны уметь надежно отличать друг от друга лишь два уровня напряжения: низкое (для 0) и высокое (для 1). Это значительно упрощает и удешевляет производство микросхем." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По причине того, что внутри компьютера все данные имеют двоичный формат, многие языки программирования (и Python в их числе) предоставляют операции для работы непосредственно с битами. Операнды битовых операций должны иметь целочисленный тип данных (`int` или `bool`), при этом тип результата будет соответствовать типу операндов. Для всех бинарных битовых операций можно использовать комбинированные инструкции присваивания, то есть вместо \"x = x *op* y\" писать \"x *op*= y\"." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Синтаксис | Описание |\n", "|-----------------------------------------------------|-------------------------------------------------|----------------------|\n", "|
Битовое AND (И)
|
x & y
|Для каждого бита с номером n в двоичном представлении x выполняет x(n) & y(n) |\n", "|
Битовое OR (ИЛИ)
|
x | y
|Для каждого бита с номером n в двоичном представлении x выполняет x(n) | y(n) |\n", "|
Битовое XOR (исключающее ИЛИ)
|
x ^ y
|Для каждого бита с номером n в двоичном представлении x выполняет x(n) ^ y(n) |\n", "|
Сдвиг влево
|
x << y
|Свигает все биты в двоичном представлении x на y позиций влево |\n", "|
Сдвиг вправо
|
x >> y
|Свигает все биты в двоичном представлении x на y позиций вправо |\n", "|
Инвертация битов
|
~x
|Инвертирует (заменяет 0 на 1 и наоборот) все биты в двоичном представлении x |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Объясним теперь, как работают битовые операции AND, OR и XOR для отдельно взятых двух битов. Это удобно делать в виде таблицы:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
AND
| bit1 = 0 | bit1 = 1 |||||
OR
| bit1 = 0 | bit1 = 1 |||||
XOR
| bit1 = 0 | bit1 = 1 |\n", "|---------------------------------------------|||||---------------------------------------------|||||---------------------------------------------|\n", "| **bit2 = 0** | 0 | 0 ||||| **bit2 = 0** | 0 | 1 ||||| **bit2 = 0** | 0 | 1 |\n", "| **bit2 = 1** | 0 | 1 ||||| **bit2 = 1** | 1 | 1 ||||| **bit2 = 1** | 1 | 0 |\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "С помощью этой таблицы легко можно увидеть, что операция битового AND возвращает для двух битов 1 только в том случае, если оба они равны 1, а операция OR - если хотя бы один равен 1. Для операндов с типом `bool` считается, что `True` это 1, а `False` - 0." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Приоритеты битовых операций следующие (от наибольшего к наименьшему):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. инвертация битов `~`\n", "2. сдвиг влево/вправо `<<`, `>>`\n", "3. битовое AND `&`\n", "4. битовое XOR `^`\n", "5. битовое OR `|`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Рассмотрим пример, демонстрирующий работу данных операций. Заметим, что в нем мы используем функцию `format` для того, чтобы выводить значения переменных в двоичном формате. Кроме того желательно, чтобы читатель имел представление о том, как десятичные числа преобразуются в двоичные и наоборот (оба эти преобразования можно найти [здесь](https://ru.wikipedia.org/wiki/Двоичная_система_счисления))." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a = 1001\n", "b = 0011\n", "a & b = 0001\n", "a | b = 1011\n", "a ^ b = 1010\n" ] } ], "source": [ "a = 0b1001 \n", "b = 0b0011\n", "\n", "print('a = {:04b}'.format(a))\n", "print('b = {:04b}'.format(b))\n", "print('a & b = {:04b}'.format(a & b)) # здесь выполняется операция a & b, а затем ее результат передается функции format\n", "print('a | b = {:04b}'.format(a | b))\n", "print('a ^ b = {:04b}'.format(a ^ b))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Часто в программировании приходится отслеживать и в нужные моменты изменять состояние некоторого процесса. Это состояние удобно описывать набором **флагов** - специальных значений, у которых все биты, кроме одного, равны 0." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В качестве примера рассмотрим гипотетическую программу, предназначенную для отправки текстовых сообщений. Состояние каждого сообщения может быть описано произвольной комбинацией следующих флагов (обратите внимание, что для каждого флага мы используем свой бит в двоичном представлении!):" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "MSG_CREATED = 0b000001 # сообщение создано\n", "MSG_DELIVERED = 0b000010 # сообщение доставлено получателю\n", "MSG_READ = 0b000100 # сообщение прочитано получателем\n", "MSG_EDITED = 0b001000 # сообщение было отредактировано\n", "MSG_FORWARDED = 0b010000 # сообщение было отправлено еще кому-то, кроме первоначального получателя\n", "MSG_REPLIED = 0b100000 # на сообщение был отправлен ответ" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Состояние сообщения в этом случае можно хранить в одной переменной следующим образом:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "state = 100111\n" ] } ], "source": [ "state = 0\n", "\n", "# ... пользователь набрал текст сообщения\n", "state |= MSG_CREATED\n", "\n", "# ... сообщение было доставлено\n", "state |= MSG_DELIVERED\n", "\n", "# ... сообщение было прочитано\n", "state |= MSG_READ\n", "\n", "# ... на сообщение был отправлен ответ\n", "state |= MSG_REPLIED\n", "\n", "print('state = {:06b}'.format(state))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь мы очень просто можем проверять, наступили или нет для сообщения определенные события:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2, 0)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# is_delivered не равно 0 только если флаг MSG_DELIVERED установлен, т.е. сообщение доставлено\n", "# заметим, что нас не особо интересует точное значение is_delivered, а только то, равно оно нулю или нет\n", "is_delivered = state & MSG_DELIVERED\n", "\n", "# is_edited не равно 0 только если флаг MSG_EDITED установлен, т.е. сообщение было отредактировано\n", "is_edited = state & MSG_EDITED\n", "\n", "is_delivered, is_edited" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Битовые операции сдвигов используются нечасто в программах на языках высокого уровня, тем не менее рассмотрим небольшой пример, поясняющий их работу:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a << 1 = 1010\n", "b >> 2 = 0010\n" ] } ], "source": [ "a = 0b0101\n", "b = 0b1001\n", "\n", "print('a << 1 = {:04b}'.format(a << 1))\n", "print('b >> 2 = {:04b}'.format(b >> 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Операция `a << 1` сдвинула все биты в двоичном представлении переменной `a` на одну позицию влево, при этом дописав справа один нулевой бит. Операция `b >> 2` сдвинула все биты переменной `b` вправо на две позиции, при этом младшие два бита `b` были отброшены, а слева дописаны два нулевых бита." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если вы внимательно ознакомились с преобразованием чисел в двоичную систему счисления, то для вас будет очевидным тот факт, что операция сдвига переменной влево на $n$ позиций эквивалентна умножению ее же на $2^n$:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(40, 40)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 5\n", "b = a << 3\n", "c = a * (2 ** 3)\n", "b, c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Такой прием применяется опытными разработчиками в участках кода, где требуется максимальное быстродействие программы. Дело в том, что битовые операции наиболее быстро выполняются процессором, и выигрыш от использования сдвига влево вместо умножения может достигать десятки и сотни раз." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Операции сравнения" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Язык программирования Python предоставляет стандартный набор операций сравнения с предсказуемой семантикой. Их операторы могут иметь целочисленный тип данных (`int`, `bool`), тип данных с плавающей точкой (`float`, `complex`, `Decimal`), а также строковый тип данных `str`, для которого сравнения выполняются лексикографически (так, как принято в словарях). Результат имеет булевый тип данных `bool` и принимает значение `True`, если сравнение верно и `False` в противном случае. Все операции сравнения имеют одинаковый приоритет." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Синтаксис | Описание |\n", "|-----------------------------------------|----------------------------------------------|-------------------------------------|\n", "|
Меньше
|
x < y
|True, если x меньше y, иначе False |\n", "|
Меньше либо равно
|
x <= y
|True, если x меньше или равен y, иначе False |\n", "|
Больше
|
x > y
|True, если x больше y, иначе False |\n", "|
Больше или равно
|
x >= y
|True, если x больше или равен y, иначе False |\n", "|
Равно
|
x == y
|True, если x равен y, иначе False |\n", "|
Не равно
|
x != y
|True, если x не равен y, иначе False |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы еще раз вернемся к операциям сравнения в следующем разделе, посвященном логическим операциям, а пока ограничимся простым примером:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(True, True, False, False, True)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 0\n", "b = 1\n", "\n", "s1 = 'hello'\n", "s2 = 'hello'\n", "s3 = 'world'\n", "\n", "b1 = True # для булевых значений считается, что True > False\n", "b2 = False\n", "\n", "a < b, a >= b - 1, s1 != s2, s1 > s3, b1 > b2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Логические операции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Логические операции пришли в языки программирования из таких областей науки, как дискретная математика, математическая логика и исчисление высказываний. Всего в Python три логических операции. Каждая из них в качестве аргументов принимает переменные и константы типа данных `bool` и возвращает результат такого же типа. Операции `and` и `or` также могут использоваться и с операндами типа `int`, `float` и `complex` (в этом случае их результат имеет соответствующий операндам тип), однако необходимость в этом возникает крайне редко, и мы рассмотрим этот частный случай отдельно в конце раздела." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Синтаксис | Описание |\n", "|-------------------------------------------|--------------------------------------------|-------------------------------------|\n", "|
Логическое AND (И)
|
x and y
|True, если x и y равны True, иначе False |\n", "|
Логическое OR (ИЛИ)
|
x or y
|True, если x или y равен True, иначе False |\n", "|
Логическое NOT (НЕ)
|
not x
|True, если x равен False, иначе False |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По аналогии с битовыми операциями, значения логических также удобно представлять в виде таблицы (обратите внимание на связь между операциями `and` и `&`, а также `or` и `|` - по сути они идентичны, только одни работают с логическими значениями `True` и `False`, а другие - с битами 0 и 1):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
AND
| False | True |||||
OR
| False | True |||||
NOT
| False | True |\n", "|---------------------------------------|||||-------------------------------------|||||---------------------------------------|\n", "| **False** | False | False ||||| **False** | False | True ||||| | True | False | \n", "| **True** | False | True ||||| **True** | True | True ||||| | | |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Приоритеты логических операций следующие (от наибольшего к наименьшему):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. логическое NOT `not`\n", "2. логическое AND `and`\n", "3. логическое OR `or`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Логические операции часто применяются вместе с операциями сравнения. Рассмотрим, например, как определить, что значение переменной попадает в определенный интервал:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(True, bool)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "start = 0.0\n", "end = 100.0\n", "point = 33.8\n", "\n", "# логические операции имеют самый низкий приоритет по сравнению с остальными операциями, поэтому в следующем\n", "# выражении скобки ставить не обязательно\n", "\n", "is_internal_point = point > start and point < end\n", "is_internal_point, type(is_internal_point)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В выражении `point > start and point < end` происходит следующее:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. сравнивается, больше ли `point` чем `start` (результат `True`, потому что 33.8 > 0.0)\n", "2. сравнивается, меньше ли `point` чем `end` (результат `True`, потому что 33.8 < 100.0)\n", "3. выполняется логическая операция `and` (результат `True`, потому что оба ее операнда равны `True`)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В языке Python поддеживается более простой (и более естественный) способ проверить, что значение попадает в некоторый интервал:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "start = 0.0\n", "end = 100.0\n", "point = 33.8\n", "\n", "is_internal_point = start < point < end\n", "is_internal_point" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь давайте немного усложним пример предыдущий пример. Пусть нам теперь нужно определить, что `point` лежит либо в интервале от $(0;10)$, либо в интервале $(90;100)$:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "start1 = 0.0\n", "end1 = 10.0\n", "start2 = 90.0\n", "end2 = 100.0\n", "\n", "point = 9.1\n", "\n", "# в скобках определяем принадлежит ли point первому или второму интервалу, затем объединяем результат\n", "# операцией or, которая дает True, если хотя бы один из операндов равен True\n", "\n", "is_internal_point = (start1 < point < end1) or (start2 < point < end2)\n", "is_internal_point" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Важной особенностью логических операций является то, что они вычисляются по так называемой **короткой схеме**:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. если левый операнд операции `and` равен `False`, то правый не вычисляется (что логично, так как он уже не окажет влияние на результат операции `and`).\n", "2. если левый операнд операции `or` равен `True`, то правый не вычисляется (аналогично, он уже не окажет влияние на результат операции `or`)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Это означает, что в примере выше сравнения в правых скобках вообще не проверялись, так как уже после вычисления результата сравнений в левых скобках оказалось, что он равен `True`, а значит и результат всей операции `or` будет `True`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как мы говорили в самом начале этого раздела, логические операции `and` и `or` можно использовать и для значений типов `int`, `float` и `complex`. Действуют они таким образом:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* `and` возвращает 0, если один из операндов 0, или значение операнда справа, если оба операнда не 0\n", "* `or` возвращает 0, если оба операнда 0, значение левого операнда, если он не 0, иначе - значение правого операнда" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0, 1.0, -2, (1+2j), 0)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 0\n", "b = 1.0\n", "c = -2\n", "d = 1 + 2j\n", "\n", "a and b, a or b, b and c, d or b, a or a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим, какой тип имеет результат логических операций в этом случае:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(int, float, int, complex, int)" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(a and b), type(a or b), type(b and c), type(d or b), type(a or a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Строковые операции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Строковые операции принимают в качестве операндов строковый тип данных `str` и возвращают результат такого же типа. Для перечисленных ниже операций возможно использование комбинированных инструкций присваивания." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Синтаксис | Описание |\n", "|------------------------------------|------------------------------------------|----------------------------------------------|\n", "|
Конкатенация
|
x + y
|Возвращает строку, составленную из строки x и приписанной к ее концу строки y |\n", "|
Дублирование
|
x * y
|Возвращает строку x, продублированную y раз |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Обратите внимание, что для представленных строковых операций используются знаки `+` и `*`, которые выше применялись для операций сложения и умножения чисел. Интерпретатор может понять, какую операцию выполнить, по тому, какой тип данных имеют ее операнды. Например, для чисел `+` означает сложение, а для строк - конкатенацию. Это не единственный пример, когда один и тот же знак используется для разных операций, как будет видно в других лекциях. При этом приоритет их остается тем же, что и для операций с числами (для строк это означает, что конкатенация имеет меньший приоритет, чем дублирование)." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'result: hello, world!!!'" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s1 = 'hello, '\n", "s2 = 'world'\n", "s3 = '!'\n", "s4 = 'result: '\n", "\n", "s4 += s1 + s2 + s3 * 3\n", "s4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Получение срезов строк" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Срезом строки** (англ. *slice*) в языке Python называется некоторое подмножество ее символов. Операцией получения среза является операция `[]`, которая имеет три формы записи:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* string\\[ *start* \\] - возвращает строку, содержащую один символ, находящийся на позиции *start* в строке string (*start* еще называют **индексом** символа)\n", "* string\\[ *start* : *end* \\] - возвращает строку, содержащую все символы из интервала \\[*start*; *end*) в строке string\n", "* string\\[ *start* : *end* : *step* \\] - возвращает строку, содержащую все символы из интервала \\[*start*; *end*) в строке string, взятые с шагом *step*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Значения *start*, *end* и *step* должны быть переменными или константами с целочисленным типом данных `int`. Также обратите внимание, что во второй и третьей форме записи символ с индексом *end* **не попадает** в результат (у интервала открытая правая граница)!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Символы в строке пронумерованы следующим образом (можно обращаться к ним как с помощью верхних положительных индексов, так и с помощью нижних отрицательных):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Нумерация символов](./images/05/symbols-numeration.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Простейшее использование операции среза заключается в получении одного символа из строки по его индексу:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('A', 'A', 'E', 'E')" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = 'ABC DEF GHI'\n", "s[0], s[-11], s[5], s[-6]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вторая форма записи операции взятия среза позволяет сразу получить целую подстроку:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('DEF', 'DEF')" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s[4:7], s[-7:-4]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "При таком способе записи мы можем опустить любой из индексов: если не указать начальный индекс, то вместо него будет использован 0, если конечный, то вместо него будет использоваться индекс на 1 больше индекса последнего элемента (в нашем примере 10 + 1):" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('ABC D', 'EF GHI', 'ABC DEF GHI')" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s[:5], s[5:], s[:] # последний срез возвращает всю строку" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Наконец, третья форма записи позволяет получить строку, которая заполняется символами оргинальной строки, взятыми с определенным шагом:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'AD'" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# получаем символы от начала и до конца указанного интервала, начиная с первого и с шагом 4 (т.е. берем\n", "# первый символ, \"шагаем\" на 4 позиции вперед, берем следующий символ и так далее, пока не выйдем за\n", "# границы интервала или не достигнем его конца)\n", "s[0:7:4]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если в качестве шага используется отрицательный индекс, то интерпретатор просматривает строку в обратном направлении:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'FED'" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# получаем строку, состоящую из символов s[6], s[5] и s[4]\n", "s[6:3:-1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В третьей форме записи также можно не указывать параметры *start* и *end*. В этом случае для положительного *step* строка просматривается с первого символа до конца строки, а для отрицательного - с последнего и до начала:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('ACDFGI', 'IGFDCA')" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s[::2], s[::-2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Часто операция получения среза используется вместе с операцией конкатенации для изменения некоторого символа в строке:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'there is a mistake in the string'" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = 'there is a miscake in the string'\n", "s = s[:14] + 't' + s[15:] # 'there is a mis' + 't' + 'ake in the string'\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Неявные преобразования типов" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Несмотря на то, что язык программирования Python относится к языкам со [строгой типизацией](./04_Data_Types.ipynb#Определение-типа-данных), в нем существует несколько неявных преобразований типов данных, которые считаются безопасными и не приводящими к ошибкам в программах. Безопасными считаются такие преобразования, при которых не происходит потеря важной информации, например:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* новый тип данных позволяет хранить все значения, допустимые для старого, плюс возможно еще некоторые (говоря на языке теории множеств, все возможные значения старого типа данных являются подмножеством значений нового)\n", "* та информация, которая теряется при преобразовании, не важна в данном контексте" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Неявные преобразования типов используются интерпретатором в следующих ситуациях:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* В одном выражении могут быть смешаны константы и переменные разных типов. В этом случае интерпретатор пытается безопасно преобразовать значения к некоторому общему для всех типу.\n", "* В некоторых операциях и инструкциях языка требуется константа или переменная определенного типа. Если в таком месте в программе встречается значение другого типа, то интерпретатор Python пытается безопасно преобразовать его к нужному." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В случае, если интерпретатор Python не смог произвести преобразование типов, он генерирует исключение `TypeError`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Перечислим преобразования типов, считающиеся безопасными в языке программирование Python:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. *bool -> int -> float -> complex* - в арифметических операциях, операциях сравнения и некоторых других (при этом `True` преобразуется в 1, а `False` в 0)\n", "2. *int -> bool, float -> bool, complex -> bool, str -> bool* - в контекстах, где ожидается значение булевого типа (при этом любое ненулевое число или непустая строка преобразуется в `True`, а 0 или пустая строка - в `False`)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Работу преобразований второго типа мы увидим, когда будем проходить инструкцию ветвления и циклы в языке программирования Python. Пока приведем примеры преобразований первого типа:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(5.0, float)" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 2\n", "b = 2.5\n", "c = a * b # a преобразуется в float (int -> float)\n", "\n", "c, type(c)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "((2.3+4j), complex)" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 2\n", "b = 4.6 + 8j\n", "c = b / a # a преобразуется в complex (int -> float -> complex)\n", "\n", "c, type(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Несколько более неожиданный результат, связанный со спецификой преобразования булевого типа, демонстрирует следующий пример:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2, int, 0.0, float)" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = True\n", "b = False\n", "\n", "c = 1\n", "d = 5.0\n", "\n", "r1 = a + c # c преобразуется в int (bool -> int), True -> 1\n", "r2 = b * d # b преобразуется в float (bool -> int -> float), False -> 0\n", "\n", "r1, type(r1), r2, type(r2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В конце обратим ваше внимание на то, что в языке программирования Python отсутствует неявное преобразование из строки в число, поэтому выполнение такого кода завершается генерацией исключения `TypeError`:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unsupported operand type(s) for +: 'int' and 'str'", "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[0;32m 1\u001b[0m \u001b[0ma\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m10\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0ms\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m'20'\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0ma\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0ms\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'int' and 'str'" ] } ], "source": [ "a = 10\n", "s = '20'\n", "a + s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Несмотря на то, что для представленного выше примера неявное преобразование из строки в число скорее всего было бы тем, чего и хотел программист, зачастую оно может приводить к труднообнаруживаемым ошибкам, например, если в строке случайно окажется не цифровой символ, а буквенный." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Вопросы для самоконтроля" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Что такое операнд?\n", "2. На какие типы в зависимости от количества аргументов разделяются операции в Python?\n", "3. Перечислите все унарные операции, с которыми мы познакомились в этой лекции.\n", "4. Что такое приоритет операции? Зачем он нужен?\n", "5. В чем смысл короткой схемы вычисления логических операций? Какие именно сравнения будут выполнены интерпретатором в примере *In \\[16\\]* в случае, если `point` равно -1.0?\n", "6. Что такое срез строки?\n", "7. В каких случаях интерпретатор Python может использовать неявные преобразования типов? Когда их можно назвать безопасными?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Задание" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Напишите программу, находящую корни [квадратного уравнения](https://ru.wikipedia.org/wiki/%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B5_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5) $a*x^2+b*x+c=0$ для некоторых $a$, $b$ и $c$ (подсказка: $\\sqrt{x}=x^\\frac{1}{2}$).\n", "2. Используя [пример](#flags_example) про состояние сообщения, напишите программу, которая определяет, что сообщение или доставлено, или отредактировано, или и то и другое. Программа должна выводить True, если сообщение удовлетворяет перечисленным условиям, и False в противном случае.\n", "3. Напишите программу, которая выводит True, если некоторое слово одинаково выглядит как при написании слева направо, так и справа налево (например, \"топот\"), и False в противном случае." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- - -\n", "[Предыдущая: Типы данных](04_Data_Types.ipynb) |\n", "[Содержание](00_Overview.ipynb#Содержание) |\n", "[Следующая: Инструкция ветвления и циклы](06_Branch_Instruction_And_Loops.ipynb)" ] } ], "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.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }