9 странностей Python и их объяснение

1. Есть оператор “else”, но нет “if”

Многие языки программирования имеют структуру “if-else” для работы с условными операторами.

Однако в Python можно использовать оператор “else” без “if”.

leaders = ["Elon", "Tim", "Warren"]

for i in leaders:
if i == "Yang":
print("Yang is a leader!")
break
else:
print("Not found Yang!")

# Янг не найден!

В приведенном выше коде нет оператора “if”. Но блок кода с оператором “else” был выполнен успешно!

Таковы особенности синтаксиса “for-else” в Python.

Это странная фича требует осторожности при использовании в производственных проектах. Но идея ее неожиданно проста:

Блок “else” будет выполняться, если в цикле for нет прерывания.

Чтобы доказать это, немного изменим список:

leaders = ["Yang", "Elon", "Tim", "Warren"]

for i in leaders:
if i == "Yang":
print("Yang is a leader!")
break
else:
print("Not found Yang!")

# Янг - лидер!

Как видно из приведенной выше программы, список leaders теперь включает в себя “Yang”. Значит, цикл for был прерван, и оператор else не был выполнен.

2. Изменение неизменяемого кортежа

Как известно, кортежи  —  это неизменяемые объекты Python.

Но следующий кортеж может быть изменен:

tp = ([1, 2, 3], 4, 5)
tp[0].append(4)
print(tp)
# ([1, 2, 3, 4], 4, 5)

Это происходит потому, что изменяемость вложенных объектов Python зависит от каждого объекта. tp  —  неизменяемый кортеж, но первый элемент tp  —  список, который является изменяемым.

3. 256 — это 256, но 257 — не 257

Иногда результаты при проверке на равенство чисел в Python могут удивить:

>>> a=256
>>> b=256
>>> a is b
True
>>> x=257
>>> y=257
>>> x is y
False

Для экономии времени и затрат памяти Python предварительно загружает все малые целые числа в диапазоне [-5, 256]. Поэтому, когда объявляется целое число в этом диапазоне, Python просто ссылается на кэшированное целое число и не создает новый объект.

a и b  —  один и тот же объект, но x и y  —  два разных объекта.

Чтобы убедиться в этом, выведем id каждой переменной:

>>> id(a)
1696073345424
>>> id(b)
1696073345424
>>> id(x)
1696122928496
>>> id(y)
1696122928752

Этот механизм называется интернированием целых чисел или кэшированием целых чисел.

Но что выведет следующий код? Снова False?

>>> 257 is 257

Python всегда старается сделать все возможное, чтобы сохранить ясность. Поскольку две переменные не были определены отдельно, Python получит достаточно контекста в рамках однострочной команды и на этот раз выведет True.

4. Интернирование строк

Подобно механизму интернирования целых чисел, Python также кэширует строки небольшого размера для экономии вычислительных ресурсов.

Рассмотрим пример:

>>> a = "Yang"
>>> b = "Yang"
>>> a is b
True
>>> c = "Yang Zhou"
>>> d = "Yang Zhou"
>>> c is d
False

Интернирование строк также зависит от реализации Python.

Для приведенного выше примера был использован CPython с алгоритмом кэширования AST optimizer. Этот алгоритм может кэшировать до 4096 символов, но строки, включающие пробелы, не будут интернированы.

5. 0,1+0,2 — это не 0,3

Все знают, что 0,1+0,2 равно 0,3, но Python так не считает:

print(0.1+0.2==0.3)
# False

Каков же результат вычисления 0,1+0,2 в Python, если не 0,3?

print(0.1+0.2)
# 0.30000000000000004

По правде говоря, это не вина Python. Ни один компьютер не может точно вычислить плавающее значение.

Компьютеры могут хранить и обрабатывать числа с плавающей точкой только с определенной точностью. Поэтому операции с плавающей точкой зависят от аппаратной реализации в процессоре компьютера. Нет такого языка программирования, который мог бы гарантировать, что его вычисления с плавающей точкой всегда верны.

6. “+=” быстрее, чем “=”

При конкатенации строк в Python операторы += и + могут дать один и тот же результат, но с разными временными затратами:

import timeit

print(timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100))
# 0.7268792000000001
print(timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100))
# 0.3451913999999999

При конкатенации более двух строк в Python оператор += быстрее, чем +. Поскольку +=  —  это операция in-place (на месте), время на создание нового объекта будет сэкономлено по сравнению с операцией +.

Такая функция на первый взгляд кажется странной. Но она помогает значительно ускорить работу программ, особенно в сложных сценариях работы со строками.

7. Три точки

Если вам потребуется заполнитель для Python-функции, вы, скорее всего, воспользуетесь ключевым словом pass:

def my_func():
pass

Но есть и другой способ сделать это:

def my_func():
...

Три точки, или многоточие (Ellipsis), тоже могут быть заполнителем.

История, стоящая за этим синтаксическим сахаром, довольно интересна. Гвидо ван Россум, создатель Python, сделал многоточие синтаксически корректным, поскольку “некоторые считали, что это было бы круто”.

Кроме того, многоточие используется для подсказки типов в Python.

Например, кортежи произвольной длины, содержащие однородные данные, можно выразить так: Tuple[int, ...].

8. Оператор “with” 

Ни в одном другом языке программирования нет такого оператора, поэтому он удивляет начинающих в Python.

with open("test.txt",'w') as f:
f.write("Yang is writing!")

Оператор “with”  —  это просто синтаксический сахар для управления контекстом. Используя его, вы можете не прописывать явно функцию close() для закрытия файла, поскольку он будет закрыт автоматически после использования.

Код выше приведенного примера может выглядеть следующим образом:

f = open("test.txt",'w')
try:
f.write("Yang is writing!")
except Exception as e:
print(f"An error occurred while handling the file: {e}")
finally:
# Всегда закрывать файл после использования
f.close()

Какой из фрагментов смотрится лучше?

Оператор “with”  —  это особая фишка Python.

9. Префиксные операторы * и **

При чтении проектов Python с открытым исходным кодом вам, вероятно, неоднократно встречался следующий стиль определения функции:

def func(*args, **kwargs):
pass

Что означают эти странные звездочки?

При определении Python-функции можно определить параметр, снабженный звездочками, чтобы собрать неограниченное количество аргументов.

  • Параметр с префиксом * может собрать любое количество позиционных аргументов в tuple.
  • Параметр с префиксом в виде двойной * может собрать любое количество аргументов ключевых слов в dict.

Кроме того, в некоторых случаях использование звездочек в Python делает код более элегантным.

Например, с помощью звездочек можно производить распаковку итерируемых переменных:

A = [1, 2, 3]
B = (4, 5, 6)
C = {7, 8, 9}
L = [*A, *B, *C]
print(L)
# [1, 2, 3, 4, 5, 6, 8, 9, 7]

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Yang Zhou: 9 Weird Python Features and How To Explain Them

Предыдущая статьяКак правильно объявлять API устаревшими и не сломать пользователям код
Следующая статьяТоп-10 библиотек React для создания высокопроизводительных веб-приложений в 2023 году