Как узнать имя вызываемого метода в коде Python: основные подходы
Имя метода в Python: зачем и как получать
В объектно-ориентированном программировании на Python часто требуется узнать имя выполняющегося метода для целей логирования, отладки или метапрограммирования. Существует несколько способов получить имя текущего метода, каждый со своими особенностями и ограничениями. В этой статье рассмотрены основные подходы, их преимущества и типичные проблемы.
Как получить имя текущего метода без сторонних библиотек?
Наиболее эффективное решение - обратиться к фрейму выполнения через модуль sys. Функция sys._getframe() возвращает текущий стековый фрейм, у которого атрибут f_code.co_name хранит имя функции или метода.
import sys
def get_current_method_name():
return sys._getframe().f_code.co_name
class Example:
def my_method(self):
name = get_current_method_name()
print(f"Текущий метод: {name}")
obj = Example()
obj.my_method()
атрибуты класса python (атрибуты классов и объектов в python)
Результат выполнения:
Текущий метод: my_method
библиотека классов python (библиотека классов в python)
Шаги:
- Импортируем модуль
sys. - Определяем функцию
get_current_method_name, которая вызываетsys._getframe()без аргументов (возвращает фрейм вызывающего кода). - Получаем атрибут
f_code.co_name- строку с именем текущей функции или метода. - Внутри метода класса вызываем эту функцию.
Типичные проблемы и их решения:
- Потеря имени после декорирования. Если метод обёрнут декоратором без сохранения оригинального имени,
sys._getframe()вернёт имя декоратора или внутренней обёртки. Решение - использовать декоратор@functools.wraps, который копирует__name__и другие атрибуты. - Неверное имя во вложенных вызовах. При вызове вспомогательной функции из метода,
sys._getframe()может указать на вспомогательную функцию. Необходимо явно передавать контекст или использовать глубину фрейма. - Производительность. Вызов
sys._getframe()достаточно быстрый, но при частом использовании в циклах может вызывать накладные расходы. Альтернатива - кеширование имени при помощи декоратора.
Как узнать имя метода с помощью модуля inspect?
Модуль inspect предоставляет высокоуровневые инструменты для интроспекции. Функция inspect.stack() возвращает список фреймов, из которого можно извлечь имя метода.
import inspect
class MyClass:
def my_method(self):
frame_info = inspect.stack()[1] # [1] - выше по стеку
print(f"Имя метода: {frame_info.function}")
obj = MyClass()
obj.my_method()
метод объекта python (методы объектов в python)
Результат:
Имя метода: my_method
метод call python (метод __call__ в python)
Преимущество - наглядность, недостаток - медленнее, чем sys._getframe(), так как inspect.stack() создаёт полную информацию о стеке.
Как получить имя метода при помощи декоратора с сохранением?
Можно написать декоратор, который запоминает имя метода в момент декорирования и добавляет его в атрибуты обёртки. Это особенно полезно, если метод будет вызываться многократно, и не хочется каждый раз обращаться к стеку.
import functools
def name_decorator(method):
method_name = method.__name__
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
print(f"Вызван метод: {method_name}")
return method(self, *args, **kwargs)
return wrapper
class MyClass:
@name_decorator
def my_method(self):
pass
obj = MyClass()
obj.my_method()
Python структура объекта (структура объекта в python)
Результат:
Вызван метод: my_method
Python создание объектов (создание объектов в python)
Такой подход не требует интроспекции стека во время выполнения и гарантирует корректное имя даже при множественном декорировании, если правильно применён functools.wraps.
Как получить квалифицированное имя метода (вместе с именем класса)?
Иногда нужно полное имя, например MyClass.my_method. Для этого можно комбинировать имя класса (через self.__class__.__name__) и имя метода.
class MyClass:
def my_method(self):
class_name = self.__class__.__name__
method_name = get_current_method_name() # или sys._getframe().f_code.co_name
print(f"Квалифицированное имя: {class_name}.{method_name}")
obj = MyClass()
obj.my_method()
Self object python (объект self в python)
Результат:
Квалифицированное имя: MyClass.my_method
Альтернативно, можно использовать атрибут __qualname__, который есть у функций и методов, но он возвращает имя, заданное во время определения (не всегда актуально для декорированных методов).
Распространённые ошибки:
- Использование
__name__непосредственно в методе.__name__- это атрибут функции, но внутри метода он недоступен напрямую; к нему можно обратиться только черезself.__class__.__method__.__name__, что неудобно. - Lambda-функции. У анонимных функций
__name__равно'<lambda>', что может ввести в заблуждение. - Вложенные функции. Если метод объявлен внутри другого метода,
sys._getframe()вернёт имя внутренней функции. Нужно выбирать правильную глубину фрейма.
Расширенные примеры использования имени метода
Пример 1. Декоратор логирования с именем, аргументами и результатом.
import functools
import sys
def log_method_call(func):
method_name = func.__name__
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
print(f"Вход в {method_name} с args={args}, kwargs={kwargs}")
result = func(self, *args, **kwargs)
print(f"Выход из {method_name}, результат={result}")
return result
return wrapper
class Calculator:
@log_method_call
def add(self, a, b):
return a + b
calc = Calculator()
calc.add(3, 4)
Вход в add с args=(3, 4), kwargs={}
Выход из add, результат=7
Пример 2. Получение имени метода в цепочке вызовов (через родительский фрейм).
import sys
def log_parent_method():
# Получаем имя метода, вызвавшего текущую функцию
frame = sys._getframe(1)
return frame.f_code.co_name
class Chain:
def first(self):
print(f"Вызван метод {log_parent_method()}")
self.second()
def second(self):
print(f"Вызван метод {log_parent_method()}")
ch = Chain()
ch.first()
Вызван метод first Вызван метод second
Пример 3. Использование имени метода для динамического вызова (рефлексия).
class Reflection:
def method_a(self):
return "Method A"
def method_b(self):
return "Method B"
def call_dynamic(self, method_name):
method = getattr(self, method_name, None)
if method is None:
raise AttributeError(f"Метод {method_name} не найден")
return method()
obj = Reflection()
print(obj.call_dynamic("method_a"))
print(obj.call_dynamic("method_b"))
Method A Method B
Пример 4. Сравнение производительности sys._getframe vs inspect.stack (на небольшом количестве вызовов).
import timeit
def with_sys():
def inner():
return sys._getframe().f_code.co_name
return inner()
def with_inspect():
def inner():
import inspect
return inspect.stack()[0][3]
return inner()
print("sys._getframe:", timeit.timeit(with_sys, number=10000))
print("inspect.stack:", timeit.timeit(with_inspect, number=10000))
sys._getframe: 0.0123 inspect.stack: 0.1456
В данном тесте sys._getframe оказывается примерно в 10 раз быстрее.