Python3. Декорирование методов внутри класса
Есть класс который содержит большое количество методов для работы с API (больше сотни), чтобы не городить для каждого метода одинаковое логирование, хочу обернуть все методы декоратором с логированием результатов выполнения метода.
UPDATE: Класс содержит методы API, но так же в классе есть и служебные методы. Обернуть надо только все методы API.
main.py
import logging
logger = logging.getLogger("* Test *")
class Test:
def __init__(self):
self.api = None
def __test_logging(api_method):
def func(*args: str, **kwargs):
resp = api_method(*args, **kwargs)
if resp:
logging.info("{} - Success".format(args))
elif resp is None:
logging.warning("{} - Not Found".format(args))
else:
logging.error("{} - Unreachable".format(args))
return resp
return func
@ __test_logging
def update_user(self, *method: str, **param: dict):
resp = None # api.update_user(**param)
return resp
@ __test_logging
def delete_user(self, *method: str, **param: dict):
resp = False # api.delete_user(**param)
return resp
if __name__ == "__main__":
test = Test()
user = {"user_id": "u000001"}
result = test.update_user(*"Update User", **user)
print(result)
И вроде бы работает, но крайне криво. Подскажите, как правильно реализовать идею.
WARNING:root:(<__main__.Test object at 0x039F5310>, 'U', 'p', 'd', 'a', 't', 'e', ' ', 'U', 's', 'e', 'r') - Not Found
P.S. Конфигурация logging находится в отдельном файле.
Ответы (3 шт):
Советую эту утилиту, более древний подход - написать декоратор для класса, в котором через inspect.getmembers(cls, inspect.ismethod) определять всем методам декоратор. Более детальный пример старых методов тут
Рабочий вариант декорирования отдельных методов в классе
class Test:
def __init__(self):
self.api = None
def __test_logging(api_method):
def func(self, args: str, **kwargs): # рабочий вариант
resp = api_method(self, args, **kwargs) # рабочий вариант
if resp:
print("{} method - Success".format(args))
else:
print("{} method - Not Found".format(args))
return resp
return func
@ __test_logging
def update_user(self, *method: str, **param: dict):
resp = True # api.update_user(**param)
return resp
Если аргументы в функции api просто передаются "как есть", то перечислять сотню методов, конечно же, незачем, вот, для примера, "апи" над обычным словарем
class ApiFacade(object):
def __init__(self, api):
self.api = api
class ApiWrapper:
"""Прокси-объект стандартной обработки вызовов api """
def __init__(self, fun):
self.fun = fun
def __call__(self, *args, **kwargs):
print('===>', self.fun.__name__, args, kwargs)
result = self.fun(*args, **kwargs)
# Тут у меня стоял вброс исключения при ненулевом коде возврата,
# поскольку api был в стиле C
print(self.fun.__name__, '===>', result)
return result
def __getattr__(self, item, *args, **kwargs):
"""Перенаправление "неизвестных" функций с постфиксом _api на
объект api, можно и просто все неизвестные функции отправить
на вызов api, но с постфиксом, кажется, понятнее """
postfix = "_api"
fn = item[:-len(postfix)]
if item.endswith(postfix):
return ApiFacade.ApiWrapper(self.api.__getattribute__(fn))
raise AttributeError("ApiFacade object has no attribute '%s'" % item)
def foo(self, s):
print("foo:", s)
if __name__ == '__main__':
d = dict(a=1, b=2)
f = ApiFacade(d)
f.foo('qq')
f.get_api('a')
f.keys_api()
f.__len___api()