← Назад к вопросам

В чем разница между слабыми и сильными ссылками в 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