【问题标题】:Handling staticmethods while working with metaclasses在处理元类时处理静态方法
【发布时间】:2021-07-27 12:33:09
【问题描述】:

在下面的代码中,我使用元类和装饰器来装饰所有用户定义的方法。

它适用于所有实例方法,但在静态方法的情况下,由于 self 参数而失败,以避免我使用 try 和 except 块,从而解决了问题。但在我的一个项目中,它没有成功。

有没有更好的方法通过包含在元类中的函数装饰器来装饰静态方法的输出?

from functools import wraps
import types

def decorator_function(input_function):
    @wraps(input_function)
    def wrapper(self, *args, **kwargs):
        if kwargs.get("test_parameter"):
            kwargs["test_parameter"] = 999
        try:
            result = input_function(self, *args, **kwargs)
        except:
            result = input_function(*args, **kwargs)
        return result
    return wrapper


class DecoratorMetaClass(type):
    def __new__(meta, name, bases, class_dict):
        klass = super().__new__(meta, name, bases, class_dict)

        for key in dir(klass):
            value = getattr(klass, key)
            if isinstance(value, types.FunctionType) and "__" not in key:
                wrapped = decorator_function(value)
                setattr(klass, key, wrapped)
        return klass


class InterfaceClass(metaclass=DecoratorMetaClass):
    def function(self, test_parameter=1):
        print(f"function - Test Parameter= {test_parameter}")

    @staticmethod
    def static_function(test_parameter=1):
        print(f"static_function - Test Parameter= {test_parameter}")


class UserClass(InterfaceClass, metaclass=DecoratorMetaClass):
    def __init__(self):
        pass
    def function_2(self, test_parameter=1):
        print(f"function_2 - Test Parameter= {test_parameter}")


instance = UserClass()
instance.function(test_parameter=2)
instance.function_2(test_parameter=2)
instance.static_function(test_parameter=2)
print(isinstance(instance, InterfaceClass))

PS:我没有使用类装饰器,因为它会导致 isinstance 检查失败。

【问题讨论】:

  • 您可以将函数wrapper的签名从wrapper(self, *args, **kwargs)更改为wrapper(*args, **kwargs)。然后分配result = input_function(*args, **kwargs)。你不需要这个装饰器的 try/except 块。

标签: python-3.x


【解决方案1】:

解释

主要问题归结为方法参数。你快到了。

  1. 您必须使装饰器参数与您的方法参数兼容;

  2. 您可以将函数包装器的签名从wrapper(self, *args, **kwargs) 更改为wrapper(*args, **kwargs)。然后分配result = input_function(*args, **kwargs)你不需要这个装饰器的 try/except 块;

def decorator_function(input_function):
    @wraps(input_function)
    def wrapper(*args, **kwargs):
        if kwargs.get("test_parameter"):
            kwargs["test_parameter"] = 999
        return input_function(*args, **kwargs)
    return wrapper
  1. 理想情况下,您应该添加到方法*args(变量参数)和**kwargs(变量命名参数)以使它们与您的装饰器兼容;

  2. 在这种情况下,我在test_parameter=1 之前将*args 添加到InterfaceClass 中的static_function

class InterfaceClass(metaclass=DecoratorMetaClass):
    @staticmethod
    def static_function(*args, test_parameter=1):
        print(f"static_function - Test Parameter= {test_parameter}")

可运行代码

from functools import wraps
import types

def decorator_function(input_function):
    @wraps(input_function)
    def wrapper(*args, **kwargs):
        if kwargs.get("test_parameter"):
            kwargs["test_parameter"] = 999
        return input_function(*args, **kwargs)
    return wrapper


class DecoratorMetaClass(type):
    def __new__(meta, name, bases, class_dict):
        klass = super().__new__(meta, name, bases, class_dict)

        for key in dir(klass):
            value = getattr(klass, key)
            if isinstance(value, types.FunctionType) and "__" not in key:
                wrapped = decorator_function(value)
                setattr(klass, key, wrapped)
        return klass


class InterfaceClass(metaclass=DecoratorMetaClass):
    def function(self, test_parameter=1):
        print(f"function - Test Parameter= {test_parameter}")

    @staticmethod
    def static_function(*args, test_parameter=1):
        print(f"static_function - Test Parameter= {test_parameter}")


class UserClass(InterfaceClass, metaclass=DecoratorMetaClass):
    def __init__(self):
        pass
    def function_2(self, test_parameter=1):
        print(f"function_2 - Test Parameter= {test_parameter}")

instance = UserClass()
instance.function(test_parameter=2)
instance.function_2(test_parameter=2)
instance.static_function(test_parameter=2)
UserClass.static_function(test_parameter=3)
print(isinstance(instance, InterfaceClass))

输出

function - Test Parameter= 999
function_2 - Test Parameter= 999
static_function - Test Parameter= 999
static_function - Test Parameter= 999
True

处理 OP 的评论

考虑到test_parameter 始终是一个命名参数,将decorator_function 写成如下:

def decorator_function(input_function):
    @wraps(input_function)
    def wrapper(*args, **kwargs):
        if kwargs.get("test_parameter"):
            kwargs["test_parameter"] = 999
        try:
            result = input_function(*args, **kwargs)
        except TypeError:
            result = input_function(**kwargs)
        return result
    return wrapper

这样您就不需要更改方法签名。

如果您还使用位置参数调用函数,则需要检查插入args 的第一个参数的类型。事情变得复杂且容易出错。

【讨论】:

  • 感谢您的详细回答,我之所以添加 self 是因为我想访问装饰器内部的一些实例变量,但我可以通过检查 args 结构内部来查找实例引用来做到这一点。
  • 但是有没有其他方法,不涉及更改静态方法的签名?因为如果我要使用具有更大框架的元类,我无法更改所有静态方法的签名。
  • 这是可行的,但我很难知道,因为我不知道如何在其他地方调用这些方法(使用位置或命名参数)。如果使用位置参数调用该方法,则值将转到args,如果使用命名参数kwargs 调用。这就是argskwargs 通常应该出现在方法签名中的原因之一。至少我是这么认为的……
  • 考虑到test_parameter 始终是一个命名参数,我正在为您写一个解决方案...
  • 我更新了我的答案。请参阅解决 OP 的评论部分...
猜你喜欢
  • 2021-12-08
  • 2012-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多