Почему аргумент decorator не подчищается?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблема с сборкой мусора и аргументами decorator'а
Аргумент decorator не подчищается сборщиком мусора из-за циклических ссылок между функцией-обёрткой и исходной функцией. Python'овский GC не может автоматически разрешить такие циклы в некоторых случаях.
Как это происходит
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet(name):
return f"Hello, {name}"
Здесь создаётся циклическая ссылка:
wrapperудерживает ссылку наfuncчерез замыкание- Объект функции
greetудерживает ссылку наwrapperкак свой__wrapped__атрибут - Если где-то есть ещё одна ссылка на
greet, возникает цикл
Почему GC их не удаляет
Сборщик мусора Python (CPython) использует reference counting как первый уровень очистки. Когда счётчик ссылок падает до нуля, объект удаляется немедленно. Но если есть циклические ссылки, счётчик никогда не будет нулевым.
def create_decorator():
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.__wrapped__ = func # Циклическая ссылка
return wrapper
return decorator
decorator_inst = create_decorator()
@decorator_inst
def func1():
pass
# func1 → wrapper → func
# func → wrapper.__wrapped__ → func (цикл!)
Решения
1. Используй functools.wraps
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
functools.wraps правильно обрабатывает метаданные и не создаёт лишних циклических ссылок.
2. Явная очистка при удалении
class Decorator:
def __init__(self, func):
self.func = func
self.wrapper = self._create_wrapper()
def _create_wrapper(self):
def wrapper(*args, **kwargs):
return self.func(*args, **kwargs)
return wrapper
def __del__(self):
# Явно разрушаем ссылки
self.func = None
self.wrapper = None
3. Используй weakref для слабых ссылок
import weakref
import functools
def my_decorator(func):
# Слабая ссылка на функцию
func_ref = weakref.ref(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
f = func_ref()
if f is None:
raise RuntimeError("Original function was garbage collected")
return f(*args, **kwargs)
return wrapper
4. Отключи циклический мусор вручную
import gc
# Для критичных операций, где нужна предсказуемость
gc.disable()
try:
# Твой код
pass
finally:
gc.enable()
Практический пример: Когда это критично
Это особенно важно при:
- Работе с большим количеством временных функций (генерирование динамических функций)
- Долгоживущих приложениях (серверы, демоны), где утечки памяти накапливаются
- Встраивании Python в другие системы
# Проблема: утечка памяти при создании множества декораторов
for i in range(1000000):
@my_decorator
def temp_func():
return i
# temp_func удаляется, но память не освобождается сразу
Вывод
Используй functools.wraps — это стандарт и решает большинство проблем. Если нужна более агрессивная очистка, рассмотри слабые ссылки или явную очистку в __del__. Помни, что в CPython reference counting обычно справляется с циклами при удалении переменных, но в других реализациях (PyPy, Jython) поведение может отличаться.