170

假设 afunction A只有 需要function B,A 应该在 B 中定义吗?

简单的例子。两种方法,一种从另一种调用:

def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

在 Python 中,我们可以def在另一个def. 那么,如果method_b需要并且只调用 from method_a,我应该method_b在里面声明method_a吗?像这样 :

def method_a(arg):
    
    def method_b(arg):
        return some_data

    some_data = method_b(arg)

还是我应该避免这样做?

4

12 回答 12

136
>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

这是你要找的吗?它被称为闭包

于 2011-01-28T18:29:58.830 回答
52

这样做并没有真正获得太多收益,实际上它会减慢速度method_a,因为它会在每次调用时定义并重新编译另一个函数。鉴于此,最好只在函数名称前加上下划线来表示它是私有方法——即_method_b.

如果嵌套函数的定义由于某种原因每次都不同,我想您可能想要这样做,但这可能表明您的设计存在缺陷。也就是说,这样做有正当理由的,以允许嵌套函数使用传递给外部函数但未显式传递给它们的参数,例如,在编写函数装饰器时有时会发生这种情况。尽管没有定义或使用装饰器,但这是在接受的答案中显示的内容。

更新:

这是嵌套它们速度较慢的证据(使用 Python 3.6.1),尽管在这种微不足道的情况下承认不是很多:

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

请注意,我self为您的示例函数添加了一些参数,以使它们更像真正的方法(尽管method_b2在技术上仍然不是Test该类的方法)。与您的不同,嵌套函数实际上在该版本中被调用。

于 2011-01-28T20:01:28.910 回答
27

函数内部的函数通常用于闭包

(关于究竟是什么使闭包成为闭包存在很多争论。)

这是一个使用内置的示例sum()。它定义start一次并从那时起使用它:

def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

正在使用:

>>> sum_with_1 = sum_partial(1)
>>> sum_with_3 = sum_partial(3)
>>> 
>>> sum_with_1
<function sum_start at 0x7f3726e70b90>
>>> sum_with_3
<function sum_start at 0x7f3726e70c08>
>>> sum_with_1((1,2,3))
7
>>> sum_with_3((1,2,3))
9

内置python闭包

functools.partial是一个闭包的例子。

从 python docs,它大致相当于:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

(感谢@user225312 下面的答案。我发现这个例子更容易理解,希望能帮助回答@mango 的评论。)

于 2014-06-06T21:32:14.997 回答
26

一般来说,不,不要在函数内部定义函数。

除非你有充分的理由。你没有。

为什么不?

在函数内部定义函数的真正理由什么?

当你真正想要的是一个 dingdang闭包

于 2017-08-17T17:17:39.920 回答
10

在另一个函数中声明一个函数实际上很好。这对于创建装饰器特别有用。

但是,根据经验,如果函数很复杂(超过 10 行),最好在模块级别声明它。

于 2011-01-28T18:31:19.933 回答
8

我发现这个问题是因为我想提出一个问题,为什么如果使用嵌套函数会对性能产生影响。我在配备四核 2.5 GHz Intel i5-2530M 处理器的 Windows 笔记本上使用 Python 3.2.5 对以下函数进行了测试

def square0(x):
    return x*x

def square1(x):
    def dummy(y):
        return y*y
    return x*x

