← Назад к вопросам
Что такое счетчик ссылок?
1.7 Middle🔥 131 комментариев
#DevOps и инфраструктура#Django
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Счётчик ссылок (Reference Counting)
Счётчик ссылок (Reference Counting) — это механизм управления памятью, при котором каждый объект в памяти имеет счётчик, указывающий, сколько ссылок на него существует. Когда счётчик достигает нуля (на объект никто не ссылается), объект немедленно удаляется из памяти и память освобождается.
Это основной механизм управления памятью в Python и является простым, но эффективным способом автоматической очистки неиспользуемых объектов.
Как работает счётчик ссылок
Объект: "hello" (некий объект в памяти)
Пошаг:
1. a = "hello" → ref_count = 1
2. b = a → ref_count = 2 (ещё одна ссылка)
3. c = a → ref_count = 3
4. del a → ref_count = 2
5. del b → ref_count = 1
6. del c → ref_count = 0 → объект удалён из памяти
Проверка счётчика ссылок в Python
import sys
obj = [1, 2, 3]
print(sys.getrefcount(obj)) # Обычно 2 (сама ссылка obj + параметр getrefcount)
a = obj
print(sys.getrefcount(obj)) # Теперь 3 (obj, a, параметр getrefcount)
b = obj
print(sys.getrefcount(obj)) # Теперь 4 (obj, a, b, параметр getrefcount)
del a
print(sys.getrefcount(obj)) # Вернулось к 3
del b
print(sys.getrefcount(obj)) # Вернулось к 2
Пример: создание и удаление объекта
import sys
class MyObject:
def __init__(self, name):
self.name = name
def __del__(self):
print(f"Object {self.name} is being deleted")
obj = MyObject("Alice") # ref_count = 1
print(sys.getrefcount(obj)) # 2
ref1 = obj # ref_count = 2
print(sys.getrefcount(obj)) # 3
ref2 = obj # ref_count = 3
print(sys.getrefcount(obj)) # 4
del ref1 # ref_count = 2
print(sys.getrefcount(obj)) # 3
del ref2 # ref_count = 1
print(sys.getrefcount(obj)) # 2
del obj # ref_count = 0 → удаление
# Вывод: Object Alice is being deleted
Циклические ссылки — проблема счётчика ссылок
import sys
import gc
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)
node1.next = node2
node2.next = node1 # Циклическая ссылка!
print(f"node1 ref_count: {sys.getrefcount(node1)}") # 2
print(f"node2 ref_count: {sys.getrefcount(node2)}") # 2
del node1 # node1 умер, но node2 всё ещё ссылается на его содержимое
del node2 # node2 умер, но node1 всё ещё ссылается...
# Оба объекта не удалены! Утечка памяти!
print("Done")
# Для решения:
gc.collect() # Запуск garbage collector
# Вывод:
# Node 1 deleted
# Node 2 deleted
Решение циклических ссылок: Слабые ссылки (Weak References)
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)
node1.next = node2
node2.next = weakref.ref(node1) # Слабая ссылка — не увеличивает ref_count!
print(f"node1 ref_count: {sys.getrefcount(node1)}") # 2
print(f"node2 ref_count: {sys.getrefcount(node2)}") # 3 (нормальная ссылка)
del node1 # node1 удаляется нормально
# Вывод: Node 1 deleted
del node2 # node2 удаляется нормально
# Вывод: Node 2 deleted
Счётчик ссылок в контейнерах
import sys
obj = "test"
print(f"Начальный ref_count: {sys.getrefcount(obj)}") # 2
lst = [obj, obj, obj] # obj в списке 3 раза
print(f"После добавления в список: {sys.getrefcount(obj)}") # 5 (старая ссылка + 3 в списке + параметр)
di = {'a': obj, 'b': obj}
print(f"После добавления в dict: {sys.getrefcount(obj)}") # 7 (старая + 3 в списке + 2 в dict + параметр)
del lst # Удалили список
print(f"После удаления списка: {sys.getrefcount(obj)}") # Уменьшилось
del di # Удалили dict
print(f"После удаления dict: {sys.getrefcount(obj)}") # Вернулось к начальному
Счётчик ссылок vs Garbage Collector
import gc
import sys
# Счётчик ссылок (автоматический)
obj = [1, 2, 3]
ref = obj
del ref # Сразу проверяется ref_count, и если 0 → удаление
# Garbage Collector (для циклических ссылок)
gc.enable() # Обычно включен
class Node:
def __init__(self):
self.ref = None
a = Node()
b = Node()
a.ref = b
b.ref = a # Цикл
del a
del b # Счётчик ссылок НЕ удалит их, так как они ссылаются друг на друга
# Garbage Collector придёт и удалит оба объекта
Производительность: кэширование и счётчик ссылок
import sys
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_computation(n):
return n * 2
result1 = expensive_computation(5)
result2 = expensive_computation(5) # Из кэша
print(f"ref_count of result: {sys.getrefcount(result1)}") # Высокий, т.к. в кэше
# Кэш держит ссылки, поэтому объекты не удаляются из памяти
Контекст-менеджеры и счётчик ссылок
class ResourceManager:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"{self.name} acquired")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"{self.name} released")
# Контекст-менеджер гарантирует очистку
with ResourceManager("Database") as db:
print(f"Using {db.name}")
# Когда выходим из with, __exit__ вызывается
# ref_count уменьшается, и ресурс освобождается
# Вывод:
# Database acquired
# Using Database
# Database released
Утечки памяти: на что обратить внимание
# ПЛОХО: циклические ссылки без слабых ссылок
class TreeNode:
def __init__(self, value):
self.value = value
self.parent = None
self.children = []
def add_child(self, child):
child.parent = self # Циклическая ссылка!
self.children.append(child)
# ХОРОШО: используй слабые ссылки для parent
import weakref
class TreeNodeSafe:
def __init__(self, value):
self.value = value
self.parent = None # Будет слабой ссылкой
self.children = []
def add_child(self, child):
child.parent = weakref.ref(self) # Слабая ссылка
self.children.append(child)
Лучшие практики
- Понимай как работает счётчик ссылок — это основа управления памятью Python
- Избегай циклических ссылок — или используй слабые ссылки
- Используй контекст-менеджеры для управления ресурсами
- Профилируй память если подозреваешь утечку
- Явно удаляй большие объекты если знаешь, что они больше не нужны
- Помни про garbage collector — он справляется с циклами, но лучше избегать их
Счётчик ссылок — это элегантный механизм, позволяющий Python автоматически управлять памятью без необходимости явного выделения и освобождения ресурсов.