【问题标题】:Python inner functions/decorators: When should I use parentheses when returning an inner function?Python 内部函数/装饰器:返回内部函数时应该何时使用括号?
【发布时间】:2019-06-04 20:18:50
【问题描述】:

我正在学习 Python 装饰器和内部函数,并对我通过 codeacademy.com https://youtu.be/WOHsHaaJ8VQ 的 YouTube 视频学习的课程有一些疑问。

当使用内部函数时,有时我必须返回带括号的函数,有时不带括号。

如果我在不使用装饰器的情况下调用内部函数,则在返回内部函数时必须使用括号,否则内部函数似乎作为对象返回(?)。 在codeacademy.com的YouTube视频以及https://www.youtube.com/watch?v=FsAPt_9Bf3U这一个视频中,他们调用了不带括号的内部函数,并输出了预期的结果。

如果我使用装饰器调用内部函数,则在返回内部函数时必须不要使用括号,否则它似乎可以正常工作,但会引发错误以及其他一些奇怪的结果。

我编写了一些代码来测试不同的变化并输出结果。 您可以在此处查看实时代码:https://trinket.io/python/af1b47658f

# Test 1: The title function returns inner function wrapper without parentheses.
def title(print_name_function):
  def wrapper():
    print("Professor:")
    print_name_function()
  return wrapper # Without parentheses

def print_my_name():
  print("John")

print('Test 1')
title(print_my_name)
# Results: Nothing is printed.


# Test 2: The title function returns inner function wrapper with parentheses.
def title(print_name_function):
  def wrapper():
    print("Professor:")
    print_name_function()
  return wrapper() # With parentheses

def print_my_name():
  print("John")

print('Test 2')
title(print_my_name)
# Results: Professor John is printed.


# Test 3: Using a decorator while the title function returns inner function wrapper without parentheses
def title(print_name_function):
  def wrapper():
    print("Professor:")
    print_name_function()
  return wrapper # Without parentheses

@title
def print_my_name():
  print("John")

print('Test 3')
print_my_name()
# Results: Professor John is printed.


# Test 4: Using a decorator while the title function returns inner function wrapper with parentheses
def title(print_name_function):
  def wrapper():
    print("Professor:")
    print_name_function()
  return wrapper() # With parentheses

@title
def print_my_name():
  print("John")

print('Test 4')
print_my_name()
# Results: Professor John is printed and the following error is thrown:
'''
Traceback (most recent call last):
  File "decorators.py", line 59, in <module>
    print_my_name()
TypeError: 'NoneType' object is not callable.
'''
# Additionally, Professor John is printed before 'Test 4' is printed which seems that print_my_name() runs, then print('Test 4') runs.

在上面列出的关于我发现的内部函数/装饰器的两个视频中...

对于内部函数:内部函数在没有使用括号的情况下返回并正确运行。在我的测试中,我必须使用括号才能正确运行。

对于装饰器:内部函数在没有使用括号的情况下返回并正确运行。根据我的测试,不使用括号运行是可行的。带括号运行似乎可行,但输出顺序混淆并抛出错误(参见我的代码中的测试 4)。

【问题讨论】:

  • 这可能会有所帮助。使用像 @title; def print_my_name(): print("John") 这样的装饰器是 def print_my_name(): print("John"); print_my_name = title(print_my_name) 的语法糖
  • “我必须返回带括号的函数,有时没有。”。你想错了。如果没有括号,则返回函数对象本身,如果有括号,则返回调用函数的结果
  • @Josh 如果您觉得我的回答有帮助,请随时接受并点赞;)

标签: python python-decorators


【解决方案1】:

让我们把它分成两部分。

1) 让我们暂时忽略装饰器。

当你想调用某个函数时,你应该使用括号。

没有括号,函数就是它的名字。

例如:

这是一个函数,我们给它一个数字,然后我们得到那个数字加上 5。

def add_five(x):
    return x + 5

