Атрибуты функций: хранение данных и настройка поведения
Основные способы управления атрибутами функций
Функции в Python являются полноправными объектами, поэтому им можно присваивать произвольные атрибуты. Это позволяет хранить метаданные, настраивать поведение без создания отдельного класса или глобальных переменных. В данном разделе рассмотрены основные приёмы добавления, чтения и удаления атрибутов, а также типичные ошибки.
Прямое присваивание атрибута
Наиболее простой и быстрый способ – присвоить значение непосредственно через точечную нотацию:
def hello():
print("Привет")
hello.lang = "Russian"
print(hello.lang)атрибуты функции python (атрибуты функции в python)
Russian
Такой код напрямую добавляет запись в словарь __dict__ функции. Метод эффективен и интуитивно понятен. После присваивания атрибут можно читать, изменять или удалять оператором del.
Типичные ошибки и их решения:
- Переопределение встроенных атрибутов – если присвоить значение атрибуту __name__, __doc__ или __code__, интерпретатор не выдаст ошибку, но такое вмешательство способно нарушить работу интроспекции и декораторов. Рекомендуется использовать уникальные имена без двойных подчёркиваний.
- Попытка обратиться к несуществующему атрибуту – вызовет AttributeError. Для безопасного чтения применяют getattr(func, 'attr', default).
- Модификация атрибута во время выполнения – если функция используется в нескольких местах, все они увидят изменение атрибута. Для изолированного состояния рекомендуется замыкание или класс.
Как создать счётчик вызовов без глобальной переменной?
Атрибут функции отлично подходит для хранения числа выполненных вызовов:
def counter():
counter.calls += 1
print(f"Вызов номер {counter.calls}")
counter.calls = 0
counter()
counter()Вызов номер 1 Вызов номер 2
Обратите внимание: инициализация атрибута выполняется вне функции. Если забыть это сделать, при первом вызове возникнет AttributeError. Альтернатива – проверять существование атрибута внутри функции с помощью hasattr.
Проблема: при повторном импорте модуля счётчик сбросится. Для долгоживущих приложений атрибут функции может оказаться недостаточно устойчивым; в таких случаях лучше использовать замыкание или атрибут объекта более высокого уровня.
Как получить все атрибуты функции?
Пользовательские атрибуты хранятся в __dict__ функции. Встроенные атрибуты (например, __name__) в этом словаре не отображаются. Для их просмотра служит функция dir:
def example():
pass
example.custom_attr = 42
print(example.__dict__) # {'custom_attr': 42}
print([name for name in dir(example) if not name.startswith('__')]){'custom_attr': 42}
['custom_attr']При получении атрибутов через dir следует помнить, что список включает также методы, унаследованные от object.
Как добавить атрибут с помощью setattr?
Функция setattr делает код более динамичным, позволяя задавать имя атрибута строкой:
def work():
pass
setattr(work, 'version', 2.0)
print(getattr(work, 'version')) # 2.02.0
Это удобно, когда имя атрибута формируется программно. Для массового копирования атрибутов можно использовать цикл по словарю.
Цели использования атрибутов функций:
- Хранение конфигурационных данных (приоритет, тайм-аут, язык).
- Сбор статистики (количество вызовов, время выполнения).
- Реализация паттерна «функция-диспетчер» с регистрацией обработчиков.
- Передача метаинформации декораторам и фреймворкам.
Практические примеры с атрибутами функций
В этом разделе приведены более сложные сценарии, демонстрирующие гибкость атрибутов функций. Каждый пример сопровождается кодом и выводом.
Декоратор для подсчёта вызовов с сохранением атрибута
Часто требуется обернуть функцию так, чтобы она автоматически подсчитывала количество вызовов. Атрибут calls присваивается обёртке:
def count_calls(func):
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
@count_calls
def say_hello(name):
print(f"Привет, {name}")
say_hello("Анна")
say_hello("Иван")
print(f"Вызовов: {say_hello.calls}")Привет, Анна Привет, Иван Вызовов: 2
Обратите внимание: wrapper возвращается как новая функция, поэтому её атрибут calls изолирован от исходной функции. Если нужно сохранить и другие атрибуты оригинала, используйте functools.wraps.
Кэширование результатов (мемоизация) с помощью атрибута cache
Атрибут может хранить словарь уже вычисленных значений. Это ускоряет повторные вызовы с теми же аргументами:
def factorial(n):
if not hasattr(factorial, 'cache'):
factorial.cache = {}
if n in factorial.cache:
return factorial.cache[n]
if n == 0:
result = 1
else:
result = n * factorial(n - 1)
factorial.cache[n] = result
return result
print(factorial(5))
print(factorial(5))
print(factorial.cache)120
120
{0: 1, 1: 1, 2: 2, 3: 6, 4: 24, 5: 120}Кэш инициализируется при первом вызове. Метод подходит для чистых функций без побочных эффектов.
Установка атрибутов через декоратор для настройки поведения
Декоратор может добавлять функции метаданные, влияющие на логику работы приложения:
def set_priority(priority):
def decorator(func):
func.priority = priority
return func
return decorator
@set_priority(1)
def process_a():
pass
@set_priority(2)
def process_b():
pass
print(process_a.priority)
print(process_b.priority)1 2
В коде, определяющем порядок выполнения, можно отфильтровать функции по значению атрибута priority.
Функция-диспетчер с регистрацией подфункций
Атрибуты позволяют централизованно собирать обработчики событий:
def dispatcher():
pass
dispatcher.handlers = {}
def register(event):
def decorator(func):
dispatcher.handlers[event] = func
return func
return decorator
@register('start')
def start_handler():
print("Старт")
@register('stop')
def stop_handler():
print("Стоп")
print(dispatcher.handlers)
dispatcher.handlers['start'](){'start': , 'stop': }
Старт Атрибут handlers живёт на уровне самого диспетчера, что удобно для расширяемых систем.
Хранение метаинформации с помощью __annotations__
Аннотации типов тоже являются атрибутами функции. Их можно дополнить пользовательскими данными:
def calculate(a: int, b: int) -> int:
return a + b
calculate.info = "Сложение чисел"
print(calculate.__annotations__)
print(calculate.info){'a': int, 'b': int, 'return': int}
Сложение чиселКомбинация встроенных и пользовательских атрибутов даёт полную картину о назначении функции.
Сохранение атрибутов при использовании functools.wraps
Декораторы, возвращающие обёртку, часто теряют атрибуты исходной функции. Функция wraps копирует их автоматически:
from functools import wraps
def add_metadata(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.extra = "metadata"
return wrapper
@add_metadata
def example():
"""Documentation."""
pass
print(example.__name__) # example, не wrapper
print(example.__doc__) # Documentation.
print(example.extra) # metadataexample Documentation. metadata
Без wraps пришлось бы вручную копировать __name__, __doc__ и другие атрибуты.
Динамическое изменение атрибута функции из другого модуля
Атрибуты доступны глобально, если импортировать функцию:
# module_a.py
def task():
print(f"Config: {task.config}")
task.config = "default"
# module_b.py
import module_a
module_a.task.config = "custom"
module_a.task()Такой подход позволяет менять поведение во время исполнения, однако он нарушает инкапсуляцию. Следует применять его осознанно и документировать глобальные атрибуты.