![](/img/trans.png)
[英]Python dynamically add decorator to class' methods by decorating class
[英]Add a decorator to a class add a decorator to class' methods by decorating class
我正在嘗試創建一個可以在 class 上定義的裝飾器,並裝飾其中定義的所有內容。 首先讓我展示一下我已經根據其他 SO 答案獲得的設置:
import inspect
# https://stackoverflow.com/a/18421294/577669
def log(func):
def wrapped(*args, **kwargs):
try:
print("Entering: [%s]" % func)
try:
# https://stackoverflow.com/questions/19227724/check-if-a-function-uses-classmethod
if inspect.ismethod(func) and func.__self__: # class method
return func(*args[1:], **kwargs)
if inspect.isdatadescriptor(func):
return func.fget(args[0])
return func(*args, **kwargs)
except Exception as e:
print('Exception in %s : (%s) %s' % (func, e.__class__.__name__, e))
finally:
print("Exiting: [%s]" % func)
return wrapped
class trace(object):
def __call__(self, cls): # instance, owner):
for name, m in inspect.getmembers(cls, lambda x: inspect.ismethod(x) or inspect.isfunction(x)):
setattr(cls, name, log(m))
for name, m in inspect.getmembers(cls, lambda x: inspect.isdatadescriptor(x)):
setattr(cls, name, property(log(m)))
return cls
@trace()
class Test:
def __init__(self, arg):
self.arg = arg
@staticmethod
def static_method(arg):
return f'static: {arg}'
@classmethod
def class_method(cls, arg):
return f'class: {arg}'
@property
def myprop(self):
return 'myprop'
def normal(self, arg):
return f'normal: {arg}'
if __name__ == '__main__':
test = Test(1)
print(test.arg)
print(test.static_method(2))
print(test.class_method(3))
print(test.myprop)
print(test.normal(4))
從 class 中刪除@trace
裝飾器時,這是 output:
123
static
class
myprop
normal
添加@trace
裝飾器時,我得到這個:
Entering: [<function Test.__init__ at 0x00000170FA9ED558>]
Exiting: [<function Test.__init__ at 0x00000170FA9ED558>]
1
Entering: [<function Test.static_method at 0x00000170FB308288>]
Exception in <function Test.static_method at 0x00000170FB308288> : (TypeError) static_method() takes 1 positional argument but 2 were given
Exiting: [<function Test.static_method at 0x00000170FB308288>]
None
Entering: [<bound method Test.class_method of <class '__main__.Test'>>]
Exiting: [<bound method Test.class_method of <class '__main__.Test'>>]
class: 3
Entering: [<property object at 0x00000170FB303E08>]
Exiting: [<property object at 0x00000170FB303E08>]
myprop
Entering: [<function Test.normal at 0x00000170FB308438>]
Exiting: [<function Test.normal at 0x00000170FB308438>]
normal: 4
從這個例子得出的結論:init、normal、class 和 prop 方法都正確檢測。
但是,static 方法不是。
我對這個片段的問題是:
謝謝!
我查看了檢查的源代碼,注意到它可以在classify_class_attrs 中找到static 方法,因此我修改了您的代碼以使用該function。
我還分離了日志,這樣我就可以使用不同的包裝函數來處理不同的規則。 其中一些是多余的,但這就是我最初分離靜態方法的方式。 我擔心 classmethod 應該得到 cls 參數,也許這是一個合理的擔憂,但它通過了這些簡單的測試而沒有成為問題。
import inspect
import types
# https://stackoverflow.com/a/18421294/577669
def log(func, *args, **kwargs):
try:
print("Entering: [%s]" % func)
try:
if callable(func):
return func(*args, **kwargs)
except Exception as e:
print('Exception in %s : (%s) %s' % (func, e.__class__.__name__, e))
raise e
finally:
print("Exiting: [%s]" % func)
def log_function(func):
def wrapped(*args, **kwargs):
return log(func, *args, **kwargs)
return wrapped
def log_staticmethod(func):
def wrapped(*args, **kwargs):
return log(func, *args[1:], **kwargs)
return wrapped
def log_method(func):
def wrapped(*args, **kwargs):
instance = args[0]
return log(func, *args, **kwargs)
return wrapped
def log_classmethod(func):
def wrapped(*args, **kwargs):
return log(func, *args[1:], **kwargs)
return wrapped
def log_datadescriptor(name, getter):
def wrapped(*args, **kwargs):
instance = args[0]
return log(getter.fget, instance)
return wrapped
class trace(object):
def __call__(self, cls): # instance, owner):
for result in inspect.classify_class_attrs(cls):
if result.defining_class == cls:
func = getattr(cls, result.name, None)
if result.kind == 'method':
setattr(cls, result.name, log_method(func))
if result.kind == 'class method':
setattr(cls, result.name, log_classmethod(func))
if result.kind == 'static method':
setattr(cls, result.name, log_staticmethod(func))
for name, getter in inspect.getmembers(cls, inspect.isdatadescriptor):
setattr(cls, name, property(log_datadescriptor(name, getter)))
return cls
@trace()
class Test:
def __init__(self, arg):
self.value = arg
@staticmethod
def static_method(arg):
return f'static: {arg}'
@classmethod
def class_method(cls, arg):
return f'class Test, argument: {arg}'
@property
def myprop(self):
return f'myprop on instance {self.value}'
def normal(self, arg):
return f'normal: {arg} on instance {self.value}'
if __name__ == '__main__':
test = Test(123)
print(test.value)
print(test.static_method(2))
print(test.class_method(3))
print(test.myprop)
print(test.normal(4))
輸入:[<功能測試。 在 0x000002338FDCCA60> 處初始化
退出:[<功能測試。 在 0x000002338FDCCA60> 處初始化
123
輸入:[<function Test.static_method at 0x000002338FDCCAF0>]
退出:[<function Test.static_method at 0x000002338FDCCAF0>]
static:2
輸入:[<綁定方法 Test.class_method of <class ' main .Test'>>]
退出:[<綁定方法 Test.class_method of <class ' main .Test'>>]
class 測試,參數:3
輸入:[<function Test.myprop at 0x000002338FDCCC10>]
退出:[<function Test.myprop at 0x000002338FDCCC10>]
實例 123 上的 myprop
輸入:[<function Test.normal at 0x000002338FDCCCA0>]
退出:[<function Test.normal at 0x000002338FDCCCA0>]
正常:實例 123 上為 4
某些文本不完全匹配,因為我們都對跟蹤的 class 中的 output 進行了一些微不足道的更改。
我的最終解決方案:
import inspect
from typing import Type
from decorator import decorator
@decorator
def log(func, *args, **kwargs):
try:
print("Entering: [%s]" % func)
return func(*args, **kwargs)
finally:
print("Exiting: [%s]" % func)
def _apply_logger_to_class(cls):
for attr in inspect.classify_class_attrs(cls):
if attr.defining_class is not cls:
continue
if attr.kind == 'data':
continue
if isinstance(attr.object, (classmethod, staticmethod)):
setattr(cls, attr.name, attr.object.__class__(log(attr.object.__func__)))
elif isinstance(attr.object, property):
setattr(cls, attr.name, property(log(attr.object.fget)))
else:
setattr(cls, attr.name, log(attr.object))
return cls
def trace(func=None):
if isinstance(func, type):
return _apply_logger_to_class(func) # logger is the class
return log(func)
@trace
def normal_function_call(arg):
return f'normal_function_call: {arg}'
@trace
class Test:
def __init__(self, arg):
self.arg = arg
print(f'{self.__class__.__name__}.__init__: {arg}')
@staticmethod
def static_method(arg):
return f'Test.static: {1}'
@classmethod
def class_method(cls, arg):
print(f'{cls.__name__}.class: {arg}')
@property
def myprop(self):
print(f'{self.__class__.__name__}.myprop.getter')
return 1
@myprop.setter
def myprop(self, item):
print(f'{self.__class__.__name__}.myprop.setter')
@myprop.deleter
def myprop(self):
print(f'{self.__class__.__name__}.myprop.deleter')
def normal(self, arg):
print(f'{self.__class__.__name__}.normal: {arg}')
@trace
class TestDerived(Test):
@staticmethod
def static_method(arg):
print(f'TestDerived.class: {arg}')
if __name__ == '__main__':
print(normal_function_call(0))
def do_test(test_class: Type[Test]):
print('-'*20, test_class.__name__)
test = test_class(1)
test.static_method(2)
test.__class__.static_method(2.5)
test.class_method(3)
test.__class__.class_method(3.5)
test.myprop
test.normal(4)
assert inspect.getfullargspec(test.normal).args == ['self', 'arg']
assert inspect.getfullargspec(test.normal).kwonlyargs == []
do_test(Test)
do_test(TestDerived)
這適用於派生類,適用於我想要的所有對象,並保留簽名。 (@wraps 沒有)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.