← Назад к вопросам
В чем разница между слабыми и сильными ссылками в Python?
2.8 Senior🔥 71 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Слабые и сильные ссылки в Python (Weak vs Strong References)
Это механизм управления памятью. Понимание важно для борьбы с утечками памяти и циклическими ссылками.
1. Сильные ссылки (Strong References) — обычные переменные
Сильная ссылка — это обычное присваивание переменной. Она увеличивает счётчик ссылок объекта.
import gc
class Person:
def __init__(self, name):
self.name = name
def __del__(self):
print(f"{self.name} was deleted")
alice = Person("Alice") # Сильная ссылка, ref_count = 1
bob = alice # Сильная ссылка, ref_count = 2
del alice # ref_count = 1, объект НЕ удалиться
print(f"{bob.name}") # Alice (объект ещё живой)
del bob # ref_count = 0, объект удалиться
# Выведет: "Alice was deleted"
gc.collect() # Можно явно запустить garbage collector
Как это работает:
import sys
class Person:
def __init__(self, name):
self.name = name
alice = Person("Alice")
print(sys.getrefcount(alice)) # 2 (сама переменная + параметр getrefcount)
bob = alice
print(sys.getrefcount(alice)) # 3 (alice, bob + getrefcount)
c = alice
print(sys.getrefcount(alice)) # 4
del bob
print(sys.getrefcount(alice)) # 3
2. Проблема: циклические ссылки
Утечка памяти при циклических ссылках:
class Node:
def __init__(self, value):
self.value = value
self.next = None
def __del__(self):
print(f"Node {self.value} deleted")
# Создаём циклическую ссылку
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node1.next = node2
node2.next = node3
node3.next = node1 # ЦИКЛ!
del node1 # Удаляем переменную
del node2
del node3
# Ничего не выведется! Объекты живы потому что друг друга держат
# Это утечка памяти
3. Слабые ссылки (Weak References) — решение
Слабая ссылка — это ссылка, которая НЕ увеличивает счётчик ссылок. Объект может быть удален, слабая ссылка станет None.
import weakref
class Node:
def __init__(self, value):
self.value = value
self.next = None
def __del__(self):
print(f"Node {self.value} deleted")
# Используем слабые ссылки для разрыва цикла
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node1.next = node2
node2.next = node3
node3.next = weakref.ref(node1) # СЛАБАЯ ссылка! Не держит живым
del node1 # ref_count = 1, удалиться!
print(f"Node 1 deleted") # Выведет: "Node 1 deleted"
del node2 # Выведет: "Node 2 deleted"
del node3 # Выведет: "Node 3 deleted"
4. Как работать со слабыми ссылками
import weakref
class Person:
def __init__(self, name):
self.name = name
alice = Person("Alice")
print(f"Strong ref: {alice}") # <Person object>
# Создаём слабую ссылку
weak_alice = weakref.ref(alice)
print(f"Weak ref: {weak_alice}") # <weakref at 0x...>
# Восстанавливаем объект из слабой ссылки
obj = weak_alice()
print(f"Restored: {obj.name}") # Alice
# Удаляем сильную ссылку
del alice
# Слабая ссылка теперь мёртва
obj = weak_alice()
print(f"After deletion: {obj}") # None
5. WeakKeyDictionary и WeakValueDictionary
Проблема: кэш удерживает объекты:
class User:
def __init__(self, id, name):
self.id = id
self.name = name
def __del__(self):
print(f"User {self.id} deleted")
cache = {} # Обычный словарь
user1 = User(1, "Alice")
cache[1] = user1 # Кэш держит объект живым
del user1 # Объект НЕ удалиться, кэш держит сильную ссылку
print(f"Cache: {cache[1].name}") # Alice
# НЕ выведет: "User 1 deleted"
Решение — WeakValueDictionary:
import weakref
class User:
def __init__(self, id, name):
self.id = id
self.name = name
def __del__(self):
print(f"User {self.id} deleted")
cache = weakref.WeakValueDictionary() # Слабые ссылки!
user1 = User(1, "Alice")
cache[1] = user1
print(f"Cache size: {len(cache)}") # 1
del user1 # Объект удалиться, кэш не помешает
print(f"Cache size: {len(cache)}") # 0 (объект удалён)
# Выведет: "User 1 deleted"
WeakKeyDictionary — слабые ключи:
import weakref
class Database:
pass
db1 = Database()
db2 = Database()
# Словарь с слабыми ключами
metadata = weakref.WeakKeyDictionary()
metadata[db1] = "Database 1 metadata"
metadata[db2] = "Database 2 metadata"
print(len(metadata)) # 2
del db1 # Ключ удалиться, запись в словаре тоже
print(len(metadata)) # 1
6. Практический пример: Observer pattern
НЕПРАВИЛЬНО — утечка памяти:
class EventEmitter:
def __init__(self):
self.listeners = [] # Сильные ссылки на слушателей!
def subscribe(self, callback):
self.listeners.append(callback)
def emit(self, event):
for callback in self.listeners:
callback(event)
class MyListener:
def __init__(self, name):
self.name = name
def on_event(self, event):
print(f"{self.name} got event: {event}")
def __del__(self):
print(f"{self.name} deleted")
emitter = EventEmitter()
listener = MyListener("Listener1")
emitter.subscribe(listener.on_event)
del listener # НЕ удалиться, emitter держит ссылку на метод
# НЕ выведет: "Listener1 deleted"
ПРАВИЛЬНО — используем слабые ссылки:
import weakref
class EventEmitter:
def __init__(self):
self.listeners = []
def subscribe(self, callback):
# Оборачиваем в слабую ссылку
weak_callback = weakref.ref(callback.__self__, self._remove_listener)
self.listeners.append((weak_callback, callback.__func__))
def _remove_listener(self, weak_ref):
# Callback вызовется когда объект удалится
self.listeners = [
(wr, func) for wr, func in self.listeners if wr() is not None
]
def emit(self, event):
for weak_callback, func in self.listeners:
obj = weak_callback()
if obj is not None:
func(obj, event)
class MyListener:
def __init__(self, name):
self.name = name
def on_event(self, event):
print(f"{self.name} got event: {event}")
def __del__(self):
print(f"{self.name} deleted")
emitter = EventEmitter()
listener = MyListener("Listener1")
emitter.subscribe(listener.on_event)
print(len(emitter.listeners)) # 1
del listener # Удалиться!
print(len(emitter.listeners)) # 0 (автоматически очистилось)
# Выведет: "Listener1 deleted"
7. Когда использовать слабые ссылки
Используй слабые ссылки:
- Кэши — не хочешь держать объекты живыми
- Observer/Listener паттерны — слушатели могут быть удалены
- Обратные ссылки — parent → child (сильная), child → parent (слабая)
- Регистрации — объект регистрируется, но можно удалить
НЕ используй слабые ссылки:
- Основные данные — должны быть в памяти
- Критичные ссылки — которые должны держать объект живым
- При работе с примитивами — int, str, tuple не поддерживают weakref
8. Примитивы и слабые ссылки
import weakref
my_string = "hello"
try:
weak_str = weakref.ref(my_string)
except TypeError as e:
print(f"Ошибка: {e}") # Strings не поддерживают слабые ссылки
my_int = 42
try:
weak_int = weakref.ref(my_int)
except TypeError as e:
print(f"Ошибка: {e}") # Ints не поддерживают
# Только custom классы и некоторые встроенные поддерживают
class MyClass:
pass
obj = MyClass()
weak_obj = weakref.ref(obj) # Работает ✓
9. Таблица сравнения
| Параметр | Сильная ссылка | Слабая ссылка |
|---|---|---|
| Синтаксис | obj = MyClass() | weak = weakref.ref(obj) |
| ref_count | Увеличивает | Не увеличивает |
| Объект живой | ✓ Да | ✗ Может быть удален |
| Использование | obj.method() | obj = weak() потом obj.method() |
| На примитивах | ✓ Работает | ✗ TypeError |
| Memory leak | Возможны циклы | Защита от циклов |
| При удалении | Остаётся сильная ссылка | None, автоматический cleanup |
Итоги
- Сильная ссылка — обычная переменная, держит объект в памяти
- Слабая ссылка — не препятствует сборке мусора (weakref.ref)
- Циклические ссылки — главная причина утечек памяти
- Используй слабые ссылки для кэшей, observers и обратных ссылок
- WeakValueDictionary и WeakKeyDictionary — словари со слабыми ссылками
- Примитивы (str, int, float) не поддерживают weakref