我们看到add_five,没有括号,只是函数定义。把它想象成一个食谱。这不是真正的蛋糕,只是有关如何烘烤蛋糕的说明。

>>> add_five
<function add_five at 0x10da3ce18>

现在我们给它一种配料,它就做成了蛋糕:

>>> add_five(1) 
6

让我们做一个类似的事情,但用更好的名字。

>>> def make_cake(cake_type):
>>>     print("Making: " + cake_type + " cake!")

>>> make_cake("carrot")
'Making: carrot cake!'

>>> make_cake
<function make_cake at 0x10da3cf28>

好的,所以当我们把函数名不带任何括号时,我们实际上并没有调用函数,我们只是得到函数的声明(有点像函数的出生证明,它有它的内存地址,在这种情况下:0x10da3cf28

同样的事情也适用于不需要任何参数的函数。

  • 没有括号,你只是在问,“嘿,函数,你存在吗?”

  • 使用括号(以及所需的必要参数/变量),您是在说:“嘿,函数,做点什么!”

现在是第二部分。

2) 装饰器做什么?

@SyntaxVoid 对你在做什么有很好的解释。装饰器是一个复杂得多的东西,所以我会坚持解释他们在这个特定的上下文中所做的事情。

基本上,您的装饰器@&lt;Some Function Name&gt; 指定一个函数来调用装饰函数。

def some_decorator(function_that_I_decorated):
    print("I'm going to print this, and then call my decorated function!")
    function_that_I_decorated()

@some_decorator
def my_decorated_function():
    print("Did I do anything?")

然后我们看到结果:

>>> def some_decorator(function_that_I_decorated):
...     print("I'm going to print this, and then call my decorated function!")
...     function_that_I_decorated()
... 
>>> @some_decorator
... def my_decorated_function():
...     print("Did I do anything?")
... 
I'm going to print this, and then call my decorated function!
Did I do anything?

现在是重要的部分:

>>> my_decorated_function
>>> my_decorated_function() 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

等等……我们不是定义了my_decorated_function吗?

是的,我们定义了函数,但装饰器正在将该函数名称重新分配给其他名称。

my_decorator_function = some_decorator(my_decorator_function)

现在some_decorator 恰好在调用my_decorator_function 之前做了一些事情。它打印一些东西。但是some_decorator 的返回值是多少?没有return 语句,所以some_decorator 默认返回None

因此,my_decorator_function 被创建并运行,现在有了一个新值。

我们为什么需要这种行为?

当我们想要改变输出时,当使用相同的输入多次运行相同的函数时。

例如,也许我想要一个函数,每隔一次调用它就返回“Go Left”,或者每隔 5 次调用该函数就返回“Go Right”。

如果我想使用具有多个变量的函数来执行此操作,那很容易!只需将其传递并检查if num_times == whatever_int

但是生活并不那么容易——有时其他人已经编写了更简单的函数,并且只允许一个变量,因为这样更通用。或者它可能太复杂了,我们需要很长时间才能弄清楚函数是如何工作的(而且我们通常不想违反抽象障碍)。在这些情况下,我们需要根据我们的需要调整它们的功能。

我鼓励您阅读更多有关 Currying 的信息,因为这也有助于您了解其他用途。

【讨论】:

  • 装饰器在您编写测试之类的事情时也非常有用,并且您不希望更改日期:github.com/spulec/freezegun,或者为某个功能做一些预设置:@ 987654323@
  • 非常感谢您的回答!我仍然在倾注它并试图将我的大脑包裹在装饰器周围,但对我来说它非常复杂!我将不得不再读几遍,但我觉得我已经接近了所有点击的那一刻。
猜你喜欢
  • 2017-08-01
  • 2021-07-25
  • 2019-07-29
  • 2013-01-20
  • 1970-01-01
  • 2017-12-11
  • 1970-01-01
  • 1970-01-01
  • 2021-08-21
相关资源
最近更新 更多