【问题标题】:Passing an object to a decorator将对象传递给装饰器
【发布时间】:2021-02-11 15:52:51
【问题描述】:

我正在尝试创建一个 Python 文件,其中包含我需要在程序的其余部分中使用的所有装饰器。这些装饰器存储在一个类中,我称之为Decorators。然后我尝试添加一个装饰器以检查装饰函数的参数是否与传递给装饰器本身的参数类型匹配(我从站点https://www.python.org/dev/peps/pep-0318/#examples 的示例 4 中获取了这种装饰器,但我对其进行了一些更改更好地适应我的编码风格)。语法是这样的:

class Decorators(object):

    """ Decorators class: contain all the decorators """

    @classmethod
    def argument_consistency(cls, *function_arguments_type):

        """ check the consistency of argument and their types of the decorated function """

        def check_arguments(function):

            """ check if the number of passed arguments is different from the number of accepted arguments """

            # check if the number of passed arguments is different from the number of accepted arguments
            if not len(function_arguments_type) == function.__code__.co_argcount:
                raise Exception("the number of passed argument is different from the number of the accepted arguments")

            def inner_function(*args, **kwds):

                """ check if the type of the passed arguments match with the requested ones """

                # iterate through the list of couples (argument, argument's type) and check for their match
                for (arguments, argument_types) in zip(args, function_arguments_type):

                    # remember that: {arguments} is the n-th argument passed to the function, while
                    # the {argument_types} is the n-th argument types. {args} is the entire list of arguments
                    # passed to the function; {function_arguments_type} is the entire list of types. So zip
                    # returns an iterator of tuples of element of {args} and {function_arguments_type} paired
                    # together, for example zip((1, 2, 3, 4), (a, b, c, d)) = ((1, a), (2, b), (3, c), (4, d))
                   
                    # check if the n-th argument type match with the one requested
                    if not type(arguments) == argument_types:
                        raise Exception(f"The argument {arguments} doesn't match the type, "
                                        f"which must be {argument_types}")

                # returning the passed function using the passed arguments
                return function(*args, **kwds)

            # changing the name of the inner_function to the {function}'s name
            inner_function.__name__ = function.__name__

            # return the inner function
            return inner_function

        # return the check_argument function
        return check_arguments

为了测试之前的装饰器,我创建了一个简单的类 A 和一个函数 a:

class A():

    def __init__(self):
        pass

    @Decorators.argument_consistency(str, str)
    def a(self, str1, str2):
        print(f"{str1} AND {str2}")

a = A()
a.a("ciao", "ciao2")

显然,当我装饰函数 a 时出现错误(由 argument_consistency 装饰器本身引发)。这是因为列表参数类型的长度与传递的参数列表的长度不同。出现错误是因为我没有放self参数。了解了这个错误,我尝试将self 传递给装饰器,但是我得到了一个错误:NameError: name 'self' is not defined(即使我传递了type(self),也会发生这种情况);然后我尝试通过 A 本身的类,但我仍然遇到同样的错误。所以我试图通过在 for 循环和 if not type(arguments) == argument_types 之间添加一行到装饰器来解决这个问题:

if not (args.index(arguments) == 0 and argument_types is None):
    
    # check if the n-th argument type match with the one requested
    if not type(arguments) == argument_types:

        # the rest of the code
        pass

这行检查传递给函数装饰器的第一个参数是否为None,那么这意味着函数的第一个参数是self,因此函数不会继续检查None是否相等self 参数的类型(显然不是)。这种方式非常繁琐,与优雅相反。因此我想知道是否有办法避免此修复并直接将self 类型传递给装饰器。

【问题讨论】:

  • 我认为你需要的是简单地跳过第一个参数,如果装饰函数是一个方法/类方法。检查您是否可以使用inspect.ismethod()。并处理静态方法,看看这个stackoverflow.com/questions/8727059/…
  • 感谢您的帮助!我会试试看,如果它有效,我会告诉你

标签: python python-3.x python-decorators


【解决方案1】:

您可以为对象/类方法的 self 参数创建一个存根类

class selftype:
    pass

并将其传递给装饰器

@Decorators.argument_consistency(selftype, str, str)
def a(self, str1, str2):
    print(f"{str1} AND {str2}")

如果装饰器中的第一个类型是您的存根类型,则检查inner_function:

def inner_function(*args, **kwds):
    for (argument, argument_type, i) in zip(args, function_arguments_type, range(0, len(args))):
        if argument_type == selftype and i == 0:
            pass
        # check if the n-th argument type match with the one requested
        elif not type(argument) == argument_type:
            raise Exception(f"The argument {argument} doesn't match the type, "
                            f"which must be {argument_type}")

    # returning the passed function using the passed arguments
    return function(*args, **kwds)

不是很优雅,但这适用于对象/类方法和函数

class A():

    def __init__(self):
        pass

    @Decorators.argument_consistency(selftype, str, str)
    def a(self, str1, str2):
        print(f"{str1} AND {str2}")

a = A()
a.a("ciao", "ciao2")

@Decorators.argument_consistency(str)
def b(str1):
    print(f"{str1}")

b("a")

另外,如果您想将您的装饰器与@classmethod 或@staticmethod 配对使用,请务必先应用您的装饰器,否则它将无法访问function.__code__ 属性。

我非常喜欢 @go2nirvana 在 cmets 中提出的解决方案,但不幸的是,它对我不起作用。 inspect.ismethod(function) 在装饰器函数调用中返回 False,我知道为什么。

【讨论】:

  • 非常感谢,我还在努力实现这个,但如果我成功了我会告诉你的
  • 另一件事,如果我应用到我想要装饰@classmethod 装饰器的函数(除了@Decorators.argument_consistency)我得到一个错误:AttributeError: 'classmethod' object has no attribute '__code__'。有什么想法可以避免这种事情吗?
  • 请记住应用装饰器的顺序很重要。在这种情况下,你想先应用 @Decorators.argument_consistency 装饰器,所以它会抓取你未装饰的函数并检查参数
  • 非常感谢!我实际上注意到了它,我想知道这是正常行为还是我的代码的错误。谢谢你的帮助。我不是专业的程序员,这是我第一次研究更复杂的概念,如果有什么方法可以回馈您并帮助您告诉我!
猜你喜欢
  • 2015-12-13
  • 2021-12-23
  • 2021-11-15
  • 2014-11-17
  • 1970-01-01
  • 2021-11-21
  • 2018-08-24
  • 2014-10-01
相关资源
最近更新 更多