<jupyter_start><jupyter_text>Переменные и выражения Переменные и операция связывания, цепочки присваиванияВ дальнейших примерах используется функция ```print``` -- одна из встроенных функций Python, которая позволяет выводить что-либо на экран. Она может принимать любое количество аргументов, печатая их через пробел.Для использования переменной достаточно указать ее имя и через знак ```=``` задать определенное значение. Указывать тип переменной и объявлять переменную отдельно в Python не нужно.<jupyter_code>foo = 42
print('foo =', foo)<jupyter_output>foo = 42<jupyter_text>В Python такая операция называется операцией связывания, которая ставит в соответствие некоторому имени переменной определенное значение, т. е. связывает.После создания переменной её можно использовать в других выражениях, где будет использоваться ее значение.Для того, чтобы связать один объект с несколькими именами использую так называемые цепочки присваивания. Сначала записываются имена, чередующиеся знаками ```=```, а после нужное значение.<jupyter_code>a = b = c = 42
print('a =', a)
print('b =', b)
print('c =', c)<jupyter_output>a = 42
b = 42
c = 42<jupyter_text>Теперь каждое имя связано с одним объектом, значение которого 42.Похожим способом можно связать несколько имен с несколькими объектами.<jupyter_code>a, b, c = 1, 2, 3
print('a =', a)
print('b =', b)
print('c =', c)<jupyter_output>a = 1
b = 2
c = 3<jupyter_text>Представление переменной в памятиВ Python всё является объектом, в том числе простые типы данных. Например, при вводе в интерпретатор целого числа ```42``` будет создан объект целого числа со значением 42. Эти объекты имеют ряд "служебной" информации, такой как адрес в памяти, тип значения, само значение и т.д.. Представить такой объект в упрощенном виде можно следующим образом:Здесь ```PyObject``` общее названия для всех объектов Python.В процессе выполнения выражения ```pythonfoo = 42```Python создаст объект целого числа со значением ```42```, поместит его в память с определенным адресом или id. Затем свяжет его с именем ```foo```. Это можно представить в виде ссылки на объект.Таким образом, в Python нет "классических" переменных, которые присутствуют в других языках программирования. Вместо этого переменные это лишь имена, указывающие на объект в памяти. Это приводит к тому, что само имя не имеет типа, тип есть непосредственно у объекта.В случае выражения множественного связывания:```pythonfoo = bar = baz = 42```Python свяжет все три имени с одним объектом. На схеме это выглядит так.Аналогичное поведение будет, если связывать имена с объектами в разных местах программы:<jupyter_code>foo = 42
bar = foo
baz = bar<jupyter_output><empty_output><jupyter_text>Операторы, приоритеты и операции над простыми типамиВсе доступные операторы можно разделить на группы:- Арифметические (```+```, ```-```, ```*```, ```/```, ```//```, ```%```, ```**```)- Битовые (```&```, ```|```, ```^```, ```~```, ```>```)- [Сравнения](https://docs.python.org/3/library/stdtypes.htmlcomparisons) (```>```, ```=```, ```<=```, ```==```, ```!=```)- In-place операторы (```+=```, ```-=```, ```|=``` и другие)- Операторы членства (```in```, ```not in```)- Операторы тождественности (```is```, ```is not```)- [Логические](https://docs.python.org/3/library/stdtypes.htmlboolean-operations-and-or-not) (```and```, ```or```, ```not```) Арифметические операции над числами<jupyter_code># Операции с числами
a = 42
b = 5
c = 2.5
d = 1 + 3j
print('Сумма:', a, '+', b, '=', a + b)
print('Остаток:', a, '%', b, '=', a % b)
print('Целая часть от деления:', a, '//', b, '=', a // b)
print('Возведение в степень:', 100, '**', a, '=', 100 ** a)
print('Деление:', d, '/', d, '=', d / d)<jupyter_output>Сумма: 42 + 5 = 47
Остаток: 42 % 5 = 2
Целая часть от деления: 42 // 5 = 8
Возведение в степень: 100 ** 42 = 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Деление: (1+3j) / (1+3j) = (1+0j)<jupyter_text>У целых чисел есть одна особенность -- они не ограничены по памяти, ограничением выступает только оперативная память компьютера. Поэтому в Python "из коробки" доступна длинная арифметика.Результат операций с числами разных типов будет иметь тип операнда, у которого обширнее тип. Это можно представить как ```int < float < complex```, если один операнд будет целого типа, а другой с плавающей точкой, то и результат будет с плавающей точкой, т. е. ```float```. Из этого правила есть только одно исключение. Оператор деления ```/``` всегда приводит тип к ```float```, даже когда два операнда будут целого типа. Для целочисленного деления (целая часть) есть оператор ```//```.<jupyter_code># Операции с числами разных типов
a = 42
b = 7
c = 2.5
d = 1 + 3j
print('Сумма:', a, '+', c, '=', a + c)  # результат float
print('Остаток:', b, '%', c, '=', b % c)  # результат float
print('Возведение в степень:', a, '**', d, '=', a ** d)  # результат complex
print('Деление:', a, '/', b, '=', a / b)  # результат float<jupyter_output>Сумма: 42 + 2.5 = 44.5
Остаток: 7 % 2.5 = 2.0
Возведение в степень: 42 ** (1+3j) = (9.06046295499066-41.01107181044214j)
Деление: 42 / 7 = 6.0<jupyter_text>В памяти компьютера информация представлена в виде последовательности нулей и единиц. Это накладывает ограничения на представление чисел с плавающей запятой. Они также хранятся в виде ограниченного двоичного представления. Поэтому при операциях с ними могут появляться неточности.<jupyter_code>a = 0.1
b = 0.2
print(a, '+', b, '=', a + b)<jupyter_output>0.1 + 0.2 = 0.30000000000000004<jupyter_text>In-place операторыДля всех арифметических и битовых операторов существуют их in-place аналоги. Это операторы с добавлением знака ```=```. Предназначены эти операторы для изменения переменной "на месте".<jupyter_code>a = 196
a += 1  # 197
a //= 5  # 39
a -= 18  # 21
a *= 2  # 42
print('a =', a)<jupyter_output>a = 42<jupyter_text>Операции над строкамиНекоторые арифметические операторы, такие как ```+``` и ```*``` можно применять и со строками для конкатенации (объединения) и размножения строк.<jupyter_code>a = 'foo'
b = 'bar'
c = a + '_' + b
print('Результат конкатенации:', c)
print('Умножение на константу:', a, '*', 5, '=', a * 5)<jupyter_output>Результат конкатенации: foo_bar
Умножение на константу: foo * 5 = foofoofoofoofoo<jupyter_text>Умножение строки на константу удобно использовать для разделения вывода с помощью линий из разных разделителей, например, ```-``` или ```*```.<jupyter_code>print('-' * 50)<jupyter_output>--------------------------------------------------<jupyter_text>Операции с логическим типомЛогический тип данных (```bool```) в Python образован от типа целого числа (```int```). В связи с эти его константы ```True``` и ```False``` могут быть представлены целыми числами. ```True``` эквивалентна 1, а ```False``` -- 0. Эта особенность делает возможным использование этих констант в арифметических выражениях.<jupyter_code>print(True * False)
print(5 + True - 4 * False)<jupyter_output>0
6<jupyter_text>Логические операторыЛигических операторов всего три: ```or``` (или), ```and``` (и), ```not``` (не). Их работа продемонстрирована в следующем примере.<jupyter_code>print('0 and 0:', False and False)
print('0 and 1:', False and True)
print('1 and 0:', True and False)
print('1 and 1:', True and True)
print('-' * 20)
print('0 or 0:', False or False)
print('0 or 1:', False or True)
print('1 or 0:', True or False)
print('1 or 1:', True or True)
print('-' * 20)
print('not 0:', False)
print('not 1:', True)<jupyter_output>0 and 0: False
0 and 1: False
1 and 0: False
1 and 1: True
--------------------
0 or 0: False
0 or 1: True
1 or 0: True
1 or 1: True
--------------------
not 0: False
not 1: True<jupyter_text>Однако, логические операторы способны работать не только с операндами типи ```bool```, но и с другими типапми данных.<jupyter_code>print('or ->', 2 or 3)
print('and ->', 2 and 3)
print('and ->', not 3)<jupyter_output>or -> 2
and -> 3
and -> False<jupyter_text>В этом примере оператор ```or``` вернул первый операнд в качестве результата. Это происходит в связи с тем, что Python считает объект целого числа со значением 2 истинным, т. е. эквивалентным ```True```. Это сочетается с другим свойством логических операторов, а именно с ленивостью. Под ленивостью здесь понимается тот факт, что если первый операнд ```a``` в выражении ```a and b``` является истинным, то второй ```b``` проверяться не будет, т. к. результат выражения будет ```True```. Это верно для ```or```. В случае ```and``` поведение будет обратное, если первый операнд ```a``` эквивалентен ```False```, то второй операнд не проверяется, а значение всего выражения будет ```False```. В следующей таблице представлены варианты работы операторов в зависимости от их операндов.| Выражение | a  True | b  True     | Результат ||-----------|------------|----------------|-----------|| a or b    | Да         | Не вычисляется | a         || a or b    | Нет        | Не важно       | b         || a and b   | Да         | Не важно       | b         || a and b   | Нет        | Не вычисляется | a         |(Значок `````` означает эквивалентность)Обратите внимание, что в некоторых случаях, когда результат выражения известен из первого операнда, второй операнд не вычисляется. С другой стороны, когда по первому операнду не удается понять результат выражения, то будет сразу возвращен второй операнд, т. к. результат выражения теперь зависит только от него. Ленивость операторов позволяет проводить разные проверки, когда успешность одной проверки влияет на последующую. Например, в следующем примере используется деление на ноль, в Python оно запрещено и вызывает ошибку и прекращение работы программы. Но этот пример выполниться корректно, т. к. первый операнд эквивалентен ```False```, а значит второй не вычисляется.<jupyter_code>False and 1 / 0
# Еще один пример плохо читаемого выражения
2 and 0 or 4 or 5 and False  # 4<jupyter_output><empty_output><jupyter_text>СравненияОператоры сранения в Python возвращают результат логического типа.<jupyter_code>print(2 > 4)<jupyter_output>False<jupyter_text>Кроме этого они позволяют строить [цепочки сравнения](https://docs.python.org/3/reference/expressions.htmlcomparisons), выражения аналогичные двойным неравенствам в математике. Такие цепочки раскрываются с помощью оператора ```and```.<jupyter_code>print('Цепочка сравнения:', 1 <= 3 < 5)
print('Эквивалент:', 1 <= 3 and 3 < 5)<jupyter_output>Цепочка сравнения: True
Эквивалент: True<jupyter_text>Такими выражениями легко злоупотребить, например:<jupyter_code>print((6 > 7) == 0)  # True
print(6 > (7 == 0))  # True
print(6 > 7 == 0)  # чистое зло, False<jupyter_output>False
True
True<jupyter_text>Операторы членстваЭти операторы предназначены для проверки вхождения каких-либо элементов в строки или какие-либо другие коллекции (о них речь пойдет чуть позднее).<jupyter_code>print('Оператор in:', 'monty' in 'monty_python')
print('Оператор not in:', 'foo' not in 'monty_python')<jupyter_output>Оператор in: True
Оператор not in: True<jupyter_text>Стоит отметить, что оператор ```not in``` это единая конструкция и записывать ее следующим образом будет неправильно, хоть и корректно:<jupyter_code>print('Неправильно:', not 'foo' in 'monty_python')<jupyter_output>Неправильно: True<jupyter_text>Каждый оператор в Python имеет определенный приоритет, определяющий порядок выполнения внутри выражения. Высокий приоритет, означает, что оператор будет выполнен перед более низкоприоритетными операциями. Например, оператор возведения в степень имеет один из самых высоких приоритетов, соответственно будет выполняться в первую очередь. Обратите внимание, что оператор сравнения имеют одинаковый приоритет.С полной таблицей приоритетов операций можно ознакомиться в [документации](https://docs.python.org/3/reference/expressions.htmloperator-summary)  Различие операторов == и isОператор ```==```, один из операторов сравнения, сравнивает два объекта на равенство их значений.<jupyter_code>foo = 376
bar = 376
print('Оператор "==":', foo == bar)<jupyter_output>Оператор "==": True<jupyter_text>В Python существуют операторы тождественности ```is``` и ```is not```. Эти операторы проверяют объекты на тождественность, т. е. являются ли два объекта идентичными или одним и тем же объектом. Объекты в Python могут иметь одни и те же значения, но при этом не быть одним и тем же объектом. Два объекта в Python считаются одним и тем же, если они лежат в одном месте в памяти. Пример ниже можно представить на схеме в следующем виде.Здесь числа ```id``` это просто адреса в памяти, где размещены эти объекты.Еще одна из встроенных в Python функций, помимо ```print```, называется ```id```. Она принимает только один аргумент -- произвольный объект. В качестве результата она возвращает число, которое является адресом объекта в памяти. Пример ниже демонстрирует, что два объекта целого числа имеют одинаковое значение и, соответственно, оператор ```==``` возвращает ```True```. Однако, эти объекты лежат в разных ячейках памяти, что демонстрирует функция ```id```. Таким образом результат сравнения этих объектов на тождественность отрицательный.<jupyter_code>foo = 376
bar = 376
print('Оператор "==":', foo == bar)
print('Адрес "foo":', id(foo))
print('Адрес "bar":', id(bar))
print('Сравнение адресов с помощью ==:', id(foo) == id(bar))
print('Оператор "is":', foo is bar)<jupyter_output>Оператор "==": True
Адрес "foo": 104256176
Адрес "bar": 104258720
Сравнение адресов с помощью ==: False
Оператор "is": False<jupyter_text>Здесь стоит помнить, что не всегда Python создает новые объекты для тех же значений. В качестве оптимизации Python не создает несколько объектов для малых чисел в диапазоне $[-5, 256]$. Поэтому возможно следующее<jupyter_code>a = 42
b = 42
print('42 is 42:', a is b)<jupyter_output>42 is 42: True<jupyter_text>Вот некоторые интересные источники по этому вопросу:- [документация](https://docs.python.org/3/c-api/long.html) с описанием реализации целых чисел- [обсуждение](https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers) различий операторов ```is``` и ```==```Стоит также отметить, что если выполнять такой код в файле, а не в REPL, то там может работать другая оптимизация и даже большие числа будут иметь одинаковый адрес в памяти.Помимо чисел в Python существует ряд объектов, которые всегда существуют в памяти в одном экземпляре. Такими объектам являются логические константы ```True``` и ```False```, а также ```None```. Поэтому проверку на эти значения стоит осуществлять с помощью операторов ```is``` или ```is not```. Оператор ```is not``` аналогичен оператору ```not in```, его не стоит разделять, иначе можно получить не тот результат, который предполагался.<jupyter_code>foo = None
print('is:', foo is None)
print('is not:', foo is not None)
print('not ... is:', not foo is None)  # Не правильно
print('==:', foo == None)  # Не по стандарту<jupyter_output>is: True
is not: False
not ... is: False
==: True<jupyter_text>Моржовый операторВ версии интерпретатора 3.8 в Python добавили новый оператор присваивания ```:=``` - оператор моржа ([PEP 572](https://www.python.org/dev/peps/pep-0572/)). Этот оператор предназначен для использования только внутри выражений. Использование моржового оператора вместо стандартного ```=```, т. е. выражения вида ```a := 5``` вместо ```a = 5```, работать не будет. Рассмотрим довольно бесполезный пример. Пример ниже эквивалентен записи ```a = b = 6```. Скобки здесь обязательны.<jupyter_code>a = (b := 6)
print('a =', a)
print('b =', b)<jupyter_output>a = 6
b = 6<jupyter_text>Моржовый оператор не просто присваивает переменной определенное значение, но и возвращает в качестве результата то, что было присвоено. Таким образом, здесь сначала вычисляется выражение в скобках и его результат будет ```6```. Затем этот результат связывается с именем ```a```.Вот несколько ситуаций, когда оператор моржа может быть особенно полезен:- вычисление части формулы и ее переиспользование- использования внутри условий и циклов.Рассмотрим первый случай. Для примера возьмем вычисление чисел Фибоначчи по [формуле Бине](https://en.wikipedia.org/wiki/Fibonacci_number). Эта формула выглядит следующим образом:$$F_n = \frac {\phi^n - (-\phi)^{-n}}{2\phi - 1}, \phi = \frac {1+\sqrt 5}{2}$$В этой формуле значение $\phi$ используется в трех местах, вполне логично вычислить эту часть заранее.<jupyter_code># заранее вычислим золотое сечение
phi = (1 + 5**0.5) / 2

# 5-е число Фибоначчи
f_5 = (phi**5 - (-phi)**(-5)) / (2 * phi - 1)
print('F_5 =', f_5)<jupyter_output>F_5 = 5.000000000000001<jupyter_text>Здесь мы можем применить моржовый оператор для вычисления $\phi$ непосредственно внутри $F_n$. Не стоит злоупотреблять этим оператором, т. к. он может усложнить читаемость, иногда лучше вынести часть вычислений на новую строку (как в примере выше).<jupyter_code># Вычисляем phi внутри основной формулы
f_5 = ((phi := (1 + 5**0.5) / 2)**5 - (-phi)**(-5)) / (2 * phi - 1)
print('F_5 =', f_5)<jupyter_output>F_5 = 5.000000000000001<jupyter_text>Сообщения об ошибкахПри выполнении примером неизбежно могут возникнуть ситуации, когда ключевое слово будет введено некорретно или будет произведена попытка выполнения неверной операции. В этом случае интерпретатор покажет ошибку. Например, выполним попытку деления на ноль:<jupyter_code>1 / 0<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13182107516926103, 'fraction_numerical': 0.027834037382095944, 'mean_word_length': 5.113194198797312, 'pattern_counts': {'":': 10, '<': 92, '<?xml version=': 0, '>': 97, 'https://': 8, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '263237', 'license': 'MIT', 'n_tokens_mistral': 7189, 'n_tokens_neox': 8044, 'n_words': 2009, 'path': 'python_pd/02_syntax/02_variables.ipynb', 'repo_name': 'Alexey-smurf/python-bp', 'size': '17281'}