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

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

2.0 Middle🔥 191 комментариев
#Git и VCS#REST API и HTTP

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Циклическая ссылка

Циклическая ссылка (circular reference) — это ситуация, когда два или более объектов ссылаются друг на друга, образуя цикл. В Python это может привести к утечкам памяти, если сборщик мусора не может разрешить такие циклы, хотя в современных версиях Python существует механизм для их обнаружения и удаления.

Простой пример циклической ссылки

# Циклическая ссылка между двумя объектами
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

# Создаём цикл
node1 = Node(1)
node2 = Node(2)

node1.next = node2
node2.next = node1  # Циклическая ссылка!

print(node1.next.next.next.value)  # Можем бесконечно идти по кругу

Циклические ссылки в структурах данных

# Пример в графах
class GraphNode:
    def __init__(self, value):
        self.value = value
        self.neighbors = []

# Граф с циклом
node_a = GraphNode(A)
node_b = GraphNode(B)
node_c = GraphNode(C)

node_a.neighbors = [node_b]
node_b.neighbors = [node_c]
node_c.neighbors = [node_a]  # Цикл: A -> B -> C -> A

# Пример: двусвязный список
class DoublyLinkedNode:
    def __init__(self, value):
        self.value = value
        self.next = None
        self.prev = None

# Создание цикла в двусвязном списке
head = DoublyLinkedNode(1)
second = DoublyLinkedNode(2)
tail = DoublyLinkedNode(3)

head.next = second
second.prev = head
second.next = tail
tail.prev = second
tail.next = head  # Циклический список
head.prev = tail

Проблемы с циклическими ссылками

1. Бесконечные циклы при обходе

def traverse_list(node):
    """Наивный обход — зависнет на циклическом списке!"""
    current = node
    while current is not None:
        print(current.value)
        current = current.next  # Никогда не будет None

# Решение: отслеживаем посещённые узлы
def traverse_list_safe(node):
    visited = set()
    current = node
    while current is not None and id(current) not in visited:
        print(current.value)
        visited.add(id(current))
        current = current.next

traverse_list_safe(node1)  # Выведет: 1, 2

2. Утечки памяти в Python

import sys
import gc

class Container:
    pass

# Создание циклической ссылки
obj1 = Container()
obj2 = Container()

obj1.ref = obj2
obj2.ref = obj1

print(f"Счётчик ссылок obj1: {sys.getrefcount(obj1)}")  # 3

# Удаляем явные ссылки
del obj1, obj2

# Объекты остались в памяти (кольцевая ссылка)
# Python 3.x автоматически очищает такие циклы через gc
gc.collect()  # Явный вызов сборщика мусора

Обнаружение циклических ссылок

def has_cycle(head):
    """Обнаружение цикла с использованием черепахи и зайца (Floyd cycle detection)"""
    if not head or not head.next:
        return False
    
    slow = head  # Черепаха (каждый шаг)
    fast = head  # Заяц (два шага)
    
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        
        if slow is fast:  # Встретились — есть цикл
            return True
    
    return False

# Тестирование
cyclic_list = Node(1)
cyclic_list.next = Node(2)
cyclic_list.next.next = cyclic_list  # Создаём цикл

print(has_cycle(cyclic_list))  # True

# Нециклический список
acyclic_list = Node(1)
acyclic_list.next = Node(2)
acyclic_list.next.next = Node(3)
acyclic_list.next.next.next = None

print(has_cycle(acyclic_list))  # False

Работа с циклами в Python

# Слабые ссылки для предотвращения циклических ссылок
import weakref

class Parent:
    def __init__(self, name):
        self.name = name
        self.children = []

class Child:
    def __init__(self, name, parent):
        self.name = name
        self.parent = weakref.ref(parent)  # Слабая ссылка
    
    def get_parent(self):
        parent = self.parent()
        return parent.name if parent else "Parent deleted"

parent = Parent("Alice")
child = Child("Bob", parent)
parent.children.append(child)

print(child.get_parent())  # Alice

del parent
print(child.get_parent())  # Parent deleted

Практические советы

  1. Используйте слабые ссылки (weakref) при создании обратных ссылок (parent → child)
  2. Явно разрушайте циклы перед удалением объектов
  3. Избегайте циклических импортов в модулях
  4. Используйте Context Managers для гарантированной очистки ресурсов
  5. Тестируйте утечки памяти с помощью tracemalloc и memory_profiler
class ResourceManager:
    def __init__(self):
        self.resources = []
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        # Гарантированная очистка
        self.resources.clear()
        return False

# Использование
with ResourceManager() as rm:
    rm.resources.append("data")

Понимание циклических ссылок важно для написания надёжного и эффективного кода, особенно при работе со сложными структурами данных и долгоживущими приложениями.