我正在尝试创建一个可以在类上定义的装饰器,并装饰其中定义的所有内容。首先让我展示一下我已经基于其他 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))
Run Code Online (Sandbox Code Playgroud)
从类中删除@trace装饰器时,输出如下:
123
static
class
myprop
normal
Run Code Online (Sandbox Code Playgroud)
添加@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
Run Code Online (Sandbox Code Playgroud)
此示例的结论:init、normal、class 和 prop 方法均已正确检测。
然而,静态方法则不然。
我对此片段的问题是:
谢谢!
我查看了inspect的源代码,发现它可以在classify_class_attrs中找到静态方法,因此我修改了您的代码以使用该函数。
我还分离了日志,这样我就可以有不同的包装函数来处理不同的规则。其中一些是多余的,但这就是我最初分离静态方法的方式。我担心 classmethod 应该得到 cls 参数,也许这是一个合理的担忧,但它通过了这些简单的测试,而没有成为问题。
import inspect
import types
# /sf/answers/1289490611/
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))
Run Code Online (Sandbox Code Playgroud)
输入:[<功能测试。init at 0x000002338FDCCA60>]
退出:[<功能测试。init at 0x000002338FDCCA60>]
123
输入:[<function Test.static_method at 0x000002338FDCCAF0>]
退出:[<function Test.static_method at 0x000002338FDCCAF0>]
static:2输入:[<<class ' main .Test'
的绑定方法 Test.class_method >>]退出: [<<class ' main .Test' 的绑定方法 Test.class_method of <class ' main .Test'>>] class Test, argument: 3 Entering: [<function Test.myprop at 0x000002338FDCCC10>] 退出: [<function Test.myprop at 实例 123 上的0x000002338FDCCC10>] myprop 输入:[<function Test.normal at 0x000002338FDCCCA0>] 退出:[<function Test.normal at 0x000002338FDCCCA0>] 正常:实例 123 上为 4
有些文本并不完全匹配,因为我们都对跟踪类中的输出进行了一些细微的更改。