Объекты, память и управляющие конструкции в Python
В этой главе мы углубимся в то, как Python работает с памятью, что такое изменяемые и неизменяемые объекты, и как эффективно использовать условные конструкции и циклы. Мы также затронем некоторые неочевидные аспекты языка, которые помогут вам писать более качественный код.
Объекты в памяти
В Python всё является объектом. Каждый объект имеет:
- Тип (например,
int,str,list), - Значение (например,
42,"hello"), - Уникальный идентификатор в памяти, который можно получить с помощью функции
id().
Пример:
s = "hello"
print(id(s)) # Выведет что-то вроде 4390213040
Ссылки, а не копирования
Когда вы присваиваете переменную другой переменной, вы создаёте ссылку на тот же объект, а не копируете его:
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] – изменился и a!
Сравнение: is vs ==
isпроверяет, ссылаются ли две переменные на один и тот же объект.==проверяет, равны ли значения объектов.
Пример:
x = [1, 2]
y = [1, 2]
print(x == y) # True
print(x is y) # False
Неочевидное поведение: кэширование целых чисел
Python кэширует небольшие целые числа (от -5 до 256). Поэтому:
a = 256
b = 256
print(a is b) # True
c = 257
d = 257
print(c is d) # False (вне диапазона кэширования)
💡 Золотое правило: всегда используйте
==для сравнения значений, аis— только для проверки наNone.
Изменяемые и неизменяемые объекты
Неизменяемые (immutable)
int,float,str,tuple,bytes,bool.- Нельзя изменить после создания. Любая операция создаёт новый объект.
Пример:
s = "hello"
print(id(s))
s += "!"
print(id(s)) # ID изменился – это новый объект!
Изменяемые (mutable)
list,dict,set.- Можно изменять без создания нового объекта.
Пример:
lst = [1, 2]
print(id(lst))
lst.append(3)
print(id(lst)) # ID остался прежним
Неочевидный нюанс: кортежи с изменяемыми элементами
Кортеж неизменяем, но если он содержит изменяемые объекты, их можно менять:
t = ([1, 2], [3, 4])
t[0].append(3)
print(t) # ([1, 2, 3], [3, 4]) – это допустимо!
Строки и байты
Строки (str)
- Предназначены для текста (Unicode).
- Неизменяемы.
Байты (bytes)
- "Сырые" данные (числа от 0 до 255).
- Тоже неизменяемы.
Пример преобразования:
text = "привет"
encoded = text.encode('utf-8') # str -> bytes
decoded = encoded.decode('utf-8') # bytes -> str
Условные конструкции
Базовый синтаксис
temperature = 15
if temperature > 20:
print("Наденьте футболку")
else:
print("Лучше взять куртку")
Элегантные проверки
Используйте "истинность" объектов:
name = input("Введите имя: ")
if name: # False, если строка пустая
print(f"Привет, {name}!")
else:
print("Привет, незнакомец!")
Современный match (Python 3.10+)
Аналог switch из других языков:
http_status = 404
match http_status:
case 200 | 201:
print("Успех")
case 401 | 403 | 404:
print("Ошибка клиента")
case 500 | 503:
print("Ошибка сервера")
case _:
print("Неизвестный статус")
💡 Совет: используйте
match, когда сравниваете одну переменную с несколькими значениями. Для сложных условий оставайтесь сif/elif/else.
Циклы
while
i = 1
while i < 1000:
print(i)
i *= 2
for и range
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(1, 10, 2): # 1, 3, 5, 7, 9
print(i)
enumerate для получения индекса
for idx, char in enumerate("abc"):
print(f"Индекс: {idx}, Символ: {char}")
Неочевидный трюк: else в циклах
Блок else выполняется, если цикл завершился естественно (без break):
for i in range(5):
if i == 10:
break
else:
print("Цикл завершился без break")
Производительность при работе со строками
Помните задачу сборки строки по частям?
Неэффективный способ:
result = ""
for _ in range(100000):
result += "a" # Создаёт новый объект на каждой итерации!
Эффективный способ:
parts = []
for _ in range(100000):
parts.append("a")
result = "".join(parts) # Быстрое объединение
💡 Совет: для частых операций конкатенации используйте
list+join()для строк илиbytearrayдля байтов.
Сборка мусора
Python использует двухуровневую систему управления памятью:
-
Подсчёт ссылок:
- Быстрый и предсказуемый.
- Не справляется с циклическими ссылками.
-
Циклический сборщик мусора (Generational GC):
- Находит и удаляет "острова" объектов с циклическими ссылками.
- Работает периодически.
Пример циклической ссылки:
a = []
b = [a]
a.append(b) # Теперь a и b ссылаются друг на друга
del a, b # Объекты недостижимы, но ссылки остались
Сборщик мусора найдёт и удалит такие объекты.
Неочевидные особенности Python
1. Изменяемые аргументы по умолчанию
Опасно использовать изменяемые объекты как значения по умолчанию:
def append_to(element, target=[]): # Не делайте так!
target.append(element)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] – тот же список!
Правильный подход:
def append_to(element, target=None):
if target is None:
target = []
target.append(element)
return target
2. Ленивые логические операторы
and и or вычисляются лениво:
def expensive_call():
print("Вызов выполнен!")
return True
# expensive_call() не будет вызвана, т.к. первое условие False
if False and expensive_call():
pass
3. Атрибуты функций
Функции в Python — это тоже объекты, и им можно добавлять атрибуты:
def my_func():
pass
my_func.custom_attr = 42
print(my_func.custom_attr) # 42
Заключение
Понимание работы с памятью и объектами в Python — ключ к написанию эффективного и надёжного кода. Помните:
- Используйте
isтолько для сравнения сNone - Для частой конкатенации строк применяйте
join() - Избегайте изменяемых аргументов по умолчанию
- Знайте разницу между изменяемыми и неизменяемыми объектами
Полезные материалы:
- Python Documentation
- Ned Batchelder - Facts and Myths about Python Names and Values
- Real Python - Python Memory Management
Эта глава даёт прочную основу для понимания того, как Python работает "под капотом", что необходимо для написания эффективных и надёжных программ.