【问题标题】:How does Python know which argument will be original function in decorators?Python 如何知道哪个参数将是装饰器中的原始函数?
【发布时间】:2020-11-14 20:30:31
【问题描述】:

我不熟悉 Python 的更高级功能,例如装饰器。

我无法理解 Python 解释器如何真正理解将原始函数对象放在装饰器中的什么位置。

让我们看一个例子:来自here的例子。

没有参数的简单装饰器

def call_counter(func):
    def helper(*args, **kwargs):
        helper.calls += 1
        return func(*args, **kwargs)
    helper.calls = 0

    return helper

@call_counter
def succ(x):
    return x + 1

如果我们可以假设装饰器call_counter(func) 的第一个/唯一参数是需要包装的函数对象,那么这非常有意义。在本例中为 succ() 函数。

但是当您谈论“带参数的装饰器”时,事情变得不一致。看下面的例子:

带有一个参数的装饰器:

def greeting(expr): # Shouldn't expr be the function here ? Or at least isn't there suppose to be another parameter.
    def greeting_decorator(func): # How does Python know to pass the function down here ?
        def function_wrapper(x):
            print(expr + ", " + func.__name__ + " returns:")
            func(x)
        return function_wrapper
    return greeting_decorator

@greeting("Hello")
def foo(x):
    print(42)

foo("Hi")

现在我们知道 Python 没有数据类型的概念,所以函数参数没有提供关于它们将包含什么类型的对象的信息。

我说的对吗?

话虽如此,让我们看看上面例子中的那一行:

def greeting(expr):

如果对于装饰器,第一个参数是要包装的函数,那么按照该逻辑 expr 应该指向 foo() 对吗?否则greeting()中至少应该有两个参数,比如:

def greeting(func, expr):

但是 Python 可以“神奇地”理解内部函数需要传递函数引用:

def greeting(expr): 
    def greeting_decorator(func): # How is it correctly put one level down ?

代码没有指定数据类型或类型信息,那么对于没有参数的装饰器,函数作为第一个参数传递,而对于有参数的装饰器,函数是如何传递给内部函数的呢?

解释器如何检测到这一点?

这是怎么回事?

这对我来说似乎是“魔法”。

如果我有 5 或 6 层嵌套函数会怎样?

我很确定我在这里遗漏了一些非常基本的东西。

谢谢。

【问题讨论】:

  • “现在我们知道 Python 没有数据类型的概念,” 绝对不正确,Python 是一种强类型语言,具有明确定义的数据类型。每个对象都有一个类型,并且知道它的类型,并且该类型在运行时是可自省的。虽然,这不是装饰者知道的。请注意,Python 是动态类型的,而不是静态类型的,这可能是您的意思。
  • 您可能希望看到我的回答以进一步了解:stackoverflow.com/questions/51891951/…。总而言之,greeting("Hello") 返回一个接受foo 作为参数的函数,你当然可以嵌套任意多的层。
  • greeting("Hello") 的第一个参数 是修饰函数。 greeting 的第一个参数是 "Hello"
  • @ng.newbie you 调用 greeting("Hello"),它返回一个装饰器,在这种情况下,内部的 greeting_decorator @decorator 语法一起应用时,得到变成了function = greeting_decorator(function),这就是@decorator 的语法被定义为如何工作
  • @ng.newbie then you 将不得不执行类似@level1('foo')('bar') 的操作,@ 语法只是调用 表达式的结果与功能。它不知道涉及到多少嵌套......

标签: python python-decorators language-design


【解决方案1】:

Python 计算@ 之后的表达式,并将结果用作装饰器函数。

Python 以函数为参数调用装饰器对象的__call__ 方法。

使用

@call_counter
def succ(x):
    return x + 1

callcounter 是寻找__call__ 以提供参数func 的对象

如果你使用

@greeting("Hello")
def foo(x):
    print(42)

greeting("Hello") 被求值,其结果是 Python 使用带有 func 参数的 __call__ 方法的对象。

【讨论】:

  • 关于__call__ 的内容在技术上是准确的,但并不是特别相关。
  • 我同意,对于试图理解参数化装饰器的人来说,这可能比启发更令人困惑。
  • @juanpa.arrivillaga :我做了一些调整,但不告诉幕后发生的事情会导致更多的混乱。函数是可以使用() 运算符调用的对象。
猜你喜欢
  • 2010-11-03
  • 2015-08-18
  • 2013-05-08
  • 2018-01-21
  • 2017-07-17
  • 2014-07-21
  • 1970-01-01
  • 1970-01-01
  • 2015-10-19
相关资源
最近更新 更多