装饰器中的命名关键字?

0 投票
3 回答
589 浏览
提问于 2025-04-15 23:54

我最近在尝试自己写一个记忆化装饰器,想在看别人的代码之前先玩玩。这其实更多是为了好玩。不过在这个过程中,我发现我在使用装饰器时遇到了一些问题,无法实现我想要的功能。

def addValue( func, val ):
    def add( x ):
        return func( x ) + val
    return add

@addValue( val=4 )
def computeSomething( x ):
    #function gets defined

如果我想实现这个功能,我就得这样做:

def addTwo( func ):
    return addValue( func, 2 )

@addTwo
def computeSomething( x ):
    #function gets defined

为什么我不能以这种方式在装饰器中使用关键字参数呢?我哪里做错了?你能告诉我应该怎么做吗?

3 个回答

3

装饰器可以带有任何类型的参数——无论是命名参数、位置参数,还是两者都有——简单来说,就是在@name那一行你是调用它,而不仅仅是提到它。这种情况下,你需要用双重嵌套的方式(而那些只提到的装饰器只需要单层嵌套)。即使是没有参数的装饰器,如果你想在@那一行调用它,也需要双重嵌套。下面是一个最简单的、不做任何事情的双重嵌套装饰器:

def double():
  def middling():
    def inner(f):
      return f
    return inner
  return middling

你可以这样使用它:

@double()
def whatever ...

注意括号(在这个例子中是空的,因为不需要任何参数):它们表示你在调用 double,这个调用会返回middling,然后这个middling会装饰whatever

一旦你明白了“调用”和“仅仅提到”之间的区别,添加(例如可选的)命名参数就不难了:

def doublet(foo=23):
  def middling():
    def inner(f):
      return f
    return inner
  return middling

可以用以下方式使用:

@doublet()
def whatever ...

或者用:

@doublet(foo=45)
def whatever ...

或者等价地用:

@doublet(45)
def whatever ...
5

你想要对函数 addValue 进行 部分应用,也就是说给出 val 这个参数,但不提供 func。通常有两种方法可以做到这一点:

第一种叫做 柯里化,在 interjay 的回答中使用了这种方法:你不再写一个有两个参数的函数 f(a,b) -> res,而是写一个只接受第一个参数的函数,这个函数返回另一个接受第二个参数的函数,形式是 g(a) -> (h(b) -> res)

另一种方法是使用 functools.partial 对象。它会检查这个函数,找出运行所需的参数(在你的例子中是 funcval)。你可以在创建部分应用时添加额外的参数,一旦调用这个部分应用,它就会使用所有提供的额外参数。

from functools import partial
@partial(addValue, val=2 ) # you can call this addTwo
def computeSomething( x ): 
    return x

通常情况下,部分应用是解决这个 部分应用 问题的更简单的方案,尤其是当参数不止一个时。

5

你需要定义一个函数,这个函数会返回一个装饰器:

def addValue(val):
    def decorator(func):
        def add(x):
            return func(x) + val
        return add
    return decorator

当你写 @addTwo 的时候,addTwo 的值会直接作为装饰器使用。但是当你写 @addValue(4) 的时候,首先会调用 addValue(4) 这个函数,计算出结果,然后这个结果才会被用作装饰器。

撰写回答