#!/usr/bin/env python
# coding: utf-8
# # Операции
# В данной лекции рассказывается о том, какие операции (арифметические, логические и другие) можно выполнять над переменными и константами (литералами) в Python, какие типы они должны иметь, чтобы операцию можно было выполнить, а также какие преобразования осуществляет интерпретатор, если в одной операции используются значения разных типов. Не все операции языка Python описываются в этой лекции - некоторые будут рассмотрены позднее, когда мы познакомим читателя с аспектами языка, в рамках которых они используются.
# ## Содержание лекции
# * [Определение операции](#Определение-операции)
# * [Арифметические операции](#Арифметические-операции)
# * [Битовые операции](#Битовые-операции)
# * [Операции сравнения](#Операции-сравнения)
# * [Логические операции](#Логические-операции)
# * [Строковые операции](#Строковые-операции)
# * [Получение срезов строк](#Получение-срезов-строк)
# * [Неявные преобразования типов](#Неявные-преобразования-типов)
# * [Вопросы для самоконтроля](#Вопросы-для-самоконтроля)
# * [Задание](#Задание)
# ## Определение операции
# **Операция** - это некоторое действие над переменными и константами в языке программирования, зачастую аналогичное соответствующей математической операции. Операция принимает на вход один или несколько аргументов, называемых также **операндами**, и возвращает некоторый результат. Например, в операции `a + 1` операндами являются переменная `a` и константа `1`. Результатом, возвращаемым данной операцией, будет их сумма.
# По количеству принимаемых аргументов операции в языке программирования Python делятся на две группы:
# 1. **унарные** - имеют один аргумент, например операция "минус", возвращающая аргумент, взятый с противоположным знаком
# 2. **бинарные** - имеют два аргумента, например операция сложения, возвращающая сумму двух аргументов
# Для каждой операции определены типы данных, которые могут быть использованы для ее операндов. Если операция вызывается с переменной или константой неподдерживаемого типа, интерпретатор генерирует исключение `TypeError`. Ниже, когда будут подробно рассматриваться имеющиеся в Python операции, мы будем обращать ваше внимание на то, какие типы могут иметь их операнды. Как правило, эти ограничения естественны и интуитивно понятны. Рассмотрим такой пример:
# In[1]:
s1 = 'hello'
s2 = 'world'
s1 / s2
# В этом примере мы пытаемся выполнить операцию `/` (деление) над переменными с типами `str`. Очевидно, что деление строки на строку лишено смысла, поэтому интерпретатор отказывается выполнять такой код и генерирует исключение.
# По сути после выполнения операции (например, `a + 1`), в том месте, где она была в программе, вместо нее оказывается некоторая переменная, создаваемая самим интерпретатором и содержащая результат операции. Эта внутренняя переменная, как и любая другая, имеет свой тип данных, который определяется исходя из следующего:
# 1. какая операция была вызвана
# 2. какие типы данных были у ее операндов
# Для каждой конкретной операции, рассматриваемой ниже, мы расскажем, какой тип данных имеет ее результат. При этом помните о функции `type`, с помощью которой вы сами можете легко получить эту информацию:
# In[2]:
a = 1
b = 2
type(a + b) # посмотрим, какой тип имеет результат сложения двух целых чисел
# ## Арифметические операции
# Рассматриваемые в данном разделе операции могут применяться для операндов с числовыми типами данных, например `int`, `float`, `complex` и `Decimal`. Для большинства из представленных операций результат будет иметь тот же тип данных, что и тип операндов, участвующих в ней. Исключением является операция деления `/`, при выполнении которой с целочисленными операндами (тип `int`) результат будет иметь тип `float`.
# |
Название
| Синтаксис | Описание |
# |-----------------------------------------------|-------------------------------------------|----------------------------------|
# |Сложение
|x + y
|Складывает число x и число y |
# |Вычитание
|x - y
|Вычитает из числа x число y |
# |Умножение
|x * y
|Умножает число x на число y |
# |Деление
|x / y
|Делит число x на число y |
# |Целочисленное деление
|x // y
|Делит число x на число y, отбрасывая дробную часть |
# |Получение остатка (mod)
|x % y
|Возвращает остаток от деления числа x на число y |
# |Возведение в степень
|x ** y
|Возводит число x в степень y |
# |Минус
|-x
|Изменяет знак числа x|
# С помощью этих операций можно записывать арифметические выражения любой сложности. Очевидно, что для таких составных выражений интерпретатору нужно решить, в каком порядке выполнять операции. Для этого в Python каждая операция (не только арифметическая, но и любая другая) имеет такое свойство, как **приоритет**. Если в одном составном выражении встречаются различные операции, то интерпретатор выполняет их в порядке уменьшения приоритета (вначале более приоритетные, затем менее). При этом, если на каком-то уровне приоритета есть несколько операций, то интерпретатор выполняет их слева направо (за редким исключением, см. пример про возведение в степень далее).
# Для арифметических операций приоритеты установлены следующим образом (упорядочено от большего приоритета к меньшему):
# 1. возведение в степень `**`
# 2. минус `-`
# 3. умножение/деление `*`, `/`, `//`, `%`
# 4. сложение/вычитание `+`, `-`
# Попробуйте самостоятельно разобраться, в какой последовательности выполнялись операции в следующем выражении:
# In[3]:
a = 1 - 3 / 3 + 3 * 4 + -2 ** 2 / 2
a
# С помощью обычных скобок `()` можно указать интерпретатору, в какой последовательности выполнять операции (так же, как это делается в математике):
# In[4]:
a = 1 + 2 * 3
b = (1 + 2) * 3
a, b
# Как видите, значение переменной `a` высчитывалось исходя из приоритетов операций (поэтому умножение выполнилось раньше сложения), а в выражении для переменной `b` мы явно указали, что вначале должно выполниться сложение. Как и в математике, выражения в скобках можно вкладывать друг в друга, при этом вычисляются они от самых внутренних скобок к наружним.
# Скобки `()` следует использовать всегда, когда выражение является достаточно сложным, содержащим несколько различных операций - это упрощает чтение исходного кода, так как не нужно вспоминать, какой приоритет имеет каждая операция, входящая в выражение. Давайте с их помощью перепишем первый пример из этого раздела, сохранив порядок выполнения операций тем же:
# In[5]:
a = 1 - (3 / 3) + (3 * 4) + ((-(2 ** 2)) / 2)
a
# Наиболее сложный момент в этом выражении заключается в вычислении `-2 ** 2`. Поторопившись, можно подумать, что это будет 4 (минус 2 в квадрате), однако внимательно посмотрев на приоритеты операций, становится понятно, что операция `-` выполняется после возведения в степень, поэтому вначале 2 возводится в квадрат, а затем берется минус от результата. Обратите внимание, насколько понятнее стало выражение после того, как мы расставили в нем скобки.
# Для всех бинарных арифметических операций существуют *комбинированные инструкции присваивания*, имеющие вид "x *op*= y", где вместо *op* стоит конкретная операция. Комбинированные присваивания являются просто более короткой формой записи выражений вида "x = x *op* y":
# In[6]:
a = 10
a += 1 # эквивалентно a = a + 1
b = 3
c = 5
b *= c + 1 # эквивалентно b = b * (c + 1)
a, b
# В заключение отметим особенность операции возведения в степень `**`: если их несколько в одном выражении, то они выполняются не слева направо, а наоборот, справа налево:
# In[7]:
a = 2 + 2 + 3 # эквивалентно (2 + 2) + 3
b = 2 ** 2 ** 3 # эквивалентно 2 ** (2 ** 3)
a, b
# ## Битовые операции
# Как известно, вся информация в компьютере хранится в [двоичном виде](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). Это значительно упрощает и удешевляет производство микросхем.
# По причине того, что внутри компьютера все данные имеют двоичный формат, многие языки программирования (и Python в их числе) предоставляют операции для работы непосредственно с битами. Операнды битовых операций должны иметь целочисленный тип данных (`int` или `bool`), при этом тип результата будет соответствовать типу операндов. Для всех бинарных битовых операций можно использовать комбинированные инструкции присваивания, то есть вместо "x = x *op* y" писать "x *op*= y".
# |Название
| Синтаксис | Описание |
# |-----------------------------------------------------|-------------------------------------------------|----------------------|
# |Битовое AND (И)
|x & y
|Для каждого бита с номером n в двоичном представлении x выполняет x(n) & y(n) |
# |Битовое OR (ИЛИ)
|x | y
|Для каждого бита с номером n в двоичном представлении x выполняет x(n) | y(n) |
# |Битовое XOR (исключающее ИЛИ)
|x ^ y
|Для каждого бита с номером n в двоичном представлении x выполняет x(n) ^ y(n) |
# |Сдвиг влево
|x << y
|Свигает все биты в двоичном представлении x на y позиций влево |
# |Сдвиг вправо
|x >> y
|Свигает все биты в двоичном представлении x на y позиций вправо |
# |Инвертация битов
|~x
|Инвертирует (заменяет 0 на 1 и наоборот) все биты в двоичном представлении x |
# Объясним теперь, как работают битовые операции AND, OR и XOR для отдельно взятых двух битов. Это удобно делать в виде таблицы:
# |AND
| bit1 = 0 | bit1 = 1 |||||OR
| bit1 = 0 | bit1 = 1 |||||XOR
| bit1 = 0 | bit1 = 1 |
# |---------------------------------------------|||||---------------------------------------------|||||---------------------------------------------|
# | **bit2 = 0** | 0 | 0 ||||| **bit2 = 0** | 0 | 1 ||||| **bit2 = 0** | 0 | 1 |
# | **bit2 = 1** | 0 | 1 ||||| **bit2 = 1** | 1 | 1 ||||| **bit2 = 1** | 1 | 0 |
#
# С помощью этой таблицы легко можно увидеть, что операция битового AND возвращает для двух битов 1 только в том случае, если оба они равны 1, а операция OR - если хотя бы один равен 1. Для операндов с типом `bool` считается, что `True` это 1, а `False` - 0.
# Приоритеты битовых операций следующие (от наибольшего к наименьшему):
# 1. инвертация битов `~`
# 2. сдвиг влево/вправо `<<`, `>>`
# 3. битовое AND `&`
# 4. битовое XOR `^`
# 5. битовое OR `|`
# Рассмотрим пример, демонстрирующий работу данных операций. Заметим, что в нем мы используем функцию `format` для того, чтобы выводить значения переменных в двоичном формате. Кроме того желательно, чтобы читатель имел представление о том, как десятичные числа преобразуются в двоичные и наоборот (оба эти преобразования можно найти [здесь](https://ru.wikipedia.org/wiki/Двоичная_система_счисления)).
# In[8]:
a = 0b1001
b = 0b0011
print('a = {:04b}'.format(a))
print('b = {:04b}'.format(b))
print('a & b = {:04b}'.format(a & b)) # здесь выполняется операция a & b, а затем ее результат передается функции format
print('a | b = {:04b}'.format(a | b))
print('a ^ b = {:04b}'.format(a ^ b))
# Часто в программировании приходится отслеживать и в нужные моменты изменять состояние некоторого процесса. Это состояние удобно описывать набором **флагов** - специальных значений, у которых все биты, кроме одного, равны 0.
# В качестве примера рассмотрим гипотетическую программу, предназначенную для отправки текстовых сообщений. Состояние каждого сообщения может быть описано произвольной комбинацией следующих флагов (обратите внимание, что для каждого флага мы используем свой бит в двоичном представлении!):
# In[9]:
MSG_CREATED = 0b000001 # сообщение создано
MSG_DELIVERED = 0b000010 # сообщение доставлено получателю
MSG_READ = 0b000100 # сообщение прочитано получателем
MSG_EDITED = 0b001000 # сообщение было отредактировано
MSG_FORWARDED = 0b010000 # сообщение было отправлено еще кому-то, кроме первоначального получателя
MSG_REPLIED = 0b100000 # на сообщение был отправлен ответ
# Состояние сообщения в этом случае можно хранить в одной переменной следующим образом:
# In[10]:
state = 0
# ... пользователь набрал текст сообщения
state |= MSG_CREATED
# ... сообщение было доставлено
state |= MSG_DELIVERED
# ... сообщение было прочитано
state |= MSG_READ
# ... на сообщение был отправлен ответ
state |= MSG_REPLIED
print('state = {:06b}'.format(state))
# Теперь мы очень просто можем проверять, наступили или нет для сообщения определенные события:
# In[11]:
# is_delivered не равно 0 только если флаг MSG_DELIVERED установлен, т.е. сообщение доставлено
# заметим, что нас не особо интересует точное значение is_delivered, а только то, равно оно нулю или нет
is_delivered = state & MSG_DELIVERED
# is_edited не равно 0 только если флаг MSG_EDITED установлен, т.е. сообщение было отредактировано
is_edited = state & MSG_EDITED
is_delivered, is_edited
# Битовые операции сдвигов используются нечасто в программах на языках высокого уровня, тем не менее рассмотрим небольшой пример, поясняющий их работу:
# In[12]:
a = 0b0101
b = 0b1001
print('a << 1 = {:04b}'.format(a << 1))
print('b >> 2 = {:04b}'.format(b >> 2))
# Операция `a << 1` сдвинула все биты в двоичном представлении переменной `a` на одну позицию влево, при этом дописав справа один нулевой бит. Операция `b >> 2` сдвинула все биты переменной `b` вправо на две позиции, при этом младшие два бита `b` были отброшены, а слева дописаны два нулевых бита.
# Если вы внимательно ознакомились с преобразованием чисел в двоичную систему счисления, то для вас будет очевидным тот факт, что операция сдвига переменной влево на $n$ позиций эквивалентна умножению ее же на $2^n$:
# In[13]:
a = 5
b = a << 3
c = a * (2 ** 3)
b, c
# Такой прием применяется опытными разработчиками в участках кода, где требуется максимальное быстродействие программы. Дело в том, что битовые операции наиболее быстро выполняются процессором, и выигрыш от использования сдвига влево вместо умножения может достигать десятки и сотни раз.
# ## Операции сравнения
# Язык программирования Python предоставляет стандартный набор операций сравнения с предсказуемой семантикой. Их операторы могут иметь целочисленный тип данных (`int`, `bool`), тип данных с плавающей точкой (`float`, `complex`, `Decimal`), а также строковый тип данных `str`, для которого сравнения выполняются лексикографически (так, как принято в словарях). Результат имеет булевый тип данных `bool` и принимает значение `True`, если сравнение верно и `False` в противном случае. Все операции сравнения имеют одинаковый приоритет.
# |Название
| Синтаксис | Описание |
# |-----------------------------------------|----------------------------------------------|-------------------------------------|
# |Меньше
|x < y
|True, если x меньше y, иначе False |
# |Меньше либо равно
|x <= y
|True, если x меньше или равен y, иначе False |
# |Больше
|x > y
|True, если x больше y, иначе False |
# |Больше или равно
|x >= y
|True, если x больше или равен y, иначе False |
# |Равно
|x == y
|True, если x равен y, иначе False |
# |Не равно
|x != y
|True, если x не равен y, иначе False |
# Мы еще раз вернемся к операциям сравнения в следующем разделе, посвященном логическим операциям, а пока ограничимся простым примером:
# In[14]:
a = 0
b = 1
s1 = 'hello'
s2 = 'hello'
s3 = 'world'
b1 = True # для булевых значений считается, что True > False
b2 = False
a < b, a >= b - 1, s1 != s2, s1 > s3, b1 > b2
# ## Логические операции
# Логические операции пришли в языки программирования из таких областей науки, как дискретная математика, математическая логика и исчисление высказываний. Всего в Python три логических операции. Каждая из них в качестве аргументов принимает переменные и константы типа данных `bool` и возвращает результат такого же типа. Операции `and` и `or` также могут использоваться и с операндами типа `int`, `float` и `complex` (в этом случае их результат имеет соответствующий операндам тип), однако необходимость в этом возникает крайне редко, и мы рассмотрим этот частный случай отдельно в конце раздела.
# |Название
| Синтаксис | Описание |
# |-------------------------------------------|--------------------------------------------|-------------------------------------|
# |Логическое AND (И)
|x and y
|True, если x и y равны True, иначе False |
# |Логическое OR (ИЛИ)
|x or y
|True, если x или y равен True, иначе False |
# |Логическое NOT (НЕ)
|not x
|True, если x равен False, иначе False |
# По аналогии с битовыми операциями, значения логических также удобно представлять в виде таблицы (обратите внимание на связь между операциями `and` и `&`, а также `or` и `|` - по сути они идентичны, только одни работают с логическими значениями `True` и `False`, а другие - с битами 0 и 1):
# |AND
| False | True |||||OR
| False | True |||||NOT
| False | True |
# |---------------------------------------|||||-------------------------------------|||||---------------------------------------|
# | **False** | False | False ||||| **False** | False | True ||||| | True | False |
# | **True** | False | True ||||| **True** | True | True ||||| | | |
# Приоритеты логических операций следующие (от наибольшего к наименьшему):
# 1. логическое NOT `not`
# 2. логическое AND `and`
# 3. логическое OR `or`
# Логические операции часто применяются вместе с операциями сравнения. Рассмотрим, например, как определить, что значение переменной попадает в определенный интервал:
# In[15]:
start = 0.0
end = 100.0
point = 33.8
# логические операции имеют самый низкий приоритет по сравнению с остальными операциями, поэтому в следующем
# выражении скобки ставить не обязательно
is_internal_point = point > start and point < end
is_internal_point, type(is_internal_point)
# В выражении `point > start and point < end` происходит следующее:
# 1. сравнивается, больше ли `point` чем `start` (результат `True`, потому что 33.8 > 0.0)
# 2. сравнивается, меньше ли `point` чем `end` (результат `True`, потому что 33.8 < 100.0)
# 3. выполняется логическая операция `and` (результат `True`, потому что оба ее операнда равны `True`)
# В языке Python поддеживается более простой (и более естественный) способ проверить, что значение попадает в некоторый интервал:
# In[16]:
start = 0.0
end = 100.0
point = 33.8
is_internal_point = start < point < end
is_internal_point
# Теперь давайте немного усложним пример предыдущий пример. Пусть нам теперь нужно определить, что `point` лежит либо в интервале от $(0;10)$, либо в интервале $(90;100)$:
# In[17]:
start1 = 0.0
end1 = 10.0
start2 = 90.0
end2 = 100.0
point = 9.1
# в скобках определяем принадлежит ли point первому или второму интервалу, затем объединяем результат
# операцией or, которая дает True, если хотя бы один из операндов равен True
is_internal_point = (start1 < point < end1) or (start2 < point < end2)
is_internal_point
# Важной особенностью логических операций является то, что они вычисляются по так называемой **короткой схеме**:
# 1. если левый операнд операции `and` равен `False`, то правый не вычисляется (что логично, так как он уже не окажет влияние на результат операции `and`).
# 2. если левый операнд операции `or` равен `True`, то правый не вычисляется (аналогично, он уже не окажет влияние на результат операции `or`)
# Это означает, что в примере выше сравнения в правых скобках вообще не проверялись, так как уже после вычисления результата сравнений в левых скобках оказалось, что он равен `True`, а значит и результат всей операции `or` будет `True`.
# Как мы говорили в самом начале этого раздела, логические операции `and` и `or` можно использовать и для значений типов `int`, `float` и `complex`. Действуют они таким образом:
# * `and` возвращает 0, если один из операндов 0, или значение операнда справа, если оба операнда не 0
# * `or` возвращает 0, если оба операнда 0, значение левого операнда, если он не 0, иначе - значение правого операнда
# In[18]:
a = 0
b = 1.0
c = -2
d = 1 + 2j
a and b, a or b, b and c, d or b, a or a
# Посмотрим, какой тип имеет результат логических операций в этом случае:
# In[19]:
type(a and b), type(a or b), type(b and c), type(d or b), type(a or a)
# ## Строковые операции
# Строковые операции принимают в качестве операндов строковый тип данных `str` и возвращают результат такого же типа. Для перечисленных ниже операций возможно использование комбинированных инструкций присваивания.
# |Название
| Синтаксис | Описание |
# |------------------------------------|------------------------------------------|----------------------------------------------|
# |Конкатенация
|x + y
|Возвращает строку, составленную из строки x и приписанной к ее концу строки y |
# |Дублирование
|x * y
|Возвращает строку x, продублированную y раз |
# Обратите внимание, что для представленных строковых операций используются знаки `+` и `*`, которые выше применялись для операций сложения и умножения чисел. Интерпретатор может понять, какую операцию выполнить, по тому, какой тип данных имеют ее операнды. Например, для чисел `+` означает сложение, а для строк - конкатенацию. Это не единственный пример, когда один и тот же знак используется для разных операций, как будет видно в других лекциях. При этом приоритет их остается тем же, что и для операций с числами (для строк это означает, что конкатенация имеет меньший приоритет, чем дублирование).
# In[20]:
s1 = 'hello, '
s2 = 'world'
s3 = '!'
s4 = 'result: '
s4 += s1 + s2 + s3 * 3
s4
# ### Получение срезов строк
# **Срезом строки** (англ. *slice*) в языке Python называется некоторое подмножество ее символов. Операцией получения среза является операция `[]`, которая имеет три формы записи:
# * string\[ *start* \] - возвращает строку, содержащую один символ, находящийся на позиции *start* в строке string (*start* еще называют **индексом** символа)
# * string\[ *start* : *end* \] - возвращает строку, содержащую все символы из интервала \[*start*; *end*) в строке string
# * string\[ *start* : *end* : *step* \] - возвращает строку, содержащую все символы из интервала \[*start*; *end*) в строке string, взятые с шагом *step*
# Значения *start*, *end* и *step* должны быть переменными или константами с целочисленным типом данных `int`. Также обратите внимание, что во второй и третьей форме записи символ с индексом *end* **не попадает** в результат (у интервала открытая правая граница)!
# Символы в строке пронумерованы следующим образом (можно обращаться к ним как с помощью верхних положительных индексов, так и с помощью нижних отрицательных):
# ![Нумерация символов](./images/05/symbols-numeration.png)
# Простейшее использование операции среза заключается в получении одного символа из строки по его индексу:
# In[21]:
s = 'ABC DEF GHI'
s[0], s[-11], s[5], s[-6]
# Вторая форма записи операции взятия среза позволяет сразу получить целую подстроку:
# In[22]:
s[4:7], s[-7:-4]
# При таком способе записи мы можем опустить любой из индексов: если не указать начальный индекс, то вместо него будет использован 0, если конечный, то вместо него будет использоваться индекс на 1 больше индекса последнего элемента (в нашем примере 10 + 1):
# In[23]:
s[:5], s[5:], s[:] # последний срез возвращает всю строку
# Наконец, третья форма записи позволяет получить строку, которая заполняется символами оргинальной строки, взятыми с определенным шагом:
# In[24]:
# получаем символы от начала и до конца указанного интервала, начиная с первого и с шагом 4 (т.е. берем
# первый символ, "шагаем" на 4 позиции вперед, берем следующий символ и так далее, пока не выйдем за
# границы интервала или не достигнем его конца)
s[0:7:4]
# Если в качестве шага используется отрицательный индекс, то интерпретатор просматривает строку в обратном направлении:
# In[25]:
# получаем строку, состоящую из символов s[6], s[5] и s[4]
s[6:3:-1]
# В третьей форме записи также можно не указывать параметры *start* и *end*. В этом случае для положительного *step* строка просматривается с первого символа до конца строки, а для отрицательного - с последнего и до начала:
# In[26]:
s[::2], s[::-2]
# Часто операция получения среза используется вместе с операцией конкатенации для изменения некоторого символа в строке:
# In[27]:
s = 'there is a miscake in the string'
s = s[:14] + 't' + s[15:] # 'there is a mis' + 't' + 'ake in the string'
s
# ## Неявные преобразования типов
# Несмотря на то, что язык программирования Python относится к языкам со [строгой типизацией](./04_Data_Types.ipynb#Определение-типа-данных), в нем существует несколько неявных преобразований типов данных, которые считаются безопасными и не приводящими к ошибкам в программах. Безопасными считаются такие преобразования, при которых не происходит потеря важной информации, например:
# * новый тип данных позволяет хранить все значения, допустимые для старого, плюс возможно еще некоторые (говоря на языке теории множеств, все возможные значения старого типа данных являются подмножеством значений нового)
# * та информация, которая теряется при преобразовании, не важна в данном контексте
# Неявные преобразования типов используются интерпретатором в следующих ситуациях:
# * В одном выражении могут быть смешаны константы и переменные разных типов. В этом случае интерпретатор пытается безопасно преобразовать значения к некоторому общему для всех типу.
# * В некоторых операциях и инструкциях языка требуется константа или переменная определенного типа. Если в таком месте в программе встречается значение другого типа, то интерпретатор Python пытается безопасно преобразовать его к нужному.
# В случае, если интерпретатор Python не смог произвести преобразование типов, он генерирует исключение `TypeError`.
# Перечислим преобразования типов, считающиеся безопасными в языке программирование Python:
# 1. *bool -> int -> float -> complex* - в арифметических операциях, операциях сравнения и некоторых других (при этом `True` преобразуется в 1, а `False` в 0)
# 2. *int -> bool, float -> bool, complex -> bool, str -> bool* - в контекстах, где ожидается значение булевого типа (при этом любое ненулевое число или непустая строка преобразуется в `True`, а 0 или пустая строка - в `False`)
# Работу преобразований второго типа мы увидим, когда будем проходить инструкцию ветвления и циклы в языке программирования Python. Пока приведем примеры преобразований первого типа:
# In[28]:
a = 2
b = 2.5
c = a * b # a преобразуется в float (int -> float)
c, type(c)
# In[29]:
a = 2
b = 4.6 + 8j
c = b / a # a преобразуется в complex (int -> float -> complex)
c, type(c)
# Несколько более неожиданный результат, связанный со спецификой преобразования булевого типа, демонстрирует следующий пример:
# In[30]:
a = True
b = False
c = 1
d = 5.0
r1 = a + c # c преобразуется в int (bool -> int), True -> 1
r2 = b * d # b преобразуется в float (bool -> int -> float), False -> 0
r1, type(r1), r2, type(r2)
# В конце обратим ваше внимание на то, что в языке программирования Python отсутствует неявное преобразование из строки в число, поэтому выполнение такого кода завершается генерацией исключения `TypeError`:
# In[31]:
a = 10
s = '20'
a + s
# Несмотря на то, что для представленного выше примера неявное преобразование из строки в число скорее всего было бы тем, чего и хотел программист, зачастую оно может приводить к труднообнаруживаемым ошибкам, например, если в строке случайно окажется не цифровой символ, а буквенный.
# ## Вопросы для самоконтроля
# 1. Что такое операнд?
# 2. На какие типы в зависимости от количества аргументов разделяются операции в Python?
# 3. Перечислите все унарные операции, с которыми мы познакомились в этой лекции.
# 4. Что такое приоритет операции? Зачем он нужен?
# 5. В чем смысл короткой схемы вычисления логических операций? Какие именно сравнения будут выполнены интерпретатором в примере *In \[16\]* в случае, если `point` равно -1.0?
# 6. Что такое срез строки?
# 7. В каких случаях интерпретатор Python может использовать неявные преобразования типов? Когда их можно назвать безопасными?
# ## Задание
# 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}$).
# 2. Используя [пример](#flags_example) про состояние сообщения, напишите программу, которая определяет, что сообщение или доставлено, или отредактировано, или и то и другое. Программа должна выводить True, если сообщение удовлетворяет перечисленным условиям, и False в противном случае.
# 3. Напишите программу, которая выводит True, если некоторое слово одинаково выглядит как при написании слева направо, так и справа налево (например, "топот"), и False в противном случае.
# - - -
# [Предыдущая: Типы данных](04_Data_Types.ipynb) |
# [Содержание](00_Overview.ipynb#Содержание) |
# [Следующая: Инструкция ветвления и циклы](06_Branch_Instruction_And_Loops.ipynb)