关于python:如果只有函数B需要函数A应该在B中定义A?

If function A is required only by function B should A be defined inside B?

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

1
2
3
4
5
def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

在python中,我们可以在另一个def中声明def。所以,如果只需要从method_a调用method_b,我应该在method_a内声明method_b?这样地:

1
2
3
4
5
6
def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b

还是我应该避免这样做?


1
2
3
4
5
6
7
8
9
10
>>> 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

这就是你要找的吗?这叫做结束。


这样做并不能真正获得很多好处,实际上它会减慢method_a的速度,因为每次调用它时,它都会定义并重新编译另一个函数。考虑到这一点,最好只在函数名前面加上下划线,表示它是一个私有方法,即_method_b

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

更新:

这里有证据表明嵌套它们的速度较慢(使用python 3.6.1),尽管在这个小例子中不太常见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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类的方法)。而且,与您的函数不同,嵌套函数实际上是在该版本中调用的。


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

(对于什么使一个闭包成为一个闭包,有很多争论。)

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

1
2
3
4
def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

使用中:

1
2
3
4
5
6
7
8
9
10
11
>>> 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文档中,它大致相当于:

1
2
3
4
5
6
7
8
9
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的评论。)


通常,不,不定义函数内部的函数。

除非你有充分的理由。你不知道。

为什么不呢?

  • 它防止了单元测试中容易出现的钩子。你在进行单元测试,是吗?
  • 实际上,它并没有完全混淆它,更安全的做法是假设Python中没有任何内容。
  • 使用标准的python automagic代码样式指南来封装方法。
  • 每次运行外部函数时,都将不必要地为相同的代码重新创建函数对象。
  • 如果您的函数真的那么简单,那么您应该使用lambda表达式。

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

当你真正想要的是一个丁当关闭。


实际上,可以在另一个函数中声明一个函数。这是特别有用的创建装饰。

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


我发现这个问题是因为我想提出一个问题,为什么使用嵌套函数会影响性能。我在带有四核2.5GHz Intel i5-2530M处理器的Windows笔记本电脑上使用python 3.2.5测试了以下功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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:

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

得到以下结果

1
2
3
4
5
6
7
8
9
10
>>>
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中找到。


因此,归根结底,很大程度上是一个关于Python实现有多智能的问题,特别是在内部函数不是一个闭包,而只是一个只需要帮助的函数内函数的情况下。

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

而当内部函数是一个闭包时,即使该函数没有从包含函数中返回以供其他地方使用,也可以帮助实现相当多的清晰性。

所以我会说,一般都要使用它们,但是当您真正关心性能时,要注意可能会对性能造成的影响,并且只有当您进行实际的分析以显示最好地删除它们时才删除它们。

不要过早地优化只在编写的所有Python代码中使用"内部函数bad"。拜托。


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

使用python,最好避免在外层空间(模块或类)中暴露api,函数是一个很好的封装位置。

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

  • 内部函数仅由外部函数使用。
  • 内部函数有一个很好的名字来解释它的目的,因为代码会说话。
  • 您的同事(或其他代码阅读器)无法直接理解代码。
  • 尽管如此,滥用此技术可能会导致问题并暗示设计缺陷。

    从我的经验来看,可能误解了你的问题。


    MDLP的回答对我不起作用。

    这确实是:

    1
    2
    3
    4
    5
    6
    def some_function():
        return some_other_function()
    def some_other_function():
        return 42

    print some_function()


    做一些类似的事情:

    1
    2
    3
    4
    def some_function():
        some_other_function()
    def some_other_function():
        return 42

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

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


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

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

    否则,一些数据将成为函数。

    在模块级别拥有它将允许其他函数使用方法_B(),如果您正在使用类似sphinx(和autodoc)的东西进行文档编制,它也将允许您对方法_B进行文档编制。

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


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

    a)使用不带全局变量的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)使用全局函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    _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)在另一个函数中使用函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)允许在外部函数范围内使用变量,而不需要在内部函数中声明它们。在某些情况下可能有用。