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

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

2.8 Senior🔥 211 комментариев
#Git и VCS#Базы данных (NoSQL)

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

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

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

Ссылка в Python

Ссылка (Reference) в Python — это адрес объекта в памяти, на который указывает переменная. В отличие от некоторых других языков программирования (как C или C++), в Python нет явных указателей, но система ссылок является центральной для понимания того, как работает память в Python. Каждая переменная содержит ссылку на объект, а не сам объект.

Основная концепция: всё — это объект

# В Python всё — это объект с ссылкой
x = 42
y = x  # y содержит ссылку на тот же объект, что и x

print(id(x))  # Адрес объекта в памяти: 140730315935872
print(id(y))  # Тот же адрес: 140730315935872
print(x is y)  # True — одна и та же ссылка

Как работают ссылки

1. Присваивание создаёт новую ссылку, а не копирует объект:

# Числа (immutable — неизменяемые)
a = 100
b = a  # Обе переменные указывают на тот же объект

print(id(a) == id(b))  # True — ссылки одинаковые

# После присваивания нового значения
a = 200  # Создаётся новый объект, a указывает на него
print(id(a) == id(b))  # False — разные объекты
print(b)  # 100 — b остаётся прежней ссылкой

2. Изменяемые объекты (mutable) — опасность ссылок:

# Список — изменяемый объект
list1 = [1, 2, 3]
list2 = list1  # list2 — ссылка на ТОТ ЖЕ объект

print(list1 is list2)  # True

list2.append(4)  # Изменяем через ссылку list2
print(list1)  # [1, 2, 3, 4] — list1 тоже изменился!

# Это потому что обе переменные указывают на один объект в памяти

Глубокие и поверхностные копии

Поверхностная копия (shallow copy) — копирует только ссылки:

original = [[1, 2], [3, 4]]
shallow = original.copy()  # Или list(original)

print(original is shallow)  # False — разные списки
print(original[0] is shallow[0])  # True — но внутренние списки — одни и те же!

shallow[0].append(99)  # Изменяем вложенный список
print(original)  # [[1, 2, 99], [3, 4]] — оригинал тоже изменился!

Глубокая копия (deep copy) — копирует всю структуру:

import copy

original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)  # Полная копия

print(original is deep)  # False
print(original[0] is deep[0])  # False — разные вложенные списки

deep[0].append(99)  # Изменяем копию
print(original)  # [[1, 2], [3, 4]] — оригинал не изменился!

Ссылки в функциях

Передача по ссылке:

def modify_list(lst):
    """Функция получает ССЫЛКУ на объект"""
    lst.append(999)
    print(f"Внутри функции: {lst}")

my_list = [1, 2, 3]
modify_list(my_list)  # Передаём ссылку
print(f"После функции: {my_list}")  # [1, 2, 3, 999] — изменился!

# Это потому что функция получила ссылку на тот же объект

Но переприсваивание внутри функции не влияет на оригинал:

def reassign_list(lst):
    lst = [999, 999, 999]  # Локальная переменная указывает на новый объект
    print(f"Внутри функции: {lst}")

my_list = [1, 2, 3]
reassign_list(my_list)
print(f"После функции: {my_list}")  # [1, 2, 3] — не изменился!

# Потому что переприсваивание внутри функции создаёт локальную ссылку

Счётчик ссылок (Reference Counting)

Python отслеживает, сколько ссылок указывают на объект:

import sys

# Функция для проверки количества ссылок
a = [1, 2, 3]
print(sys.getrefcount(a))  # 2 (переменная a + параметр getrefcount)

b = a  # Добавляем ещё одну ссылку
print(sys.getrefcount(a))  # 3 (a, b, и параметр getrefcount)

c = a  # Ещё одна ссылка
print(sys.getrefcount(a))  # 4

del b  # Удаляем ссылку
print(sys.getrefcount(a))  # 3 (b больше не указывает)

Автоматическая очистка памяти (Garbage Collection):

# Когда счётчик ссылок падает до нуля, объект удаляется
class MyObject:
    def __del__(self):
        print("Объект удалён из памяти")

obj = MyObject()
print("Создан объект")

del obj  # Удаляем ссылку
# Выведет: "Объект удалён из памяти"

Циклические ссылки и утечки памяти

Циклические ссылки могут заблокировать очистку памяти:

class Node:
    def __init__(self):
        self.next = None

a = Node()
b = Node()

a.next = b  # a указывает на b
b.next = a  # b указывает на a — циклическая ссылка!

del a
del b

# Оба объекта всё ещё в памяти!
# Счётчик ссылок: a.next и b.next = 1 (нельзя удалить)

Решение: использование weakref (слабые ссылки):

import weakref

class Node:
    def __init__(self):
        self.next = None

a = Node()
b = Node()

a.next = b  # Обычная ссылка
b.next = weakref.ref(a)  # Слабая ссылка (не блокирует удаление)

del a
del b

# Оба объекта удалены (слабая ссылка не препятствует очистке)

Практические примеры

1. Список функций и ссылки:

functions = []
for i in range(3):
    def func():
        return i  # ОПАСНО: замыкание (closure) на переменную i!
    functions.append(func)

print([f() for f in functions])  # [2, 2, 2] — не [0, 1, 2]!
# Все функции ссылаются на ту же переменную i

# Решение: создать локальную переменную
functions = []
for i in range(3):
    def func(i=i):  # Захватить текущее значение i
        return i
    functions.append(func)

print([f() for f in functions])  # [0, 1, 2] — правильно!

2. Глобальные переменные и ссылки:

global_list = [1, 2, 3]

def modify_global():
    global global_list  # Получить доступ к глобальной ссылке
    global_list.append(4)  # Изменить объект
    global_list = [999]  # Переприсвоить ссылку

modify_global()
print(global_list)  # [999] — переприсваивание сработало

3. Кэширование и ссылки:

from functools import lru_cache

@lru_cache(maxsize=None)
def expensive_function(x):
    """Результаты кэшируются (хранятся ссылки)"""
    return x ** 2

result = expensive_function(5)
# Кэш содержит ссылку на результат

expensive_function.cache_clear()  # Удалить все ссылки

Отладка ссылок

Использование sys.getrefcount() для отладки:

import sys

def debug_references(obj, name):
    """Функция для проверки ссылок"""
    ref_count = sys.getrefcount(obj) - 1  # Минус параметр функции
    print(f"{name}: {ref_count} ссылок")
    print(f"ID объекта: {id(obj)}")
    print(f"Тип: {type(obj)}")

my_list = [1, 2, 3]
debug_references(my_list, "my_list")

other = my_list
debug_references(my_list, "my_list")

Заключение

Ссылки в Python — это фундаментальный концепт, определяющий как работают переменные и память. Ключевые моменты:

  1. Переменные содержат ссылки на объекты, а не сами объекты
  2. Присваивание создаёт новую ссылку, не копирует объект
  3. Изменяемые объекты могут быть изменены через любую ссылку
  4. Используй copy.deepcopy() для полной копии
  5. Понимание ссылок критично для предотвращения баков с неожиданными изменениями
  6. Циклические ссылки могут привести к утечкам памяти