def square2(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    return x*x

def square5(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    def dummy3(y):
        return y*y
    def dummy4(y):
        return y*y
    def dummy5(y):
        return y*y
    return x*x

我测量了以下 20 次,包括 square1、square2 和 square5:

s=0
for i in range(10**6):
    s+=square0(i)

并得到以下结果

>>> 
m = mean, s = standard deviation, m0 = mean of first testcase
[m-3s,m+3s] is a 0.997 confidence interval if normal distributed

square? m     s       m/m0  [m-3s ,m+3s ]
square0 0.387 0.01515 1.000 [0.342,0.433]
square1 0.460 0.01422 1.188 [0.417,0.503]
square2 0.552 0.01803 1.425 [0.498,0.606]
square5 0.766 0.01654 1.979 [0.717,0.816]
>>> 

square0没有嵌套函数,square1有一个嵌套函数,square2有两个嵌套函数,square5有五个嵌套函数。嵌套函数只声明但不调用。

因此,如果您在一个未调用的函数中定义了 5 个嵌套函数,则该函数的执行时间是没有嵌套函数的函数的两倍。我认为在使用嵌套函数时应该谨慎。

生成此输出的整个测试的 Python 文件可以在ideone找到。

于 2014-10-05T21:38:41.907 回答
6

所以最后,很大程度上是一个关于 python 实现是否智能的问题,特别是在内部函数不是闭包而只是 in 函数需要帮助器的情况下。

在清晰可理解的设计中,仅在需要它们的地方而不在其他地方公开函数是好的设计,无论它们是嵌入在模块中、作为方法的类中,还是在另一个函数或方法中。如果做得好,它们确实可以提高代码的清晰度。

并且当内部函数是一个闭包时,即使该函数没有从包含函数返回以在其他地方使用,它也可以帮助更清楚地说明问题。

所以我会说通常会使用它们,但是当你真正关心性能时要注意可能的性能影响,并且只有在你进行实际分析显示它们最好被删除时才删除它们。

不要在您编写的所有 python 代码中只使用“内部函数 BAD”进行过早优化。请。

于 2016-08-13T23:37:33.027 回答
5

这只是关于暴露 API 的一个原则。

使用python,避免在外层空间(模块或类)暴露API是一个好主意,函数是一个很好的封装地方。

这可能是个好主意。当你确保

  1. 内部函数由外部函数使用。
  2. 内部函数有一个很好的名字来解释它的目的,因为代码会说话。
  3. 您的同事(或其他代码阅读器)无法直接理解代码。

尽管如此,滥用此技术可能会导致问题并暗示设计缺陷。

只是从我的经验,也许误解了你的问题。

于 2012-12-18T10:02:55.877 回答
1

这样做完全没问题,但除非您需要使用闭包或返回我可能会放在模块级别的函数。我想在第二个代码示例中你的意思是:

...
some_data = method_b() # not some_data = method_b

否则, some_data 将是函数。

将它放在模块级别将允许其他函数使用 method_b() 并且如果您使用Sphinx(和 autodoc)之类的东西来记录文档,它也将允许您记录 method_b 。

如果您正在做一些可以由对象表示的事情,您可能还想考虑将功能放在一个类的两个方法中。如果这就是您要寻找的全部,这也包含逻辑。

于 2011-01-28T20:17:38.130 回答
1

执行以下操作:

def some_function():
    return some_other_function()
def some_other_function():
    return 42 

如果您要运行some_function(),它将运行some_other_function()并返回 42。

编辑:我最初说过你不应该在另一个函数内部定义一个函数,但有人指出有时这样做是很实际的。

于 2014-05-30T12:26:48.620 回答
1

您可以使用它来避免定义全局变量。这为您提供了其他设计的替代方案。3个设计提出了一个问题的解决方案。

A) 使用没有全局变量的函数

def calculate_salary(employee, list_with_all_employees):
    x = _calculate_tax(list_with_all_employees)

    # some other calculations done to x
    pass

    y = # something 

    return y

def _calculate_tax(list_with_all_employees):
    return 1.23456 # return something

B) 使用带有全局变量的函数

_list_with_all_employees = None

def calculate_salary(employee, list_with_all_employees):

    global _list_with_all_employees
    _list_with_all_employees = list_with_all_employees

    x = _calculate_tax()

    # some other calculations done to x
    pass

    y = # something

    return y

def _calculate_tax():
    return 1.23456 # return something based on the _list_with_all_employees var

C) 在另一个函数中使用函数

def calculate_salary(employee, list_with_all_employees):

    def _calculate_tax():
        return 1.23456 # return something based on the list_with_a--Lemployees var

    x = _calculate_tax()

    # some other calculations done to x
    pass
    y = # something 

    return y

解决方案C)允许在外部函数的范围内使用变量,而无需在内部函数中声明它们。在某些情况下可能有用。

于 2018-06-20T13:13:56.987 回答
0

函数在函数python

def Greater(a,b):
    if a>b:
        return a
    return b

def Greater_new(a,b,c,d):
    return Greater(Greater(a,b),Greater(c,d))

print("Greater Number is :-",Greater_new(212,33,11,999))
于 2019-09-15T05:17:42.163 回答