带有 kwargs 移动函数对象的 Python 装饰器类

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

首先我想说我仍然是一个Python学徒,所以我可能在这里遗漏了一些明显的东西,但是经过对堆栈溢出和一些谷歌文章的研究后我找不到确切的答案。

所以我的问题是,在Python中,当你声明一个装饰器类时,取决于你是否在kwargs存在的情况下调用它,你的func对象最终会出现在init方法或call方法上。我知道问题的原因是,当装饰器没有 kwargs 时,在装饰另一个函数时不会调用它,而只是启动它,但是当存在 kwargs 时,它会被启动并被调用。但这种行为对我来说看起来很奇怪,也许这就是 python 的工作方式。有一些解决方法,例如here提到的解决方法,但它们只是解决方法。正如我们的朋友 Raymond Hettinger 经常说的:“一定有更好的方法!”

为了展示这个问题,我编写了一个小脚本,它展示了如何在脚本启动之前调用带有 kargs 的装饰器,而仅在调用函数本身时才调用不带 kwargs 的装饰器。对于带有 kwargs 的装饰器,这会导致函数对象被解析为 __ call __ 方法,而对于不带 kwargs 的装饰器,函数对象会被传递到 __ init __ 方法。

这是代码,看看我的意思:

class Decorator1():
def __init__(self, *a, **kw):
    print('\nInitializing Decorator1 Class')
    print('init 1 arguments = ' + str(a))
    print('init 1 karguments = ' + str(kw))
    self.func = a[0]

def __call__(self, *a, **kw):
    print('\nCalling Decorator1 Class')
    print('call 1 arguments = ' + str(a))
    print('call 1 karguments = ' + str(kw))
    return self.func()


class Decorator2():
    def __init__(self, *a, **kw):
        print('\nInitializing Decorator2 Class')
        print('init 2 arguments = ' + str(a))
        print('init 2 karguments = ' + str(kw))

    def __call__(self, *a, **kw):
        print('\nCalling Decorator2 Class')
        print('call 2 arguments = ' + str(a))
        print('call 2 karguments = ' + str(kw))
        return a[0]


@Decorator1
def my_function_1(height=2):
    print('This is my_function_1')
    return True

@Decorator2(test=3)
def my_function_2(height=2):
    print('This is my_function_2')
    return True


print('\n-------------------- Script Starts ----------------------------')
print('\n### Calling my_function_1 ###')
my_function_1(height=1)
print('\n### Calling my_function_2 ###\n')
my_function_2(height=2)
print('-------------------- Script Ends ------------------------------')

这是 bash 输出:

Initializing Decorator1 Class
init 1 arguments = (<function my_function_1 at 0x000001D575367F70>,)
init 1 karguments = {}

Initializing Decorator2 Class
init 2 arguments = ()
init 2 karguments = {'test': 3}

Calling Decorator2 Class
call 2 arguments = (<function my_function_2 at 0x000001D5753791F0>,)
call 2 karguments = {}

-------------------- Script Starts ----------------------------

### Calling my_function_1 ###

Calling Decorator1 Class
call 1 arguments = ()
call 1 karguments = {'height': 1}
my function 1

### Calling my_function_2 ###

my function 2
-------------------- Script Ends ------------------------------

编辑:

总结一下问题就是: 有没有一种简单的方法可以在使用和不使用 kwargs 的情况下调用相同的装饰器?我的意思是有没有一种方法可以让我像这样 (@Decorator1(kwarg1 = 3)) 那样应用我的装饰器来处理某些函数,同时也能够像这样 (@Decorator1) 那样将它应用到其他函数上?

抱歉帖子很长,我希望你能帮助我找到更好的方法:D

python class decorator python-decorators keyword-argument
2个回答
3
投票

您使用

Decorator1
会导致被修饰的函数作为参数传递给
Decorator1.__init__

您使用

Decorator2
会导致创建
Decorator2
的实例,并且 then 正在修饰的函数将作为参数传递给
Decorator2.__call__

装饰器语法是定义函数的快捷方式,那么

  1. 调用该函数的装饰器
  2. 并将装饰器调用的结果绑定到函数的原始名称。

也就是说,以下有很大不同:

# Decorator1 is called on my_function_1
# Equivalent to my_function_1 = Decorator1(my_function_1)
@Decorator1
def my_function_1(height=2):
    print('This is my_function_1')
    return True

# An instance of Decorator1 is called on my_function_1.
# Equivalent to my_function_1 = Decorator1()(my_function_1)
@Decorator1()
def my_function_1(height=2):
    print('This is my_function_1')
    return True

从这个意义上说,装饰器语法不同于

class
语句语法,其中

class Foo:
    ...

class Foo():
    ...

完全相同。

实际上,

@
后面的字符串始终是一个计算结果为可调用对象的表达式。从 Python 3.9 开始,它实际上可以是any这样的表达式,而不是像早期版本那样仅限于点分名称或点分名称调用。


0
投票

能够仅使用一个类以及带有额外参数的它的实例进行装饰是非常有用的。这是可以实现的,但只能使用一些有些笨拙的代码。对我来说,我更喜欢一种解决方法,它可以使代码更干净,并更好地保持使用意图的清晰度。

它涉及使用我称为“using”的类方法,后跟您想要的关键字参数。 (我会将该方法命名为“with”,但这是一个保留字 - 不过可以将其命名为您喜欢的名称。)请参阅下面的示例,该示例的布局与您的示例尽可能接近。

class Decorator3():
    def __init__(self, func, **kw):
        print('\nInitializing Decorator3 Class')
        print('init 3 arguments = ' + str(func))
        print('init 3 karguments = ' + str(kw))
        self.func = func

    @classmethod
    def using(cls, **kw):
        def decorator(func):
            return cls(func, **kw)
        return decorator

    def __call__(self, *a, **kw):
        print('\nCalling Decorator3 Class')
        print('call 3 arguments = ' + str(a))
        print('call 3 karguments = ' + str(kw))
        return self.func()


@Decorator3
def my_function_1(height=2):
    print('This is my_function_1')
    return True

@Decorator3.using(test=3)
def my_function_2(height=2):
    print('This is my_function_2')
    return True


print('\n-------------------- Script Starts ----------------------------')
print('\n### Calling my_function_1 ###')
my_function_1(height=1)
print('\n### Calling my_function_2 ###')
my_function_2(height=2)
print('-------------------- Script Ends ------------------------------')

我已将类的参数从 (self, *a, **kw) 更改为 (self, func, **kw) 以强调这样一个事实:只有被修饰的函数才应作为位置参数。此外,任何其他命名参数都必须在定义装饰器类时给出默认值。

© www.soinside.com 2019 - 2024. All rights reserved.