我正在编写一个记录器装饰器,它可以应用于类方法(以及理论上任何类型的函数或方法)等。我的问题是它们的参数化根据函数是否是
示例代码可以工作,但非常优雅,并且可以通过参数化来欺骗:
def some_decorator(func):
def wrapper(*argsWrap, **kwargsWrap):
if isinstance(func, classmethod):
if isinstance(argsWrap[0], SomeClass):
# Called by some_instance.some_method as the first parameter is SomeClass
print(argsWrap[1])
return func.__func__(*argsWrap, **kwargsWrap)
else:
# Called by SomeClass.some_method
print(argsWrap[0])
return func.__func__(SomeClass, *argsWrap, **kwargsWrap)
else:
return func(*argsWrap, **kwargsWrap)
return wrapper
class SomeClass:
@some_decorator
@classmethod
def some_method(cls, some_var: str):
print(some_var)
if __name__ == '__main__':
SomeClass.some_method('Test')
some_instance = SomeClass()
some_instance.some_method('Test2')
问题很简单:是否有可能仅就参数化做出更干净、更安全的决策?
请注意,对于通用装饰器(由其他人使用),不能说它必须放在@classmethod下面,而不是上面等等。
我认为inspect模块可以更安全地处理这个任务。
它提供了几个有用的函数来帮助获取有关活动对象的信息,例如模块、类、方法、函数、回溯、框架对象和代码对象。
例如,像这样:
import functools
import inspect
def logger_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Get the function signature
sig = inspect.signature(func)
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
# Determine the type of function/method
if inspect.ismethod(func) or inspect.isfunction(func):
if isinstance(func, staticmethod):
# It's a static method
log_arg = bound_args.arguments.values()
elif inspect.isclass(bound_args.arguments.get('self')):
# It's a classmethod
cls = bound_args.arguments['self']
print(f"CLS: {cls}")
log_arg = bound_args.arguments.values()
elif 'self' in bound_args.arguments:
# It's an instance method
log_arg = bound_args.arguments.values()
else:
# It's a standalone function
log_arg = bound_args.arguments.values()
else:
# Handle other cases
log_arg = bound_args.arguments.values()
# Log the argument
print(f"Logging: {log_arg}")
# Call the original function
return func(*args, **kwargs)
return wrapper
class SomeClass:
@classmethod
@logger_decorator
def class_method(cls, some_var: str, *args, **kwargs):
print(f"Class method: {some_var}")
@logger_decorator
def instance_method(self, some_var: str, *args, **kwargs):
print(f"Instance method: {some_var}")
@logger_decorator
@staticmethod
def static_method(some_var: str, *args, **kwargs):
print(f"Static method: {some_var}")
@logger_decorator
def standalone_function(some_var: str, *args, **kwargs):
print(f"Standalone function: {some_var}")
if __name__ == '__main__':
SomeClass.class_method("Test1", "class", a1="class_a", b1="class_b")
some_instance = SomeClass()
some_instance.instance_method("Test2", "instance", a2="instance_a", b2="instance_b")
SomeClass.static_method('Test3', "static", a3="static_a", b3="static_b")
standalone_function('Test4', "standalone", a4="standalone_a", b4="standalone_b")
# OUTPUT
Logging: dict_values([<class '__main__.SomeClass'>, 'Test1', ('class',), {'a1': 'class_a', 'b1': 'class_b'}])
Class method: Test1
Logging: dict_values([<__main__.SomeClass object at 0x7fae87d70950>, 'Test2', ('instance',), {'a2': 'instance_a', 'b2': 'instance_b'}])
Instance method: Test2
Logging: dict_values(['Test3', ('static',), {'a3': 'static_a', 'b3': 'static_b'}])
Static method: Test3
Logging: dict_values(['Test4', ('standalone',), {'a4': 'standalone_a', 'b4': 'standalone_b'}])
Standalone function: Test4
两种方法的区别在于,您的示例依赖于
args
来确定函数的类型,而后者直接使用检查模块来识别 func
的类型。虽然区分instance methods
和class methods
仍然需要第一个参数(self
或cls
)来确定类型,但inspect模块仍然利用func
来生成相应的bound_args
,所以最终取决于 func
本身。
这样写有三个好处: