如何推断类方法是在实例上还是在类上调用

问题描述 投票:0回答:1

我正在编写一个记录器装饰器,它可以应用于类方法(以及理论上任何类型的函数或方法)等。我的问题是它们的参数化根据函数是否是

  • 独立函数(*args)
  • member_method(self, *args) 或
  • class_method(cls, *args)

示例代码可以工作,但非常优雅,并且可以通过参数化来欺骗:

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下面,而不是上面等等。

python-3.x python-decorators
1个回答
0
投票

我认为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
本身。

这样写有三个好处:

  1. 它不依赖于硬编码的类名(如原始示例中的 SomeClass)。
  2. 它处理不同的参数传递样式(位置、关键字、默认值)。
  3. 它更具可扩展性,可以轻松处理其他类型的方法(例如静态方法),只需进行最小的更改。
© www.soinside.com 2019 - 2024. All rights reserved.