ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 03.12.2023
Просмотров: 394
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Итоги
Несогласованность встречается в любом языке, даже в языках программирова- ния. Python содержит несколько потенциальных ловушек, которые подстерегают невнимательных. И хотя они встречаются достаточно редко, лучше знать о них заранее. Это позволит вам быстро опознать и устранить проблемы, которые могут из-за них возникнуть.
Хотя теоретически элементы можно добавлять и удалять из списка в процессе пере- бора этого списка, это потенциальный источник ошибок. Намного безопаснее пере- брать копию списка, а затем внести изменения в оригинал. Создавая копии списка
(или любого другого изменяемого объекта), помните, что команды присваивания копируют только ссылку на объект, а не сам объект. Для создания копий объекта
(и копий всех объектов, ссылки на которые в нем хранятся) можно воспользоваться функцией copy.deepcopy()
184
Глава 8.Часто встречающиеся ловушки Python
Изменяемые объекты не должны использоваться в командах def для аргументов по умолчанию, потому что они создаются однократно при выполнении команды def
, а не при каждом вызове функции. Лучше использовать в качестве аргумента по умолчанию
None и добавить код, который проверяет
None и создает изменяемый объект при вызове функции.
Еще одна коварная ловушка возникает при конкатенации нескольких строк опе- ратором
+
в цикле. При небольшом количестве итераций этот синтаксис работает нормально. Но «под капотом» Python постоянно создает и уничтожает объекты строк при каждой итерации. Более эффективное решение — присоединять меньшие строки к списку, а затем вызвать оператор join()
для создания итоговой строки.
Метод sort()
сортирует элементы по числовым кодовым пунктам, и этот порядок не совпадает с алфавитным: буква Z в верхнем регистре предшествует букве a в нижнем регистре. Проблему можно решить вызовом sort(key=str.lower)
У чисел с плавающей точкой возникают небольшие погрешности — это побочный эффект способа представления чисел в памяти компьютера. Для большинства программ эти ошибки не важны. Но если для ваших задач это важно, вы можете воспользоваться модулем Python decimal
Никогда не объединяйте операторы
!=
в цепочки, потому что, как ни странно, вы- ражения вида 'cat'
!=
'dog'
!=
'cat'
дают результат
True
Хотя в этой главе описаны ловушки Python, которые вы наверняка встретите в ра- боте, они не попадаются ежедневно в реальном коде. Python всеми силами старается свести к минимуму неприятные неожиданности в ваших программах. В следующей главе я расскажу о ловушках еще более редких и экзотических. Вероятность того, что они когда-нибудь попадутся на вашем пути, близка к нулю, и все же вам на- верняка будет любопытно исследовать причины их существования.
9
Экзотические странности Python
Системы правил, определяющие язык программирования, достаточно сложны. Порой они приводят к появлению кода хотя и не ошибочного, но достаточно странного и неожидан- ного. В этой главе мы займемся некоторыми малоизвестными странностями языка Python. Вряд ли вы когда-нибудь столк- нетесь с ними в повседневном программировании, но их можно рассматривать как неочевидное применение синтаксиса Python (или злоупотребление им — зависит от точки зрения).
Изучая примеры этой главы, вы начнете лучше представлять, как работают внутрен- ние механизмы Python. Давайте исследуем некоторые экзотические случаи — из интереса и для расширения кругозора.
Почему 256 — это 256, а 257 — не 257
Оператор
==
проверяет, равны ли два объекта, а оператор is проверяет их на тож- дественность, то есть сравнивает их идентичности. И хотя целое число
42
и число с плавающей точкой
42.0
имеют одинаковое значение, это два разных объекта, хранящихся в разных местах компьютерной памяти. Чтобы убедиться в этом, про- верьте их идентичности при помощи функции id()
:
>>> a = 42
>>> b = 42.0
>>> a == b
True
>>> a is b
False
>>> id(a), id(b)
(140718571382896, 2526629638888)
186
Глава 9.Экзотические странности Python
Когда Python создает новый объект, представляющий целое число, и сохра- няет его в памяти, создание объекта занимает очень мало времени. CPython
(интерпретатор Python, доступный для загрузки по адресу https://python.org) применяет крошечную оптимизацию, создавая объекты целых чисел от –5 до
256 при запуске каждой программы. Эти числа называются предварительно
определенными (preallocated), и CPython автоматически создает для них объекты, потому что они довольно часто применяются: число 0 или 2 будет использовано в программе с большей вероятностью, чем, скажем, 1729. При создании объекта нового целого числа в памяти CPython сначала проверяет, принадлежит ли оно диапазону от –5 до 256. В таком случае CPython экономит время, просто воз- вращая существующий объект целого числа, вместо того чтобы создавать его заново. Такое поведение также экономит память, так как она не расходуется на хранение малых целых чисел (рис. 9.1).
Рис. 9.1. Для экономии памяти Python использует множественные ссылки на один объект целого числа (слева) вместо дублирующихся объектов целых чисел для каждой ссылки (справа)
Из-за этой оптимизации некоторые нетипичные ситуации могут приводить к стран- ным результатам. Чтобы увидеть пример такого рода, введите следующий фрагмент в интерактивной оболочке:
>>> a = 256
>>> b = 256
>>> a is b
❶
True
>>> c = 257
>>> d = 257
>>> c is d
❷
False
Все объекты, представляющие целое число
256
, в действительности являются од- ним объектом, поэтому для a
и b
оператор возвращает
True
❶
. Но для c
и d
Python создает разные объекты
257
, поэтому оператор is возвращает
False
❷
Выражение
257
is
257
дает результат
True
, потому что CPython повторно исполь- зует объект целого числа, созданного для идентичного литерала в той же команде:
Интернирование строк
187
>>> 257 is 257
True
Конечно, в реальных программах обычно используется только значение целого числа, а не его идентичность. Программы никогда не используют оператор is для сравнения целых чисел, чисел с плавающей точкой, строк, логических значений или значений других простых типов данных. Одно из исключений такого рода встречается при использовании is
None вместо
==
None
, и я объясняю это в под- разделе «Использование is для сравнения с None вместо ==», с. 122. В остальных ситуациях это исключение почти не встречается.
Интернирование строк
Аналогичным образом Python повторно использует строки для представления идентичных строковых литералов в вашем коде вместо создания нескольких копий одной строки. Чтобы убедиться в этом, введите следующий фрагмент в интерак- тивной оболочке:
>>> spam = 'cat'
>>> eggs = 'cat'
>>> spam is eggs
True
>>> id(spam), id(eggs)
(1285806577904, 1285806577904)
Python замечает, что строковый литерал 'cat'
, присвоенный eggs
, совпадает со строковым литералом 'cat'
, присвоенным spam
; таким образом, вместо второго, избыточного объекта string переменной eggs просто присваивается ссылка на тот же объект строки, который используется spam
. Этим объясняется совпадение идентичностей строк.
Такая оптимизация называется интернированием (string interning) строк. Как и предварительное определение целых чисел, это всего лишь подробность реа- лизации CPython. Никогда не пишите код, который зависит от нее. Кроме того, эта оптимизация не обнаруживает все возможные идентичные строки. Попытки идентифицировать все экземпляры, к которым можно применить оптимизацию, часто занимают больше времени, чем сэкономит оптимизация. Например, попро- буйте создать строку 'cat'
из 'c'
и 'at'
в интерактивной оболочке; вы заметите, что CPython создает итоговую строку 'cat'
как новый объект строки, вместо того чтобы повторно использовать объект строки, созданный для spam
:
>>> bacon = 'c'
>>> bacon += 'at'
>>> spam is bacon
False
188
Глава 9.Экзотические странности Python
>>> id(spam), id(bacon)
(1285806577904, 1285808207384)
Интернирование строк — метод оптимизации, применяемый интерпретаторами и компиляторами для многих языков программирования. За дополнительной ин- формацией обращайтесь на https://en.wikipedia.org/wiki/String_interning.
Фиктивные операторы инкремента и декремента
в языке Python
В Python можно увеличить значение переменной на 1 или уменьшить его на 1 при помощи расширенных операторов присваивания. Выражения spam
+=
1
и spam
-=
1
увеличивают и уменьшают числовые значения в spam на 1 соответственно.
В других языках, таких как C++ и JavaScript, существуют операторы
++
и
-- для выполнения инкремента и декремента. (Этот факт отражен в самом названии языка C++; это иронический намек на то, что язык является улучшенной формой языка C.) В коде на языках C++ и JavaScript могут встречаться такие операции, как
++spam или spam++
. Создатели Python благоразумно не стали включать в язык эти операторы, получившие печальную известность из-за коварных ошибок (см. https://
softwareengineering.stackexchange.com/q/59880).
Тем не менее следующий код Python вполне допустим:
>>> spam = --spam
>>> spam
42
Первое, на что следует обратить внимание: операторы
++
и
-- в Python не уве- личивают и не уменьшают значение, хранимое в переменной spam
. Начальный знак
- является унарным оператором отрицания Python. Он позволяет писать код следующего вида:
>>> spam = 42
>>> -spam
-42
Ничто не запрещает поставить перед значением несколько унарных операторов отрицания. С двумя операторами выполняется отрицание отрицания значения, что для целых чисел просто дает исходное значение:
>>> spam = 42
>>> -(-spam)
42
Все или ничего
189
Это очень глупая операция, и, скорее всего, вы никогда не увидите двукратное ис- пользование унарного оператора отрицания в реальном коде. (А если увидите, то, наверное, программист учился программировать на другом языке и просто написал ошибочный код на Python!)
Также существует унарный оператор
+
. Он создает целое значение с таким же зна- ком, как у исходного значения, то есть по сути не делает ничего:
>>> spam = 42
>>> +spam
42
>>> spam = -42
>>> +spam
-42
Запись
+42
(или
++42
) выглядит так же глупо, как и
--42
, так почему же в Python был включен этот унарный оператор? Он существует только как парный по от- ношению к оператору
-
, если вам потребуется перегрузить эти операторы в ваших классах. (Здесь много терминов, которые могут быть вам незнакомы. Перегрузка операторов более подробно рассматривается в главе 17.)
Унарные операторы
+
и
- могут располагаться только перед значением Python, но не после него. И хотя выражения spam++
и spam-- могут быть действительным кодом в C++ или JavaScript, в Python они вызывают синтаксические ошибки:
>>> spam++
File "", line 1
spam++
^
SyntaxError: invalid syntax
В Python нет операторов инкремента и декремента. Эта странность языкового синтаксиса только создает иллюзию того, что они существуют.
Все или ничего
Встроенная функция all()
получает значение-последовательность (например, список) и возвращает
True
, если все значения в этой последовательности являются истинными. Если хотя бы одно или несколько значений являются ложными, воз- вращается
False
. Вызов функции all([False,
True,
True])
можно рассматривать как эквивалент выражения
False and
True and
True
Функция all()
может использоваться в сочетании со списковыми включениями, для того чтобы сначала создать список логических значений на основании другого
190
Глава 9.Экзотические странности Python списка, а затем вычислить их сводное значение. Например, введите следующий фрагмент в интерактивной оболочке:
>>> spam = [67, 39, 20, 55, 13, 45, 44]
>>> [i > 42 for i in spam]
[True, False, False, True, False, True, True]
>>> all([i > 42 for i in spam])
False
>>> eggs = [43, 44, 45, 46]
>>> all([i > 42 for i in eggs])
True
Функция all()
возвращает
True
, если все числа в spam или eggs больше 42.
Но если передать all()
пустую последовательность, функция всегда возвращает
True
. Введите следующий фрагмент в интерактивной оболочке:
>>> all([])
True
Правильнее считать, что all([])
проверяет утверждение «ни один из элементов списка не является ложным» (вместо «все элементы списка являются истинными»).
В противном случае вы можете получить неожиданные результаты. Например, введите следующий фрагмент в интерактивной оболочке:
>>> spam = []
>>> all([i > 42 for i in spam])
True
>>> all([i < 42 for i in spam])
True
>>> all([i == 42 for i in spam])
True
Пример вроде бы показывает, что все значения в spam
(пустой список!) не только больше 42, но они одновременно меньше 42 и равны 42! Это кажется невозможным с точки зрения логики. Но помните, что каждое из трех списковых включений при вычислении дает пустой список. В нем нет ни одного ложного элемента, а все функции all()
возвращают
True
Логические значения как целые числа
Подобно тому как Python считает, что значение с плавающей точкой
42.0
равно целому значению
42
, логические значения
True и
False считаются эквивалентными
1
и
0
соответственно. В Python тип данных bool является субклассом типа данных int
. (Классы и субклассы рассматриваются в главе 16.) Вы можете воспользоваться int()
для преобразования логических значений в целые числа:
Логические значения как целые числа
191
>>> int(False)
0
>>> int(True)
1
>>> True == 1
True
>>> False == 0
True
Вы также можете использовать isinstance()
, чтобы подтвердить, что логическое значение относится к типу integer
:
>>> isinstance(True, bool)
True
>>> isinstance(True, int)
True
Значение
True относится к типу данных bool
. Но так как bool является субклассом int
,
True также является частным случаем int
. Это означает, что
True и
False мо- гут использоваться практически в любом месте, где допустимо применение целых чисел. Это порождает странный код:
>>> True + False + True + True # То же, что 1 + 0 + 1 + 1 3
>>> -True # То же, что -1.
-1
>>> 42 * True # То же, что математическое умножение 42 * 1.
42
>>> 'hello' * False # То же, что репликация строк 'hello' * 0.
' '
>>> 'hello'[False] # То же, что 'hello'[0]
'h'
>>> 'hello'[True] # То же, что 'hello'[1]
'e'
>>> 'hello'[-True] # То же, что 'hello'[-1]
'o'
Конечно, из того, что значения bool могут использоваться как числа, не следует, что это стоит делать. Все приведенные примеры очень плохо читаются и никогда не должны использоваться в реальном коде. Изначально в Python не было типа данных bool
. Логический тип появился только в Python 2.3, когда тип bool был определен как субкласс int для упрощения реализации. С историей типа дан- ных bool можно ознакомиться в PEP 285 на https://www.python.org/dev/peps/
pep-0285/.
Кстати говоря,
True и
False стали ключевыми словами только в Python 3. А следо- вательно, в Python 2
True и
False можно было использовать как имена переменных, что приводит к появлению парадоксального кода следующего вида:
192
Глава 9.Экзотические странности Python
Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> True is False
False
>>> True = False
>>> True is False
True
К счастью, подобный запутанный код невозможен в Python 3, и при попытке ис- пользовать ключевые слова
True и
False в качестве имен переменных происходит синтаксическая ошибка.
Сцепление разных видов операторов
Цепочки разных операторов в одном выражении могут привести к неожиданным ошибкам. Например, следующий (откровенно говоря, нереалистичный) пример использует операторы
==
и in в одном выражении:
>>> False == False in [False]
True
Результат
True выглядит удивительно, потому что можно ожидать, что выражение будет вычисляться по одной из следующих схем.
(False
==
False)
in
[False]
, дает результат
False
False
==
(False in
[False])
, также дает результат
False
Но выражение
False
==
False in
[False]
не эквивалентно ни одному из этих вы- ражений. Вместо этого оно эквивалентно
(False
==
False)
and
(False in
[False])
, подобно тому как
42
<
spam
<
99
эквивалентно
(42
<
spam)
and
(spam
<
99)
. Это вы- ражение вычисляется по следующей диаграмме:
(False == False) and (False in [False])
↓
(True) and (False in [False])
↓
(True) and (True)
↓
True
Выражение
False
==
False in
[False]
скорее является любопытной головоломкой на языке Python, но вряд ли вы когда-нибудь встретите его в реальном коде.
Несогласованность встречается в любом языке, даже в языках программирова- ния. Python содержит несколько потенциальных ловушек, которые подстерегают невнимательных. И хотя они встречаются достаточно редко, лучше знать о них заранее. Это позволит вам быстро опознать и устранить проблемы, которые могут из-за них возникнуть.
Хотя теоретически элементы можно добавлять и удалять из списка в процессе пере- бора этого списка, это потенциальный источник ошибок. Намного безопаснее пере- брать копию списка, а затем внести изменения в оригинал. Создавая копии списка
(или любого другого изменяемого объекта), помните, что команды присваивания копируют только ссылку на объект, а не сам объект. Для создания копий объекта
(и копий всех объектов, ссылки на которые в нем хранятся) можно воспользоваться функцией copy.deepcopy()
184
Глава 8.Часто встречающиеся ловушки Python
Изменяемые объекты не должны использоваться в командах def для аргументов по умолчанию, потому что они создаются однократно при выполнении команды def
, а не при каждом вызове функции. Лучше использовать в качестве аргумента по умолчанию
None и добавить код, который проверяет
None и создает изменяемый объект при вызове функции.
Еще одна коварная ловушка возникает при конкатенации нескольких строк опе- ратором
+
в цикле. При небольшом количестве итераций этот синтаксис работает нормально. Но «под капотом» Python постоянно создает и уничтожает объекты строк при каждой итерации. Более эффективное решение — присоединять меньшие строки к списку, а затем вызвать оператор join()
для создания итоговой строки.
Метод sort()
сортирует элементы по числовым кодовым пунктам, и этот порядок не совпадает с алфавитным: буква Z в верхнем регистре предшествует букве a в нижнем регистре. Проблему можно решить вызовом sort(key=str.lower)
У чисел с плавающей точкой возникают небольшие погрешности — это побочный эффект способа представления чисел в памяти компьютера. Для большинства программ эти ошибки не важны. Но если для ваших задач это важно, вы можете воспользоваться модулем Python decimal
Никогда не объединяйте операторы
!=
в цепочки, потому что, как ни странно, вы- ражения вида 'cat'
!=
'dog'
!=
'cat'
дают результат
True
Хотя в этой главе описаны ловушки Python, которые вы наверняка встретите в ра- боте, они не попадаются ежедневно в реальном коде. Python всеми силами старается свести к минимуму неприятные неожиданности в ваших программах. В следующей главе я расскажу о ловушках еще более редких и экзотических. Вероятность того, что они когда-нибудь попадутся на вашем пути, близка к нулю, и все же вам на- верняка будет любопытно исследовать причины их существования.
9
Экзотические странности Python
Системы правил, определяющие язык программирования, достаточно сложны. Порой они приводят к появлению кода хотя и не ошибочного, но достаточно странного и неожидан- ного. В этой главе мы займемся некоторыми малоизвестными странностями языка Python. Вряд ли вы когда-нибудь столк- нетесь с ними в повседневном программировании, но их можно рассматривать как неочевидное применение синтаксиса Python (или злоупотребление им — зависит от точки зрения).
Изучая примеры этой главы, вы начнете лучше представлять, как работают внутрен- ние механизмы Python. Давайте исследуем некоторые экзотические случаи — из интереса и для расширения кругозора.
Почему 256 — это 256, а 257 — не 257
Оператор
==
проверяет, равны ли два объекта, а оператор is проверяет их на тож- дественность, то есть сравнивает их идентичности. И хотя целое число
42
и число с плавающей точкой
42.0
имеют одинаковое значение, это два разных объекта, хранящихся в разных местах компьютерной памяти. Чтобы убедиться в этом, про- верьте их идентичности при помощи функции id()
:
>>> a = 42
>>> b = 42.0
>>> a == b
True
>>> a is b
False
>>> id(a), id(b)
(140718571382896, 2526629638888)
186
Глава 9.Экзотические странности Python
Когда Python создает новый объект, представляющий целое число, и сохра- няет его в памяти, создание объекта занимает очень мало времени. CPython
(интерпретатор Python, доступный для загрузки по адресу https://python.org) применяет крошечную оптимизацию, создавая объекты целых чисел от –5 до
256 при запуске каждой программы. Эти числа называются предварительно
определенными (preallocated), и CPython автоматически создает для них объекты, потому что они довольно часто применяются: число 0 или 2 будет использовано в программе с большей вероятностью, чем, скажем, 1729. При создании объекта нового целого числа в памяти CPython сначала проверяет, принадлежит ли оно диапазону от –5 до 256. В таком случае CPython экономит время, просто воз- вращая существующий объект целого числа, вместо того чтобы создавать его заново. Такое поведение также экономит память, так как она не расходуется на хранение малых целых чисел (рис. 9.1).
Рис. 9.1. Для экономии памяти Python использует множественные ссылки на один объект целого числа (слева) вместо дублирующихся объектов целых чисел для каждой ссылки (справа)
Из-за этой оптимизации некоторые нетипичные ситуации могут приводить к стран- ным результатам. Чтобы увидеть пример такого рода, введите следующий фрагмент в интерактивной оболочке:
>>> a = 256
>>> b = 256
>>> a is b
❶
True
>>> c = 257
>>> d = 257
>>> c is d
❷
False
Все объекты, представляющие целое число
256
, в действительности являются од- ним объектом, поэтому для a
и b
оператор возвращает
True
❶
. Но для c
и d
Python создает разные объекты
257
, поэтому оператор is возвращает
False
❷
Выражение
257
is
257
дает результат
True
, потому что CPython повторно исполь- зует объект целого числа, созданного для идентичного литерала в той же команде:
Интернирование строк
187
>>> 257 is 257
True
Конечно, в реальных программах обычно используется только значение целого числа, а не его идентичность. Программы никогда не используют оператор is для сравнения целых чисел, чисел с плавающей точкой, строк, логических значений или значений других простых типов данных. Одно из исключений такого рода встречается при использовании is
None вместо
==
None
, и я объясняю это в под- разделе «Использование is для сравнения с None вместо ==», с. 122. В остальных ситуациях это исключение почти не встречается.
Интернирование строк
Аналогичным образом Python повторно использует строки для представления идентичных строковых литералов в вашем коде вместо создания нескольких копий одной строки. Чтобы убедиться в этом, введите следующий фрагмент в интерак- тивной оболочке:
>>> spam = 'cat'
>>> eggs = 'cat'
>>> spam is eggs
True
>>> id(spam), id(eggs)
(1285806577904, 1285806577904)
Python замечает, что строковый литерал 'cat'
, присвоенный eggs
, совпадает со строковым литералом 'cat'
, присвоенным spam
; таким образом, вместо второго, избыточного объекта string переменной eggs просто присваивается ссылка на тот же объект строки, который используется spam
. Этим объясняется совпадение идентичностей строк.
Такая оптимизация называется интернированием (string interning) строк. Как и предварительное определение целых чисел, это всего лишь подробность реа- лизации CPython. Никогда не пишите код, который зависит от нее. Кроме того, эта оптимизация не обнаруживает все возможные идентичные строки. Попытки идентифицировать все экземпляры, к которым можно применить оптимизацию, часто занимают больше времени, чем сэкономит оптимизация. Например, попро- буйте создать строку 'cat'
из 'c'
и 'at'
в интерактивной оболочке; вы заметите, что CPython создает итоговую строку 'cat'
как новый объект строки, вместо того чтобы повторно использовать объект строки, созданный для spam
:
>>> bacon = 'c'
>>> bacon += 'at'
>>> spam is bacon
False
188
Глава 9.Экзотические странности Python
>>> id(spam), id(bacon)
(1285806577904, 1285808207384)
Интернирование строк — метод оптимизации, применяемый интерпретаторами и компиляторами для многих языков программирования. За дополнительной ин- формацией обращайтесь на https://en.wikipedia.org/wiki/String_interning.
Фиктивные операторы инкремента и декремента
в языке Python
В Python можно увеличить значение переменной на 1 или уменьшить его на 1 при помощи расширенных операторов присваивания. Выражения spam
+=
1
и spam
-=
1
увеличивают и уменьшают числовые значения в spam на 1 соответственно.
В других языках, таких как C++ и JavaScript, существуют операторы
++
и
-- для выполнения инкремента и декремента. (Этот факт отражен в самом названии языка C++; это иронический намек на то, что язык является улучшенной формой языка C.) В коде на языках C++ и JavaScript могут встречаться такие операции, как
++spam или spam++
. Создатели Python благоразумно не стали включать в язык эти операторы, получившие печальную известность из-за коварных ошибок (см. https://
softwareengineering.stackexchange.com/q/59880).
Тем не менее следующий код Python вполне допустим:
>>> spam = --spam
>>> spam
42
Первое, на что следует обратить внимание: операторы
++
и
-- в Python не уве- личивают и не уменьшают значение, хранимое в переменной spam
. Начальный знак
- является унарным оператором отрицания Python. Он позволяет писать код следующего вида:
>>> spam = 42
>>> -spam
-42
Ничто не запрещает поставить перед значением несколько унарных операторов отрицания. С двумя операторами выполняется отрицание отрицания значения, что для целых чисел просто дает исходное значение:
>>> spam = 42
>>> -(-spam)
42
Все или ничего
189
Это очень глупая операция, и, скорее всего, вы никогда не увидите двукратное ис- пользование унарного оператора отрицания в реальном коде. (А если увидите, то, наверное, программист учился программировать на другом языке и просто написал ошибочный код на Python!)
Также существует унарный оператор
+
. Он создает целое значение с таким же зна- ком, как у исходного значения, то есть по сути не делает ничего:
>>> spam = 42
>>> +spam
42
>>> spam = -42
>>> +spam
-42
Запись
+42
(или
++42
) выглядит так же глупо, как и
--42
, так почему же в Python был включен этот унарный оператор? Он существует только как парный по от- ношению к оператору
-
, если вам потребуется перегрузить эти операторы в ваших классах. (Здесь много терминов, которые могут быть вам незнакомы. Перегрузка операторов более подробно рассматривается в главе 17.)
Унарные операторы
+
и
- могут располагаться только перед значением Python, но не после него. И хотя выражения spam++
и spam-- могут быть действительным кодом в C++ или JavaScript, в Python они вызывают синтаксические ошибки:
>>> spam++
File "
spam++
^
SyntaxError: invalid syntax
В Python нет операторов инкремента и декремента. Эта странность языкового синтаксиса только создает иллюзию того, что они существуют.
Все или ничего
Встроенная функция all()
получает значение-последовательность (например, список) и возвращает
True
, если все значения в этой последовательности являются истинными. Если хотя бы одно или несколько значений являются ложными, воз- вращается
False
. Вызов функции all([False,
True,
True])
можно рассматривать как эквивалент выражения
False and
True and
True
Функция all()
может использоваться в сочетании со списковыми включениями, для того чтобы сначала создать список логических значений на основании другого
190
Глава 9.Экзотические странности Python списка, а затем вычислить их сводное значение. Например, введите следующий фрагмент в интерактивной оболочке:
>>> spam = [67, 39, 20, 55, 13, 45, 44]
>>> [i > 42 for i in spam]
[True, False, False, True, False, True, True]
>>> all([i > 42 for i in spam])
False
>>> eggs = [43, 44, 45, 46]
>>> all([i > 42 for i in eggs])
True
Функция all()
возвращает
True
, если все числа в spam или eggs больше 42.
Но если передать all()
пустую последовательность, функция всегда возвращает
True
. Введите следующий фрагмент в интерактивной оболочке:
>>> all([])
True
Правильнее считать, что all([])
проверяет утверждение «ни один из элементов списка не является ложным» (вместо «все элементы списка являются истинными»).
В противном случае вы можете получить неожиданные результаты. Например, введите следующий фрагмент в интерактивной оболочке:
>>> spam = []
>>> all([i > 42 for i in spam])
True
>>> all([i < 42 for i in spam])
True
>>> all([i == 42 for i in spam])
True
Пример вроде бы показывает, что все значения в spam
(пустой список!) не только больше 42, но они одновременно меньше 42 и равны 42! Это кажется невозможным с точки зрения логики. Но помните, что каждое из трех списковых включений при вычислении дает пустой список. В нем нет ни одного ложного элемента, а все функции all()
возвращают
True
Логические значения как целые числа
Подобно тому как Python считает, что значение с плавающей точкой
42.0
равно целому значению
42
, логические значения
True и
False считаются эквивалентными
1
и
0
соответственно. В Python тип данных bool является субклассом типа данных int
. (Классы и субклассы рассматриваются в главе 16.) Вы можете воспользоваться int()
для преобразования логических значений в целые числа:
Логические значения как целые числа
191
>>> int(False)
0
>>> int(True)
1
>>> True == 1
True
>>> False == 0
True
Вы также можете использовать isinstance()
, чтобы подтвердить, что логическое значение относится к типу integer
:
>>> isinstance(True, bool)
True
>>> isinstance(True, int)
True
Значение
True относится к типу данных bool
. Но так как bool является субклассом int
,
True также является частным случаем int
. Это означает, что
True и
False мо- гут использоваться практически в любом месте, где допустимо применение целых чисел. Это порождает странный код:
>>> True + False + True + True # То же, что 1 + 0 + 1 + 1 3
>>> -True # То же, что -1.
-1
>>> 42 * True # То же, что математическое умножение 42 * 1.
42
>>> 'hello' * False # То же, что репликация строк 'hello' * 0.
' '
>>> 'hello'[False] # То же, что 'hello'[0]
'h'
>>> 'hello'[True] # То же, что 'hello'[1]
'e'
>>> 'hello'[-True] # То же, что 'hello'[-1]
'o'
Конечно, из того, что значения bool могут использоваться как числа, не следует, что это стоит делать. Все приведенные примеры очень плохо читаются и никогда не должны использоваться в реальном коде. Изначально в Python не было типа данных bool
. Логический тип появился только в Python 2.3, когда тип bool был определен как субкласс int для упрощения реализации. С историей типа дан- ных bool можно ознакомиться в PEP 285 на https://www.python.org/dev/peps/
pep-0285/.
Кстати говоря,
True и
False стали ключевыми словами только в Python 3. А следо- вательно, в Python 2
True и
False можно было использовать как имена переменных, что приводит к появлению парадоксального кода следующего вида:
192
Глава 9.Экзотические странности Python
Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> True is False
False
>>> True = False
>>> True is False
True
К счастью, подобный запутанный код невозможен в Python 3, и при попытке ис- пользовать ключевые слова
True и
False в качестве имен переменных происходит синтаксическая ошибка.
Сцепление разных видов операторов
Цепочки разных операторов в одном выражении могут привести к неожиданным ошибкам. Например, следующий (откровенно говоря, нереалистичный) пример использует операторы
==
и in в одном выражении:
>>> False == False in [False]
True
Результат
True выглядит удивительно, потому что можно ожидать, что выражение будет вычисляться по одной из следующих схем.
(False
==
False)
in
[False]
, дает результат
False
False
==
(False in
[False])
, также дает результат
False
Но выражение
False
==
False in
[False]
не эквивалентно ни одному из этих вы- ражений. Вместо этого оно эквивалентно
(False
==
False)
and
(False in
[False])
, подобно тому как
42
<
spam
<
99
эквивалентно
(42
<
spam)
and
(spam
<
99)
. Это вы- ражение вычисляется по следующей диаграмме:
(False == False) and (False in [False])
↓
(True) and (False in [False])
↓
(True) and (True)
↓
True
Выражение
False
==
False in
[False]
скорее является любопытной головоломкой на языке Python, но вряд ли вы когда-нибудь встретите его в реальном коде.