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

Что такое счетчик ссылок?

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 автоматически управлять памятью без необходимости явного выделения и освобождения ресурсов.

Что такое счетчик ссылок? | PrepBro