Операторы Python
В этом руководстве речь пойдет об операторах языка программирования Python. Вы узнаете об арифметических, логических и битовых операторах, а также операторах сравнения, присваивания, принадлежности, тождественности и их синтаксисе. Все это будет проиллюстрировано примерами.
Оператор в Python — это символ, который выполняет операцию над одним или несколькими операндами.
Операндом выступает переменная или значение, над которыми проводится операция.
Введение в операторы Python
Операторы Python бывают 7 типов:
- Арифметические операторы
- Операторы сравнения
- Операторы присваивания
- Логические операторы
- Операторы принадлежности
- Операторы тождественности
- Битовые операторы
Арифметические операторы Python
Этот тип включает операторы для проведения базовых арифметических операций.
Сложение (+)
Складывает значение по обе стороны оператора.
Пример:
Вычитание (-)
Вычитает значение правой стороны из значения в левой.
Пример:
Умножение (*)
Перемножает значения с обеих сторон оператора.
Пример:
Деление (/)
Делит значение левой стороны на значение правой. Тип данных результата деления — число с плавающей точкой.
Пример:
Возведение в степень (**)
Возводит первое число в степень второго.
Пример:
Деление без остатка (//)
Выполняет деление и возвращает целочисленное значение частного, убирая цифры после десятичной точки.
Пример:
Деление по модулю (остаток от деления) (%)
Выполняет деление и возвращает значение остатка.
Пример:
>>> 10.5%3
Операторы сравнения
Операторы сравнения в Python проводят сравнение операндов. Они сообщают, является ли один из них больше второго, меньше, равным или и то и то.
Меньше (<)
Этот оператор проверяет, является ли значение слева меньше, чем правое.
Пример:
False
Больше (>)
Проверяет, является ли значение слева больше правого.
Пример:
True
Меньше или равно (<=)
Проверяет, является ли левая часть меньше или равной правой.
Пример:
True
Больше или равно (>=)
Проверяет, является ли левая часть больше или равной правой.
Пример:
True
Равно (==)
Этот оператор проверяет, равно ли значение слева правому. 1 равна булевому True , а 2 (двойка) — нет. 0 равен False .
Пример:
True
>>> 1==True
True
>>> 7==True
False
>>> 0==False
True
>>> 0.5==True
False
Не равно (!=)
Проверяет, не равно ли значение слева правому. Оператор <> выполняет ту же задачу, но его убрали в Python 3.
Когда условие выполнено, возвращается True . В противном случае — False . Это возвращаемое значение можно использовать в последующих инструкциях и выражениях.
Пример:
False
>>> 1==True # Это вызывает SyntaxError
Операторы присваивания
Оператор присваивания присваивает значение переменной. Он может манипулировать значением до присваивания. Есть 8 операторов присваивания: 1 простой и 7 с использованием арифметических операторов.
Присваивание (=)
Присваивает значение справа левой части. Стоит обратить внимание, что == используется для сравнения, а = — для присваивания.
Пример:
>>> a = 7 >>> print(a)
Сложение и присваивание (+=)
Суммирует значение обеих сторон и присваивает его выражению слева. a += 10 — это то же самое, что и a = a + 10 .
То же касается и все остальных операторов присваивания.
Пример:
>>> a += 2 >>> print(a)
Вычитание и присваивание (-=)
Вычитает значение справа из левого и присваивает его выражению слева.
Пример:
>>> a -= 2 >>> print(a)
Деление и присваивание (/=)
Делит значение слева на правое. Затем присваивает его выражению слева.
Пример:
>>> a /= 7 >>> print(a)
Умножение и присваивание (*=)
Перемножает значения обеих сторон. Затем присваивает правое левому.
Пример:
>>> a *= 8 >>> print(a)
Деление по модулю и присваивание (%=)
Выполняет деление по модулю для обеих частей. Результат присваивает левой части.
Пример:
>>> a %= 3 >>> print(a)
Возведение в степень и присваивание (**=)
Выполняет возведение левой части в степень значения правой части. Затем присваивает значение левой части.
Пример:
>>> a **= 5 >>> print(a)
Деление с остатком и присваивание (//=)
Выполняет деление с остатком и присваивает результат левой части.
Пример:
>>> a //= 3 >>> print(a)
Это один из важных операторов Python
Логические операторы Python
Это союзы, которые позволяют объединять по несколько условий. В Python есть всего три оператора: and (и), or (или) и not (не).
И (and)
Если условия с двух сторон оператора and истинны, тогда все выражение целиком считается истинным.
Пример:
>>> a = 7 > 7 and 2 > -1 >>> print(a)
False
Или (or)
Выражение ложно, если оба операнда с двух сторон ложные. Если хотя бы одно из них истинное, то и все выражение истинно.
Пример:
>>> a = 7 > 7 or 2 > -1 >>> print(a)
True
Не (not)
Этот оператор инвертирует булевые значения выражения. True превращается в False и наоборот. В примере внизу булево значение 0 — False . Поэтому оно превращается в True .
Пример:
>>> a = not(0) >>> print(a)
True
Операторы принадлежности
Эти операторы проверяют, является ли значение частью последовательности. Последовательность может быть списком, строкой или кортежем. Есть всего два таких оператора: in и not in .
В (in)
Проверяет, является ли значение членом последовательности. В этом примере видно, что строки fox нет в списке питомцев. Но cat — есть, поэтому она возвращает True . Также строка me является подстрокой disappointment . Поэтому она вернет True .
Пример:
>>> pets=['dog','cat', 'ferret'] >>> 'fox' in pets
False
>>> 'cat' in pets
True
>>> 'me' in 'disappointment'
True
Нет в (not in)
Этот оператор проверяет, НЕ является ли значение членом последовательности.
Пример:
>>> 'pot' not in 'disappointment'
True
Операторы тождественности
Эти операторы проверяют, являются ли операнды одинаковыми (занимают ли они одну и ту же позицию в памяти).
Это (is)
Если операнды тождественны, то вернется True . В противном случае — False . Здесь 2 не является 20 , поэтому вернется False . Но ‘2’ — это то же самое, что и «2» . Разные кавычки не меняют сами объекты, поэтому вернется True .
Пример:
>>> 2 is 20
False
>>> '2' is "2"
True
Это не (is not)
2 — это число, а ‘2’ — строка. Поэтому вернется True .
Пример:
>>> 2 is not '2'
True
Битовые операторы Python
Эти операторы работают над операндами бит за битом.
Бинарное И (&)
Проводит побитовую операцию and над двумя значением. Здесь бинарная 2 — это 10 , а 3 — 11 . Результатом побитового and является 10 — бинарная 2 . Побитовое and над 011 (3) и 100 (4) выдает результат 000 (0).
Пример:
Бинарное ИЛИ (|)
Проводит побитовую операцию or на двух значениях. Здесь or для 10 (2) и 11 (3) возвращает 11 (3).
Пример:
Бинарное ИЛИ НЕТ (^)
Проводит побитовую операцию xor (исключающее или) на двух значениях. Здесь результатом ИЛИ НЕ для 10 (2) и 11 (3) будет 01 (1).
Пример:
Инвертирующий оператор (~)
Он возвращает инвертированные двоичные числа. Другими словами, переворачивает биты. Битовая 2 — это 00000010 . Ее инвертированная версия — 11111101 . Это бинарная -3 . Поэтому результат -3 . Похожим образом ~1 равняется -2 .
Пример:
Еще раз, инвертированная -3 — это 2 .
Бинарный сдвиг влево (<<)
Он сдвигает значение левого операнда на позицию, которая указана справа. Так, бинарная 2 — это 10 . 2 Пример:
Бинарный сдвиг вправо (>>)
Сдвигает значение левого оператора на позицию, указанную справа. Так, бинарная 3 — это 11 . 3 >> 2 сдвинет значение на два положение вправо. Результат — 00 , то есть 0 . 3 >> 1 сдвинет на одну позицию вправо, а результатом будет 01 — бинарная 1 .
Пример:
>>> 3>>2 >>> 3>>1
Выводы
В этом уроке были рассмотрены все 7 типов операторов Python. Для каждого был предложен пример в IDE. Для понимания особенностей работы операторов нужно продолжать с ними работать, использовать в условных конструкциях и объединять.
Операторы сравнения Python
Что такое оператор и операнды? Это можно объяснить простым примером: 10 > 5 . В этом выражении 10 и 5 — левый и правый операнды. Знак > — оператор.
Пусть переменная a = 10 , а переменная b = 5
| Оператор | Описание | Пример | Результат |
|---|---|---|---|
| == | Проверяет, равны ли значение операндов. Если равны, то условие является истиной | a == b | False |
| != | Проверяет, равны ли значение операндов. Если НЕ равны, то условие является истиной | a != b | True |
| > | Проверяет значение левого операнда. Если оно больше, чем значение правого, то условие является истиной | a > b | True |
| Проверяет значение левого операнда. Если оно меньше, чем значение правого, то условие является истиной | a < b | False | |
| >= | Проверяет значение левого операнда. Если оно больше, либо равно значению правого, то условие является истиной | a >= b | True |
| Проверяет значение левого операнда. Если оно меньше, либо равно значению правого, то условие является истиной | a | False |
Хотите выучить Python на практике?
С нуля и до создания компьютерной игры
Собственный онлайн-тренажер с проверкой практических задач
Введение в Python
Чтобы начать играть с Python, нам нужно открыть командную строку на твоём компьютере. Ты уже должна знать, как это сделать — мы изучали это в главе Введение в интерфейс командной строки.
Когда будешь готова, следуй приведенным ниже инструкциям.
Мы хотим открыть консоль Python, так что набери python , если работаешь в Windows, или python3 для Mac OS/Linux, и нажми enter .
$ python3 Python > (. ) Type "help", "copyright", "credits" or "license" for more information. >>>
Твоя первая команда Python!
После запуска Python командная строка изменилась на >>> . Для нас это означает, что сейчас мы можем использовать только команды на языке Python. Тебе не нужно вводить >>> — Python будет делать это за нас.
Если ты хочешь выйти из консоли Python, в любой момент — просто введи exit() или используй сочетание клавиш Ctrl + Z для Windows и Ctrl + D для Mac/Linux. Тогда ты больше не будешь видеть >>> .
Пока что мы не хотим выходить из консоли Python. Мы хотим узнать больше о ней. Давай начнём с чего-нибудь совсем простого. Например, попробуй набрать простое математическое выражение, вроде 2 + 3 , и нажми enter .
>>> 2 + 3 5
Прекрасно! Видишь, как выскочил ответ? Python знает математику! Ты можешь попробовать другие команды, например:
Чтобы вычислить степень числа, например, 2 в кубе, мы вводим:
>>> 2 ** 3 8
Поиграй с этим немного и затем вернись сюда :).
Как видишь, Python является прекрасным калькулятором. Если тебе интересно, что ещё можно сделать.
Строки
Как насчет твоего имени? Введи своё имя в кавычках, вот так:
>>> "Ola" 'Ola'
Ты только что создала свою первую строку! Это последовательность символов, которые могут быть обработаны компьютером. Строка должна всегда начинаться и заканчиваться одинаковым символом. Им может быть одинарная ( ‘ ) или двойная ( » ) кавычка (разницы нет!) Кавычки говорят Python’у, что внутри них находится строка.
Строки могут быть слиты воедино. Попробуй так:
>>> "Hi there " + "Ola" 'Hi there Ola'
Ты также можешь умножать строки на число:
>>> "Ola" * 3 'OlaOlaOla'
Если тебе нужно поставить апостроф внутри строки, то есть два способа сделать это.
Используй двойные кавычки:
>>> "Runnin' down the hill" "Runnin' down the hill"
или поставь перед апострофом обратную косую черту ( \ ):
>>> 'Runnin\' down the hill' "Runnin' down the hill"
Прикольно, да? Чтобы увидеть своё имя прописными буквами, просто набери:
>>> "Ola".upper() 'OLA'
Ты только что использовала метод upper своей строки! Метод (такой как upper() ) представляет собой набор инструкций, который должен выполнить Python над заданным объектом (в нашем случае: «Ola» ) при его вызове.
Если ты хочешь узнать количество букв в своём имени, то и для этого тоже существует функция!
>>> len("Ola") 3
Интересно, почему иногда мы вызываем функцию добавлением . к концу строки (как «Ola».upper() ), а иногда сначала пишем имя функции и затем помещаем строку в скобки? Ну, в некоторых случаях функции принадлежат объектам, например, функция upper() , которая может быть применена только к строкам. В этом случае мы называем функцию методом. В другом случае функции не относятся к чему-то конкретному и могут использоваться для различных типов объектов, например, функция len() . Вот почему мы передаем «Ola» в качестве параметра функции len .
Подведём итог
Хорошо, достаточно о строках. Пока ты узнала следующее:
- командная строка — ввод команд (кода) в интерактивную командную строку Python приводит к ответам на Python;
- числа и строки — в Python числа используются для вычислений, а строки — для текстовых объектов;
- операторы, такие как + и *, объединяют значения для получения нового;
- функции, такие как upper() и len(), выполняют действия над объектами.
Таковы основы каждого языка программирования, который ты можешь выучить. Готова к чему-то посложнее? Мы уверены, что готова!
Ошибки
Давай попробуем кое-что новенькое. Можем ли мы получить длину числа так же, как длину твоего имени? Введи len(304023) и нажми Enter :
>>> len(304023) Traceback (most recent call last): File "", line 1, in TypeError: object of type 'int' has no len()
Мы получили нашу первую ошибку! Иконкой мы будем обозначать код, который при запуске сработает не так, как ожидается. Совершение ошибок (даже преднамеренных) — важная часть обучения!
Она говорит о том, что у объектов типа «int» (целые числа) нет длины. Так что же мы можем сейчас сделать? Может быть, мы можем написать наше число в виде строки? У строк ведь есть длина, верно?
>>> len(str(304023)) 6
Сработало! Мы использовали функцию str внутри функции len . str() преобразует всё в строки.
- Функция str преобразует объекты в строки
- Функция int преобразует объекты в целые числа
Важно: мы можем преобразовать число в текст, но не всегда удается преобразовать текст в числа — например, каков будет результат int(‘hello’) ?
Переменные
Переменные — важное понятие в программировании. Переменная — это всего лишь имя для чего-то, чтобы использовать его позднее. Программисты используют переменные для хранения данных, чтобы их код был более читабельным, и для того, чтобы им не пришлось запоминать, что есть что.
Допустим, мы хотим создать новую переменную с именем name :
>>> name = "Ola"
Видишь? Это очень легко! Просто введи: name равно Ola.
Как ты уже заметила, твоя программа не возвращает ничего, как было ранее. Так откуда мы знаем, что переменная действительно существует? Просто введи name и нажми Enter :
>>> name 'Ola'
Ура! Твоя первая переменная:)! Ты всегда можешь изменить то, к чему она относится:
>>> name = "Sonja" >>> name 'Sonja'
Ты также можешь использовать переменные и в функциях:
>>> len(name) 5
Круто, правда? Переменными, конечно, может быть что угодно, и цифры тоже! Попробуй следующее:
>>> a = 4 >>> b = 6 >>> a * b 24
Но что делать, если мы использовали неправильное имя? Можешь догадаться, что произойдет? Давай попробуем!
>>> city = "Tokyo" >>> ctiy Traceback (most recent call last): File "", line 1, in NameError: name 'ctiy' is not defined
Ошибка! Как ты можешь видеть, в Python есть различные типы ошибок, эта называется NameError. Python выдаст эту ошибку при попытке использовать переменную, которая пока не определена. При возникновении этой ошибки проверь свой код, чтобы узнать, не написала ли ты неправильно имя переменной.
Попрактикуйся в этом какое-то время и посмотри, что ты сможешь сделать!
Функция print
>>> name = 'Maria' >>> name 'Maria' >>> print(name) Maria
Если просто ввести name , интерпретатор Python вернет строковое представление переменной ‘name’, которым, в нашем случае, являются буквы M-a-r-i-a, окруженные одинарными кавычками, ». Когда ты вводишь print(name) , Python «печатает» содержание переменной на экран, без кавычек, что удобнее.
Как мы увидим позднее, print() пригодится, когда нам будет нужно печатать что-то изнутри функций или когда мы захотим напечатать что-то на нескольких строчках.
Списки
Помимо строк и целых чисел Python имеет богатую коллекцию других типов объектов. Сейчас мы собираемся представить тебе один из них — list (список). Списки — это именно то, о чём ты подумала: объекты, которые являются списками других объектов 🙂
Давай начнем с создания списка:
Отлично, это пустой список. Не особенно полезен, да? Давай создадим список лотерейных номеров. Мы не хотим повторять их каждый раз, так что присвоим список переменной:
>>> lottery = [3, 42, 12, 19, 30, 59]
Замечательно, у нас есть список! Что мы можем с ним сделать? Для начала посмотрим, как много лотерейных номеров в нашем списке. Есть идеи, какую функцию можно использовать для этого? Ты её уже знаешь!
>>> len(lottery) 6
Точно! len() вернет тебе количество объектов в списке. Удобно, правда? Пожалуй, мы теперь отсортируем его:
>>> lottery.sort()
Эта команда не возвращает ничего, она просто меняет порядок номеров в списке. Давайте выведем его на экран и посмотрим, что получилось:
>>> print(lottery) [3, 12, 19, 30, 42, 59]
Как видишь, номера в списке теперь отсортированы от меньшего к большему. Поздравляем!
Может, нам нужно обратить порядок? Давай сделаем это!
>>> lottery.reverse() >>> print(lottery) [59, 42, 30, 19, 12, 3]
Просто, правда? Если хочешь добавить что-то к своему списку, то можешь воспользоваться этой командой:
>>> lottery.append(199) >>> print(lottery) [59, 42, 30, 19, 12, 3, 199]
Если ты хочешь получить только первый номер в списке, то можешь воспользоваться индексами. Индекс — это номер позиции в списке, на котором находится нужное нам значение. Программисты предпочитают начать считать с 0, так что первому объекту в списке соответствует индекс 0, следующему —1, и так далее. Попробуй ввести:
>>> print(lottery[0]) 59 >>> print(lottery[1]) 42
Как видишь, ты можешь обратиться к различным объектам в своем списке, используя имя списка и индекс объекта в квадратных скобках.
Чтобы удалить что-либо из списка, тебе понадобятся индексы, с которыми мы уже разобрались выше, и команда pop() . Давай попробуем закрепить пройденное на примере: мы будем удалять первый элемент из нашего списка.
>>> print(lottery) [59, 42, 30, 19, 12, 3, 199] >>> print(lottery[0]) 59 >>> lottery.pop(0) 59 >>> print(lottery) [42, 30, 19, 12, 3, 199]
Сработало как по маслу!
В качестве дополнительной тренировки попробуй следующие индексы: 6, 7, 1000, -1, -6 и -1000. Можешь предсказать результат? Видишь логику работы?
Ты можешь найти перечень всех методов, относящихся к списку, в этой главе официальной документации Python: https://docs.python.org/3/tutorial/datastructures.html
Словари
Для проходящих руководство дома: этот раздел рассмотрен в видео Python Basics: Dictionaries.
Словари схожи со списками, но ты обращаешься к значениям словаря по ключу, а не по индексу. Ключом может быть любая строка или число. Давай создадим пустой словарь:
Это показывает, что мы создали пустой словарь. Ура!
Теперь попробуй следующую команду (можешь заменить значения на своё имя, страну и т.д.):
>>> participant = 'name': 'Ola', 'country': 'Poland', 'favorite_numbers': [7, 42, 92]>
Этой командой ты создала переменную participant с тремя парами ключ/значение:
- ключ name , указывающий на значение ‘Ola’ (объект типа строка ),
- ключ country , указывающий на значение ‘Poland’ (еще одна строка ),
- и ключ favorite_numbers , указывающий на значение [7, 42, 92] (объект типа список с тремя числами внутри).
Значение конкретного ключа можно узнать следующим образом:
>>> print(participant['name']) Ola
Видишь, синтаксис похож на работу со списком. Но вместо того, чтобы запоминать индекс, тебе нужно помнить ключ.
Что случится, если мы спросим у Python значение несуществующего в словаре ключа? Можешь предположить? Давай попробуем и узнаем наверняка!
>>> participant['age'] Traceback (most recent call last): File "", line 1, in KeyError: 'age'
Смотри, другая ошибка! Эта называется KeyError. Python услужливо напоминает, что ключа ‘age’ нет в словаре.
Когда стоит использовать словарь, а когда список? Это хороший вопрос для самостоятельного размышления. Сделай предположение перед тем, как читать дальше.
- Тебе нужна упорядоченная последовательность элементов? Список — наш выбор.
- Тебе нужны сочетания ключ/значение, чтобы быстро искать значения (по ключу) в дальнейшем? Словарь отлично подойдет.
Словари, как и списки, изменяемы, т. е. они могут быть изменены после того, как были созданы. Ты можешь добавить новые пары ключ/значение в словарь следующим образом:
>>> participant['favorite_language'] = 'Python'
Так же как в примере со списками, использование функции len() вернёт число пар ключ/значение в словаре. Попробуй сама:
>>> len(participant) 4
Надеюсь, всё вышеизложенное понятно. 🙂 Готова к новым приключениям со словарями? На следующей строке тебя ждут изумительные вещи.
Ты можешь использовать команду pop() для удаления элементов из словаря. Скажем, ты хочешь удалить элемент с ключом ‘favorite_numbers’ . Просто набери следующую команду:
>>> participant.pop('favorite_numbers') [7, 42, 92] >>> participant 'country': 'Poland', 'favorite_language': 'Python', 'name': 'Ola'>
Как видишь, пара ключ/значение с ключом ‘favorite_numbers’ была удалена.
Помимо этого, ты можешь заменить значение, связанное с уже существующим ключом. Набери:
>>> participant['country'] = 'Germany' >>> participant 'country': 'Germany', 'favorite_language': 'Python', 'name': 'Ola'>
Значение, на которое ссылается ключ ‘country’ , изменилось с ‘Poland’ на ‘Germany’ . 🙂 Захватывает? Ещё бы! Ты выучила еще одну потрясающую штуку!
Подведём итог
Шикарно! Теперь ты знаешь немало о программировании. Мы познакомились с:
- ошибками — теперь ты знаешь как читать и анализировать ошибки, которые возникают, если Python не понимает твоей команды;
- переменными — именами для объектов, которые упрощают твой код и делают его более читабельным;
- списками — последовательностями объектов в заданном порядке;
- словарями — объектами, хранящими пары ключ/значение.
Готова к продолжению? 🙂
Сравнения
Для проходящих руководство дома: этот раздел рассмотрен в видео Python Basics: Comparisons.
В программировании часто надо что-то сравнивать. Что проще всего сравнить друг с другом? Числа, конечно. Давай посмотрим, как это работает:
>>> 5 > 2 True >>> 3 < 1 False >>> 5 > 2 * 2 True >>> 1 == 1 True >>> 5 != 2 True
Мы передали Python несколько чисел для сравнения. Как ты можешь заметить, Python сравнивает не только числа, но и результаты методов (например, умножения). Неплохо, правда?
Хочешь спросить, почему мы написали двойной символ равенства == , чтобы проверить, одинаковы ли числа? Потому что одинарные символ равенства = уже задействован под присваивание значения переменным. Ты всегда, всегда должна писать два символа равенства == , если хочешь проверить, одинаковы ли объекты. Мы также можем проверить, различаются ли объекты. Для этого мы используем != , как показано в примере выше.
Дадим Python еще два задания:
>>> 6 >= 12 / 2 True >>> 3 2 False
Супер! Хочешь еще? Тогда попробуй вот это:
>>> 6 > 2 and 2 < 3 True >>> 3 > 2 and 2 < 1 False >>> 3 > 2 or 2 < 1 True
Ты можешь передать Python столько чисел, сколько захочешь, и он будет возвращать ответ! Хитро, правда?
- and — если ты используешь оператор and , оба сравнения по обе стороны от него должны быть True (верны), чтобы результат всей команды был равен True.
- or — если ты используешь оператор or , достаточно одному из сравнений по обе стороны от него быть равным True, чтобы результат всей команды также равнялся True.
Ты когда-нибудь слышала выражение «сравнивать тёплое с мягким»? Попробуем сделать что-то подобное в Python:
>>> 1 > 'django' Traceback (most recent call last): File "", line 1, in TypeError: '>' not supported between instances of 'int' and 'str'
Как мы видим, Python не знает, как сравнить число ( int ) и строку ( str ) между собой. Поэтому он просто возвращает нам ошибку TypeError и предупреждает, что объекты заданных типов не могут быть сравнены.
Логические значения
Между прочим, ты только что познакомилась с новым типом объектов в Python. Он называется Boolean (логический) — и это, наверное, самый простой тип из всех.
Существует только два логических объекта в Python:
- True (Истина),
- False (Ложь).
Чтобы Python понимал тебя, ты всегда должна писать True с заглавной буквы (остальные прописные). true, TRUE, tRUE не будут восприниматься — только True. (Та же логика применима к False, само собой.)
Ты можешь присваивать переменным логические значения! Смотри сюда:
>>> a = True >>> a True
>>> a = 2 > 5 >>> a False
Попрактикуйся с логическими значениями на примере этих выражений:
- True and True
- False and True
- True or 1 == 1
- 1 != 2
Поздравляем! Логические значения — одна из самых классных фишек программирования, и ты только что научилась ими пользоваться!
Сохраняй!
До сих пор мы писали весь код в интерактивной консоли, где Python сразу анализировал, обрабатывал и выполнял наши команды. Мы были ограничены одной строкой. Обычно, программы сохраняются в файлах и выполняются интерпретатором или компилятором нашего языка программирования. Пока мы только просили интерпретатор Python выполнять наши однострочные команды из консоли. Однако нам понадобится больше места для следующих задач, поэтому задача минимум:
- закрыть интерактивную консоль Python;
- открыть наш текстовый редактор;
- сохранить код в новом файле;
- запустить его!
Чтобы закрыть интерактивную консоль Python, просто набери функцию exit() :
>>> exit() $
Это вернёт тебя в командную строку.
В главе Текстовый редактор мы выбрали себе редактор. Нам нужно открыть его сейчас и записать следующий код в новый файл (или, если ты используешь Chromebook, создай новый файл в облачной IDE и открой файл, который будет во встроенном редакторе кода):
print('Hello, Django girls!')
Очевидно, ты уже искушенный Python разработчик, так что не стесняйся добавить что-нибудь по своему вкусу из ранее изученного.
Теперь нам нужно сохранить файл с кодом и дать ему подходящее имя. Давай назовем его python_intro.py и сохраним на рабочий стол. Мы можем назвать файл как хотим, но важно, чтобы название заканчивалось на .py. Расширение .py говорит операционной системе, что это исполняемый файл python, и Python может его запустить.
Примечание Ты должна уже была заметить одну из крутейших вещей в редакторах кода: цвета! В интерактивной консоли Python весь текст был одного цвета, сейчас же функция print должна отличаться по цвету от текста, который передается в неё в качестве аргумента. Это называется «синтаксическая подсветка», и это действительно удобная штука для программирования. Цвет подсветки может подсказать тебе о незакрытой кавычке или опечатке в ключевом слове (таком как def в определении функции, с которым мы скоро познакомимся). Это одна из причин, по которой мы используем редакторы кода 🙂
После сохранения файла пришло время запустить его! Используя навыки из раздела о командной строке, открой терминал и поменяй текущую директорию на рабочий стол.
Для Mac команда будет выглядеть так:
$ cd ~/Desktop
Присваивания переменных внутри выражений Python оператором Walrus
Любое, даже самое гениальное изобретение со временем требует совершенствования и абсорбции того наилучшего, что имеется в подобных ему продуктах человеческого разума. Не является исключением и Python, который невзирая на изначально заслуженную популярность, с каждой новой версией интенсивно развивается, совершенствуя свой синтаксис. Одним из подтверждений этому является появление в сравнительно новой Python версии 3.8 оператора выражения присваивания – (:=), который в просторечии еще называют моржовым оператором (walrus operator) за свою схожесть с глазами и бивнями моржа.
Данный оператор по функциональности практически идентичен обычному оператору присваивания “=” за исключением того, что он может присваивать значения переменных прямо внутри выражений Python, что зачастую помогает упростить код программы, сделав его более компактным и читабельным. Вместе с тем, наряду с преимуществами моржового оператора, его применение иногда имеет ряд подводных камней и не рекомендуется к реализации. Возможно, существованием именно таких неоднозначностей при применении данного оператора, в частности, было продиктовано бурное обсуждение этого новшества, по итогам которого Гвидо ван Россум (автор Python) все-таки принял его в июле 2018 году и, с тех пор объявил, что устраняется от роли главы проекта – доброжелательного диктатора (BDFL).
В ходе ознакомления с данной статьей мы безусловно рассмотрим ситуации, когда применения моржового оператора следует избегать. Но прежде, давайте познакомимся с особенностями реализации операторов присвоения в Python, а также остановимся на случаях, при которых использования оператора присвоения внутри выражений сможет существенно повысить эффективность нашего кодинга.
Особенности реализации операторов присвоения в Python
Во многих языках программирования, включая C и его производных, присваивание значений переменным может осуществляться внутри выражений. Такая функциональность может быть как очень мощным катализатором повышения эффективности кодинга, так и источником сложно выявляемых ошибок. Например, следующий код допустим на C, но выполняется отнюдь не так, как следовало бы:
int x = 3, y = 8; if (x = y) printf("х и у равны (x = %d, y = %d)", x, y); >
Здесь if (x = y) будет оцениваться как true. Следовательно, фрагмент кода будет распечатан как: х и у равны (x = 8, y = 8). Это, конечно же, далеко не тот результат, который нами ожидался. Ведь мы пытались сравнить изначальные значения x и y, но как же вдруг значение x изменилось с 3 до 8?
А, объяснятся данная ситуация тем, что мы использовали оператор присваивания (=) вместо оператора сравнения (==). Ведь в языке C — x = y является выражением, в котором значение y присваивается x, следовательно, пример x = y оценивается как 8, что считается истинным в контексте if утверждения.
Но, взгляните на аналогичный пример, написанный на Python. Этот код вызывает SyntaxError:
x, y = 3, 8 if x = y: print(f"x and y are equal (x = >, y = >)")
В отличие от примера, написанного на C, с целью недопущения двузначности в интерпретации кода, Python обеспечивает четкое разграничение между оператором обычного присваивания (=) и оператором присваивания внутри выражения (:=), что предотвращает возникновения таких трудно обнаруживаемых ошибок, какие были описаны выше на C. Необходимость такого разграничения между операторами присваивания вследствие вышеназванных причин прописана также и в Официальных рекомендациях по стилю программирования PEP 572*.
Разграничения между операторами (=) и (:=) в Python заключаются отнюдь не только лишь в визуальных их различиях. Прежде всего разграничения этих операторов состоят в том, что каждый из них должен применяться исходя из своего сугубо специфического предназначения. Другими словами, для обоих этих операторов не существует идентичных контекстов кода, в которых они оба были бы допустимы. Например, мы не можем выполнить простое присваивание с помощью моржового оператора:
>>> morzh := True File "", line 1 morzh := True ^ SyntaxError: invalid syntax
Конечно же, для моржового оператора в этом случае и, во многих других случаях с целью корректной работы программы мы можем добавить вокруг выражения присваивания круглые скобки (()) и, тем самым избежать ошибки:
# Выражение действительно, но в данном контексте не рекомендовано >>> (morzh := True) True
Но, запись традиционного оператора присваивания (=) внутри таких скобок не допускается. Хотя именно такой традиционный оператор присвоения в данном контексте был бы более всего уместен. В итоге такой подход в разграничении имеющихся в Python операторов присвоения (=) и (:=) существенно ограничивает допущение нами многочисленных потенциальны ошибок.
Далее в этой статье мы остановимся на тех вариантах использования моржового оператора, при которых его применение могло бы ощутимо упростить код наших программ, сделав их более компактными и читабельными.
Варианты использования моржового оператора
В этом разделе статьи будет рассмотрен ряд примеров, где благодаря моржовому оператору мы постараемся оптимизировать наши программные коды за счет устранения следующих возможных повторений:
- Повторных вызовов функций, замедляющих ваши программные коды.
- Повторяющихся операторов, затрудняющих сопровождение вашего кода.
- Повторяющихся вызовов итераторов, делающих ваши программные коды чрезмерно сложными.
Но прежде, чем мы остановимся на устранении излишних программных повторений, давайте рассмотрим еще такую сферу применения моржового оператора, как отладка программ.
Отладка программ
Возможно, одним из наиболее наглядных и простых вариантов использования моржового оператора является его применение при отладке сложных выражений. Рассмотрим пример такой отладки для кода, рассчитывающего расстояние между двумя точками на земной поверхности с помощью формулы гаверсинуса:
где, ϕ1 и ϕ2 широта, а λ1 и λ2 долгота соответственно 1-й и 2-й точек на земной поверхности, между которыми нам нужно найти расстояние.
Чтобы продемонстрировать эту формулу на практики, давайте попробуем рассчитать расстояние между Осло (59,9° с. ш., 10,8° в. д.) и Ванкувером (49,3° с. ш., 123,1° з. д.) следующим образом:
>>> from math import asin, cos, radians, sin, sqrt >>> # Примерный радиус Земли в километрах >>> rad = 6371 >>> # Расположение Осло и Ванкувера >>> ϕ1, λ1 = radians(59.9), radians(10.8) >>> ϕ2, λ2 = radians(49.3), radians(-123.1) >>> # Расстояние между Осло и Ванкувером >>> S = 2 * rad * asin( . sqrt( . sin((ϕ2 - ϕ1) / 2) ** 2 . + cos(ϕ1) * cos(ϕ2) * sin((λ2 - λ1) / 2) ** 2 . ) . ) >>> print(S) 7181.7841229421165
Таким образом, из вышеприведенного примера мы узнаем, что расстояние от Осло до Ванкувера составляет чуть менее 7200 километров.
Примечание. Поскольку исходный код Python обычно сохраняется в универсальной кодировке UTF-8, то в нашем коде мы свободно можем использовать такие греческие буквы, как ϕ и λ. Это, в частности, весьма удобно при переносе математических формул в необходимый нам Python код.
Однако, следует помнить, что хотя полный UTF-8 и поддерживается, например в строках, имена переменных в Python все же используют более ограниченный набор символов. В частности, при именовании наших переменных мы не можем использовать смайлики, что с точки зрения наглядности и читабельности кода весьма обосновано.
Теперь давайте предположим, что у нас как-то не заладилось с правильным ответом и, следовательно нам требуется разложить наше выражение на несколько подвыражений с тем, чтобы по отдельности проверить их правильность. Допустим в нашем конкретном примере нам нужно просто посмотреть, насколько первый из членов гаверсинуса влияет на конечный результат. Конечно же для этого можно в отдельной строке продублировать необходимое нам подвыражение, присвоив его результат соответствующей переменной и, выведя затем ее на печать. Однако, мы можем избежать такого дублирования, просто за счет оператора (:=) дав имя соответствующему подвыражению прямо внутри общего расчета по формуле гаверсинуса:
>>> S = 2 * rad * asin( . sqrt( . (ϕ_hav := sin((ϕ2 - ϕ1) / 2) ** 2) . + cos(ϕ1) * cos(ϕ2) * sin((λ2 - λ1) / 2) ** 2 . ) . ) >>> print(S) 7181.7841229421165 >>> print(ϕ_hav) 0.008532325425222883
Преимущество использования здесь моржового оператора заключается в том, что одновременно с вычислением значения (S) для полного выражения, параллельно мы можем также отследить и значение ϕ_hav. Это в свою очередь дает нам всю необходимую информацию для отладки с минимальными издержками.
Списки и словари
Одними из наиболее распространенных, универсальных и удобных структур данных, с которыми приходится сталкиваться при программировании на Python, безусловно являются словари и списки. Но, иногда при настройке этих структур данных нам приходится выполнять одну и ту же операцию по несколько раз. Давайте посмотрим, как это бывает на практическом примере расчета базовых статистических показателей для маленькой виртуальной выборки в виде списка. Результаты расчета наших статистических показателей, мы будем заносить в соответствующий словарь:
>>> sampling = [2, 8, 0, 1, 1, 9, 7, 7] >>> statistical_parameters = . "Объем выборки": len(sampling), . "Сумма элементов": sum(sampling), . "Среднее значение": sum(sampling) / len(sampling), . > >>> print(statistical_parameters)
Обратите внимание, что количество (объем) и сумма элементов выборки в этом примере вычисляются дважды. В нашем случае, эти повторные вычисления практически не отражаются на скорости выполнения программы. Но, если бы объем нашей выборки был бы в сотни раз большим, или же математические операции с ее элементами были бы гораздо более сложными, то подобные повторения в вычислениях существенно сказывались на быстродействии программы и, нам бы пришлось ее оптимизировать. С целью такой оптимизации программы мы, конечно, могли бы воспользоваться дополнительными переменными, инициализированными необходимыми нам значениями при помощи обычного оператора присвоения:
>>> sampling = [2, 8, 0, 1, 1, 9, 7, 7] >>> sampling_size = len(sampling) >>> sampling_sum = sum(sampling) >>> statistical_parameters = . "Объем выборки": sampling_size, . "Сумма элементов": sampling_sum, . "Среднее значение": sampling_sum / sampling_size, . > >>> print(statistical_parameters)
Но, применив моржовый оператор вместо обычного оператора присвоения, мы несомненно обеспечим нашему коду, как более компактный, так и более понятный вид.
>>> sampling = [2, 8, 0, 1, 1, 9, 7, 7] >>> statistical_parameters = . "Объем выборки": (sampling_size := len(sampling)), . "Сумма элементов": (sampling_sum := sum(sampling)), . "Среднее значение": sampling_sum / sampling_size, . > >>> print(statistical_parameters)
Наши переменные sampling_size и sampling_sum, которые раннее инициализировались в отдельных дополнительных строках программы, теперь определяются внутри словаря statistical_parameters. Такой стиль инициализации этих переменных явно показывает нам, что они используются лишь для оптимизации расчета элементов соответствующего словаря, следовательно, это однозначно облегчает читабельность нашего кода.
Примечание. Область действия переменных sampling_size и sampling_sum, как в примере с обычным оператором присвоения, так и в примере с моржовым оператором, абсолютно одинакова. Это означает, что в обоих примерах эти переменные доступны и после определения словаря statistical_parameters.
Для расширения опыта в сфере использования моржового оператора давайте рассмотрим пример создания на Python собственной утилиты file_statistics по подсчету строк, слов и символов в интересуемых нас текстовых файлах:
1 2 3 4 5 6 7 8 9 10 11 12 13
# file_statistics.py import pathlib import sys for filename in sys.argv[1:]: path = pathlib.Path(filename) counts = ( path.read_text().count("\n"), # Число строк len(path.read_text().split()), # Число слов len(path.read_text()), # Число символов ) print(*counts, path)
Вышеприведенный код утилиты может получать на входе в своей командной строке один или несколько текстовых файлов и, выводить сведения о числе строк, слов и символов, содержащихся в каждом из этих файлов. Вот подробное описание того, что происходит в данном коде:
- Строка 6 перебирает в массиве sys.argv каждое имя файла, которое может указываться пользователями в командной строке для нашей утилиты file_statistics. Этот массив представляет собой список аргументов, предоставленных пользователями в командной строке для соответствующей утилиты, включая и имя этой утилиты, которое всегда указывается в качестве первого (нулевого) элемента массива sys.argv. Таким образом, у нас в строке 6 перебираются все возможные аргументы (имена файлов) в командной строке за исключением имени самой нашей утилиты.
- В строке 7 каждое передаваемое в командной строке для нашей утилиты имя текстового файла преобразуется в pathlib.Path объект, что обеспечивает возможность для удобного чтения таких файлов в последующих строках кода нашей утилиты.
- Строки с 8 по 12 создают кортеж счетчиков для представления количества строк, слов и символов для одного текстового файла.
- Строка 9 читает текстовый файл и вычисляет количество строк в нем путем подсчета символов перевода каретки «\n».
- Строка 10 читает файл и вычисляет количество слов в нем за счет разбиения текста в этом файле по пробелам.
- Строка 11 читает текстовый файл и вычисляет количество символов в нем находя длину строки с содержанием всего этого файла.
- Строка 13 выводит на консоль значения всех трех счетчиков, наряду с именем анализируемого файла. Синтаксис кортежа — *counts, указываемый в этой строке при распечатке, на самом деле необходим для распаковки данного кортежа в виде эквивалентном print(count[0], counts[1], counts[2], path).
Давайте попробуем нашу утилиту в действии запустив ее для определения параметров файла, содержащим ее же Python код. При этом, предположим, что сам файл file_statistics.py размещен на диске D: нашего компьютера.
$ python d:\file_statistics.py d:\file_statistics.py 13 30 353 d:\file_statistics.py
Таким образом мы выяснили, что файл file_statistics.py состоит из 13 строк, 30 слов и 353 символов.
Если мы внимательно проанализируем программный код нашей утилиты, то заметим, что он далек от идеала. В частности, вызов path.read_text() у нас повторяется аж три раза, и именно столько же раз нам требуется читать каждый текстовой файл, записываемый в командной строке нашей утилиты в качестве аргументов. Но, давайте посмотрим, как избежать этих повторений используя моржовый оператор:
# file_statistics.py import pathlib import sys for filename in sys.argv[1:]: path = pathlib.Path(filename) counts = [ (text := path.read_text()).count("\n"), # Число строк len(text.split()), # Число слов len(text), # Число символов ] print(*counts, path)
В этом примере содержимое файла из командной строки сначала присваивается переменной text, а уже потом значение этой переменной повторно используется в следующих двух вычислениях. Утилита по-прежнему у нас работает так же:
$ python d:\file_statistics.py d:\file_statistics.py 13 33 336 d:\file_statistics.py
Но наглядность (читабельность) кода из предыдущего примера весьма и весьма пострадала. Поэтому, в данном случае, лучшим вариантом будет остановиться на старом добром операторе присваивания и определить с помощью него переменную text в отдельной строке перед определением кортежа counts:
# file_statistics.py import pathlib import sys for filename in sys.argv[1:]: path = pathlib.Path(filename) text = path.read_text() counts = [ text.count("\n"), # Число строк len(text.split()), # Число слов len(text), # Число символов ] print(*counts, path)
Хотя код в данном примере на одну строку и длиннее, чем предыдущем, но он все же имеет гораздо более читабельный вид, тем самым обеспечивая нам наилучший баланс между своей удобочитаемостью и эффективностью. Таким образом, оператор (:=) , обеспечивая присваивания внутри выражений, далеко не всегда при этом способствует читабельности нашего кода, хотя и делает его более компактным.
Генераторы списков
Пожалуй, наиболее впечатляющий эффект от моржового оператора проявляется при его применении в генераторах списков — особой, очень компактной, интуитивно понятной и оптимизированной по быстродействию Python конструкции, применяющейся для формирования новых списков на основе изменения уже существующих структур данных.
Но, прежде чем углубиться в примеры по непосредственному применению моржового оператора в генераторах списков, давайте сначала рассмотрим предпосылки применения данного оператора для вышеназванных конструкций. Допустим, для прогнозного анализа имеющейся у нас выборки, к каждому ее элементу мы хотим применить какую-то сверхсложную и ресурсоемкую функцию braked(), а затем еще и отфильтровать полученные из этой функции значения в соответствии с заданным условием. Все вышесказанное мы можем оформить в виде генератора списка следующим образом:
sampling = [7, 6, 1, 4, 1, 8, 0, 6] results = [braked(element) for element in sampling if braked(element) > 0]
В этом примере, полученный от функции braked() результат обработки каждого элемента выборки sampling мы сначала фильтруем, оставляя лишь положительные результаты, а затем из этих положительных результатов формируем новый список results. Проблема с данным кодом состоит в том, что в нем наша ресурсоемкая медлительна функция braked() вызывается дважды.
Конечно же, в нашей ситуации вышеприведенный код можно было бы переписать, основываясь на использовании явного цикла for:
results = [] for element in sampling: value = braked(element) if value > 0: results.append(value)
Действительно, благодаря такому подходу функция braked() вызывается лишь один раз. Но теперь, к сожалению, код у нас разросся и стал менее понятным. Ведь конструкция генератора списка в предыдущем примере ясно сигнализировала нам о создании нового списка, в то время как сейчас процесс формирования списка замаскирован внутри явного цикла for, где данный процесс распылен на несколько строк кода внутри тела данного цикла. Кроме того, сама конструкция генератора списка выполняется быстрее, чем повторные вызовы функции .append() внутри тела цикла.
Существуют и другие варианты решения вышеописанной проблемы. Так, например, мы можем попытаться оптимизировать наш код используя функцию filter() вместе с lambda оператором или же сконструировать генератор списков на основе применения так называемого двойного списка:
# Использование фильтра совместно с lambda results = filter(lambda value: value > 0, (braked(element) for element in sampling)) # Использование генератора двойного списка results = [value for element in sampling for value in [braked(element)] if value > 0]
В обоих вариантах из вышеприведенного листинга, как мы и добивались изначально, функция braked() вызывается всего лишь один раз. Но, наряду с данным достижением в оптимизации быстродействия, коды в этих обоих вариантах крайне сложны и запутаны для понимания, а также абсолютно не наглядны.
Ведь согласитесь, что для того, чтобы вникнуть в смысл кода особенно для генератора двойного списка требуется немало усилий. Кроме этого, довольно большой кусок кода в этом примере со вторым for оператором используется только лишь для инициализации переменной value значением, возвращаемым braked(element). Удивительно, но применяя к этому сверхсложному и запутанному выражению генератора двойного списка наш моржовый оператор, мы можем кардинально упростить имеющейся у нас код.
Давайте же попробуем это сделать и, перепишем приведенный выше генератор двойного списка применив в нем оператор присваивания внутри выражения:
results = [value for element in sampling if (value := braked(element)) > 0]
В этом примере следует лишь обратить внимание, что круглые скобки вокруг выражения value := braked(element) являются обязательными. В тоже время, вполне очевидно то, что код последнего примера несомненно эффективен, удобочитаем и нагляден в восприятии своего назначения.
Примечание. При формировании генераторов списка с использованием моржового оператора, Вам следует добавлять данный оператор только лишь в подвыражение if. Иначе, если вы попытаетесь с помощью (:=) инициализировать переменную value вызовом функции braked() где ни будь в другом месте конструкции генератора списка, то это не сработает:
>>> results = [(value := braked(element)) for element in sampling if value > 0] NameError: name 'value' is not defined
Подобное, неправильное место использования моржового оператора вызывает исключение NameError, поскольку записанное в конце подвыражение для оператора if в генераторе списка всегда рассчитывается первым.
While циклы
В Python может использоваться, как while цикл, так и цикл for. Как правило, for используется в случаях, когда нам либо известно число итераций в цикле, либо когда нам нужно перебрать последовательность с четко ограниченным числом элементов. Цикл же while используется тогда, когда мы заранее не знаем, сколько раз нам понадобится выполнять данный цикл.
С целью исключения неоднозначных ситуаций, в отличии от других языков программирования, условия выполнения while циклов в Python определяются лишь в верхней их части. Это иногда приводит к определенным неудобствам в процессе кодинга, когда нам приходится выполнять некоторые настройки перед выполнением соответствующих проверок. Для примера рассмотрим фрагмент программы, реализующей проведение викторины с несколькими вариантами ответов, часть из которых является правильными:
vopros = "Будете ли вы использовать моржовый оператор?" korrektnye_otvety = "да", "Да", "y", "Y", "нет", "Нет", "n", "N"> user_otvet = input(f"\nvopros> ") while user_otvet not in korrektnye_otvety: print(f"Пожалуйста, ответьте путем ввода одного из следующих значений:" f" ', '.join(korrektnye_otvety)>") user_otvet = input(f"\nvopros> ")
Недостатком вышеприведенного кода является повторение в нем идентичных input() строк, что обусловлено необходимостью получения вне цикла хотя бы одного предварительного ответа от пользователя, который затем в цикле должен быть проверен на свое совпадение с вариантами корректных ответов. В теле while цикла у нас имеется также еще один вызов input() строки, который запрашивает дополнительный ответ пользователя на случай, если его предварительный ответ не совпал ни с одним из указываемых в нашем коде корректных ответов.
Для упрощения вышеприведённого кода можно прибегнуть к довольно популярному использованию while True цикла, когда проверка условия выполняемости цикла происходит не в его начале, а позже где-то в теле цикла совместно с применением оператора break:
while True: user_otvet = input(f"\nvopros> ") if user_otvet in korrektnye_otvety: break print(f"Пожалуйста, ответьте путем ввода одного из следующих значений:" f" ', '.join(korrektnye_otvety)>")
Такой подход позволяет избежать повторений input() строк, но сильно усугубляет читабельность кода, поскольку маскирует (прячет) проверку условия выполняемости цикла где-то в его середине.
И, опять таки, перебрав все возможные варианты мы приходим к осознанию необходимости применения присваивания в середине выражения. В случае с while циклом на примере фрагмента нашей программы викторины, это делается следующим образом:
while (user_otvet := input(f"\nvopros> ")) not in korrektnye_otvety: print(f"Пожалуйста, ответьте путем ввода одного из следующих значений:" f" ', '.join(korrektnye_otvety)>")
Несмотря на то, что оператор while здесь немного разросся, код в этом примере избавился от повторяющихся строк и бесконечных циклов, став при этом гораздо более понятным и емким.
Репрезентативные и отверженные элементы в структурах данных
В раннее приведенных примерах мы использовали оператор (:=) практически в том же контексте и с той же целью, что и в случае с обычным оператором присваивания при его повседневном применении в отдельных строках. С помощью нашего моржового оператора мы обычно упрощали код, но теперь настало время рассмотреть иной вариант использования этого сравнительно нового оператора.
Суть этого варианта заключается в хитром приеме совместного использования оператора (:=) с функцией any(), при котором в таких структурах данных, как списки, кортежи или словари нам предоставляется возможность для поиска их репрезентативных элементов. Репрезентативные элементы, в данном контексте – это элементы той или иной структуры данных, которые после прохождения проверки через функцию any() возвращают True.
Аналогичным образом можно использовать моржовый оператор и для поиска отверженных элементов в структурах данных через функцию all(). Отверженные элементы в данном контексте – это элементы, которые не проходят проверку на то или иное условие, вследствие чего функция all() возвращает False.
Чтобы иметь данные для дальнейшего практического рассмотрения материалов этого раздела статьи, давайте сначала определим следующий список с названиями городов:
>>> cities = ["Ванкувер", "Осло", "Хьюстон", "Варшава", "Прага", "Гамбург"]
Применим к определенному нами выше списку городов функции any() и all() для ответа на ряд следующих вопросов:
>>> # Есть ли в списке города, название которых начинается с "О"? >>> result1 = any(city.startswith("О") for city in cities) >>> print(result1) True >>> # Есть ли в списке города, названия которых состоят из не менее чем 10 символов? >>> result2 = any(len(city) >= 10 for city in cities) >>> print(result2) False >>> # Все ли названия городов содержат "а" или "о"? >>> result3 = all(set(city) & set("ао") for city in cities) >>> print(result3) True >>> # Все ли названия городов начинаются с "В"? >>> result4 = all(city.startswith("В") for city in cities) >>> print(result4) False
Для каждого из четырех вышеприведенных вопросов функции any() или all() дают нам однозначные ответы: True или False. Но, что, если нам необходимо увидеть, какой именно город по отношению к тому или иному вопросу влечет за собой возвращения True или False после вызова соответствующей функции. Другими словами, на наши вопросы нам нужно получит четкие ответы следующим образом:
- Есть ли в списке города, названия которых начинаются с «О»? Да, потому что «Осло» начинается с «О».
- Все ли названия городов начинаются с «В»? Нет, потому что «Прага» не начинается с «В».
Иначе говоря, чтобы обосновать ответ на тот или иной вопрос, нам в нашем списке городов нужно найти либо репрезентативный, либо отверженный элемент исходя из задаваемого вопроса.
В старых версиях Python для поиска репрезентативных элементов, без использования моржового оператора, применять функцию any() не было возможности. Тогда для выхода из сложившейся ситуации приходилось переписывать код следующим образом:
>>> representative = [city for city in cities if city.startswith("О")] >>> if representative: . print(f"representative[0]> начинается с «О»") . else: . print("Города, начинающиеся с «О» в данном списке отсутствуют") . Осло начинается с «О»
В этом примере мы сначала фиксируем все названия городов, начинающиеся с «О», а затем, если такие названия у нас все-таки есть, мы выводим на консоль первый, найденный нами город, начинающийся с искомой нами буквы. По сути здесь дедовскими методами мы выполняем работу, аналогичную работе функции any().
Теперь, попробуем переписать предыдущий код с использованием функции any() и моржового оператора (:=) следующим образом:
>>> if any((representative := city).startswith("О") for city in cities): print(f" начинается с «О»") . else: . print("Города, начинающиеся с «О» в данном списке отсутствуют") . Осло начинается с «О»
Таким образом, мы можем сразу же зафиксировать первый репрезентативный элемент списка прямо во внутреннем выражении функции any(). Вместе с тем, тут нужно иметь в виду, что функция any() точно также, как и обратная ей функция all() будут проверять в обрабатываемых ними структурах данных только столько элементов, сколько им будет требоваться для определения результата.
Примечание. В случае, если вы хотите проверить, все ли названия городов начинаются с буквы «О», лучше всего, основываясь на поиске отверженных элементов, заменить any() на all() и обновить функцию print() таким образом, чтобы с ее помощью сообщать о первом элементе, который не прошел проверку.
Давайте более подробно рассмотрим что же все-таки происходит внутри функции any(), использовав при ее вызове дополнительно определенную нами пользовательскую функцию starts_with_h(), которая будет распечатывать все элементы, проверяемые any() до прекращения ее работы. При этом давайте в any() через нашу пользовательскую функцию и выражение .startswith(‘Х’) зададим условие для поиска тех городов, названия которых начинаются на букву «Х».
>>> def starts_with_h(name): . print(f"Проверка - name>: name.startswith('Х')>") . return name.startswith("Х") >>> result1 = any(starts_with_h(city) for city in cities) >>> print(result1) Проверка - Ванкувер: False Проверка - Осло: False Проверка - Хьюстон: True True
Из результатов этого примера видно, что any() проверяет элементы из списка cities только до тех пор, пока не найдет тот, который удовлетворял бы условию. В данном случае из шести городов, находящихся в нашем списке any() остановила свою работу всего лишь на третьем городе по порядку, так как название именно этого города начиналось на «Х». Другими словами, комбинация оператора (:=) и функции any() работает посредством итеративного присвоения каждого проверяемого элемента списка переменной representative. Однако, в этой переменной сохраняется только тот элемент списка, который последним проверялся any() и, является либо элементом, удовлетворяющим заданному условию, либо последним элементом в списке.
Таким образом, если даже any() возвратить False, то переменная representative все равно будет инициализирована:
>>> result1 = any(len(representative := city) >= 10 for city in cities) >>> print(result1, representative) False Гамбург
Однако в данном случае переменная representative, показывая лишь какой элемент списка был обработан последним, не является информативной, поскольку название Гамбург содержит менее десяти символов и не удовлетворяет заданному в программе условию.
Особенности синтаксиса моржового оператора
Прежде чем разобраться в закономерностях синтаксиса оператора (:=), нам следует запомнить одно, уже упоминавшееся раннее и весьма простое правило, заключающееся в том, что моржовый оператор (:=) и обычный оператор присвоения (=) никогда не могут быть использованы для прямой взаимозамены друг на друга. Именно это правило в Python предотвращает достаточно большое количество ошибок с моржовым оператором, которые характерны для других языков программирования в отношении использования там аналогичных операторов.
Для наглядности применения данного правила, приведем ряд примеров, которые пренебрегают им и, в результате выдают исключение SyntaxError:
>>> lat = lon := 0 SyntaxError: invalid syntax >>> angle(phi = lat := 59.9) SyntaxError: invalid syntax >>> def distance(phi = lat := 0, lam = lon := 0): SyntaxError: invalid syntax
Во всех этих примерах для предотвращения ошибок вместо моржового оператора нам лучше было бы использовать обычное (=). Вместе с тем, можно избежать ошибок и для вышеприведенных примеров, переписав их следующим образом:
# Приемлемые, но не рекомендуемые варианты совместного использования (:=) и (=) >>> lat = (lon := 0) >>> angle(phi = (lat := 59.9)) >>> def distance(phi = (lat := 0), lam = (lon := 0)): . pass .
Однако, применение моржового оператора во всех трех вышеприведенных примерах отнюдь не сокращает и не улучшает их код.
Непосредственное использование лишь одного моржового оператора (без обычного оператора присваивания) в некоторых выражениях также является недопустимым. Давайте рассмотрим основные такие случаи:
- Присваивание значений для элементов и атрибутов: оператор (:=) допускает присваивание значений только для простых (цельных) переменных без использования индексов и точек:
>>> (smaylik["serdtse"] := "♥") SyntaxError: cannot use assignment expressions with subscript >>> (nomer.otveta := 42) SyntaxError: cannot use assignment expressions with attribute
Все аналогичные двум данным примерам ситуации приводят к выводу ошибок с соответствующим описательным сообщением, избежать которых простыми обходными путями, к сожалению, невозможно.
- Итерируемая распаковка: моржовый оператор не может использоваться при распаковки любых последовательностей данных типа кортежей, списков или наборов:
>>> lat, lon := 59.9, 10.8 SyntaxError: invalid syntax
При этом, если мы добавим круглые скобки вокруг всего нашего выражения выше, то оно будет интерпретировано как кортеж, содержащий три следующие элементы: lat, 59.9 и 10.8.
- Расширенное присваивание: оператор (:=) не может быть использован в сочетании с операторами расширенного присваивания типа (+=). Такое сочетание приводит к SyntaxError:
>>> count +:= 1 SyntaxError: invalid syntax
Наиболее простым выходом из этой ситуации может быть оформления приведенного выше выражения в форму явного увеличения типа (count := count + 1).
В большинстве случаев моржовый оператор будет вести себя аналогично нашему традиционному оператору присваивания. Так, например, области действия переменных, назначаемых как благодаря использованию (:=), так и за счет применения обычного присваивания, являются абсолютно одинаковыми. Как правило, (:=) используется для назначения локальных переменных, однако, если переменная предварительно была объявлена как global или как nonlocal, то это также учитывается в моржовом операторе.
Связь между элементами выражения у моржового оператора гораздо менее тесная (за исключение запятой), чем у других операторов. Поэтому, в соответствующих выражениях нам может потребоваться применение круглых скобок для обобщения необходимых участков выражения. Рассмотрим, что произойдет в случае, когда мы можем «полениться» использовать круглые скобки:
>>> number = 3 >>> if square := number ** 2 > 5: . print(square) . True
Здесь переменная square связана со всем выражением number ** 2 > 5. Другими словами, square вместо того, чтобы получить значение number ** 2 (как и было изначально задумано), реально получает значение True. Для устранения данной проблемы мы можем разделить соответствующее выражение скобками следующим образом:
>>> number = 3 >>> if (square := number ** 2) > 5: . print(square) . 9
Расставленные нами таким образом скобки делают if утверждение не только правильным, но и более ясным.
И, наконец, последней проблемой, с которой мы можем столкнуться применяя (:=), является присваивание содержимого кортежей, при котором нам всегда нужно использовать круглые скобки вокруг этих кортежей. Рассмотрим следующие примеры:
>>> morzh = 3.7, False >>> morzh (3.7, False) >>> (morzh := 3.8, True) (3.8, True) >>> morzh 3.8 >>> (morzh := (3.8, True)) (3.8, True) >>> morzh (3.8, True)
Обратите внимание, что во втором примере переменная morzh принимает значение лишь первого элемента 3.8, а не всего содержимого кортежа в целом. Это происходит потому, что оператор (:=) связывает элементы в выражении более сильно, нежели запятая. Данная закономерность на первый взгляд может показаться неоправданной, однако, если бы запятая имела бы перед моржовым оператором более высокий приоритет, то (:=) невозможно было бы использовать при вызовах функций с более чем одним аргументом.
Рекомендации по стилю для моржового оператора в основном являются такими же, как и для обычного оператора присваивания (=). Здесь, во-первых, всегда в коде следует добавлять пробелы вокруг (:=), а во-вторых, круглые скобки вокруг выражений нужно использовать лишь по мере необходимости, т.е. необходимо всегда избегать добавления лишних (ненужных) круглых скобок.
Общий подход к применению присваиваний внутри выражений заключается в том, чтобы сделать сами эти выражения более простыми и интуитивно понятными. При этом, никогда не следует злоупотреблять такими присваиваниями в случаях, когда они могут загромождать наш код и делать его менее читабельным.
Выводы
В рамках данной статьи мы вами рассмотрели основные нюансы, связанные с работой и использованием моржового оператор при различных ситуациях, могущих возникнуть в нашем коде. Так, в частности, мы убедились, что используя синтаксис оператора (:=) можно существенно сократить наш код, избежать в нем различных видов повторения и сделать его значительно более эффективным и легким для восприятия. В то же время в этой статье были приведены некоторые примеры, когда применение моржового оператора лишь усложняет программный код, делая его менее читабельным и ясным. Таким образом, основной целью настоящей статьи является культивирование у ее читателей навыков выявления и разграничения обстоятельств, при которых применение оператора (:=) либо будет целесообразным и оправданным, либо будет только лишь способствовать снижению эффективности и усложнению программного кода на Python.
![]()
Практический Python для начинающих
Станьте junior Python программистом за 7 месяцев
