如果仅功能B需要功能A,是否应在B内部定义A?[关闭]


147

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

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

def method_b(arg):
    return some_data

在Python中,我们可以def在另一个内部声明def。因此,如果method_b只需要从中调用method_a,我应该method_b在内部声明method_a吗?像这样 :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

还是应该避免这样做?


7
除非您正在做一些非常时髦的事情,否则您无需在另一个内部定义一个函数。但是,请详细说明您要尝试执行的操作,以便我们提供更有用的答案
inspectorG4dget

6
您是否意识到第二个示例是不同的,因为您不打电话 method_b?(@inspector:严格来说,您确实需要这样做,但是当您进行一些函数式编程(尤其是闭包)时,它非常有用。)

3
@delnan:我认为您的意思是“ 严格来讲,您不需要 ……”
martineau 2011年

4
内部功能的用例在链接中进行了精彩总结:https : //realpython.com/blog/python/inner-functions-what-are-they-good-for/。如果您的用法不适合任何情况,请最好避免使用。

1
好问题,但似乎没有真正的答案……
Mayou36 '17

Answers:


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

这是您要找的东西吗?这叫做闭包


14
这是一个更好的解释。我删除了我的答案
pyfunc 2011年

为什么不只是def sum(x,y):返回x + y?
芒果

4
@mango:这只是一个传达概念的玩具示例-在实际使用do_it()中,可能会比用单个return语句中的某些算术处理的事情复杂一些。
martineau 2014年

2
@mango用一个稍微有用的示例回答了您的问题。stackoverflow.com/a/24090940/2125392
CivFan 2014年

10
它没有回答问题。
洪苏2015年

49

通过这样做,您并没有真正获得太多收益,实际上,它会减慢速度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类的方法)。与您的版本不同,嵌套函数实际上也在该版本中被调用。


21
每次调用外部函数时,它实际上并没有完全编译内部函数,尽管它确实必须创建一个函数对象,这会花费一些时间。另一方面,函数名称变为局部而不是全局名称,因此调用函数更快。在我的试验中,从时间上看,基本上在大多数时候都是洗脸。如果您多次调用内部函数,它甚至可能更快。
kindall 2011年

7
是的,您需要多次调用内部函数。如果您是在一个循环中调用它,或者只是多次调用它,那么为该函数使用本地名称的好处将开始超过创建该函数的成本。在我的试验中,当您调用内部函数约3-4次时,会发生这种情况。当然,您可以通过为函数定义一个本地名称(例如method_b = self._method_b,然后调用method_b以避免重复的属性查找)来获得相同的好处(而不会花费几乎相同的成本)。(碰巧我最近一直在做很多事情的计时。:)
kindall 2011年

3
@kindall:是的,是的。我修改了时序测试,以便对辅助函数进行了30次调用,结果得到了改善。在您有机会看到此答复后,我将删除我的答案。感谢您的启发。
martineau 2011年

2
只是想解释我的-1,因为它仍然是一个有用的答案:我给它一个-1,因为它专注于微不足道的性能差异(在大多数情况下,代码对象的创建只占用函数执行时间的一小部分) )。在这些情况下,需要考虑的重要一点是拥有内联函数是否可以提高代码的可读性和可维护性,我认为通常这样做是因为无需搜索/滚动文件即可找到相关代码。
Blixt

2
@Blixt:我认为您的逻辑是有缺陷的(并且不赞成投票),因为即使没有嵌套,同一个类的另一种方法也极不可能与同一个类的“相距很远”(并且极不可能在另一个文件中)。另外,我的回答中的第一句话是“这样做并不会给您带来真正的收益”,它指出这是微不足道的区别。
martineau

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的评论。)


-1是,它们通常用作闭包。现在重新阅读问题。他基本上问他显示的概念是否可以用于案例b。告诉他,这种情况经常用于情况a,这不是一个不好的答案,但对于这个问题来说是错误的答案。他感兴趣的是,例如进行上述封装是否是一件好事。
Mayou36,2016年

@ Mayou36坦白说,这个问题是开放性的-我不想尝试回答每种可能的情况,我认为最好只关注一个问题。问题也不是很清楚。例如,它确实暗示了第二个示例中的闭包,即使这可能不是原意。
CivFan

@ Mayou36“他基本上问他显示的概念是否可以用于案例b。” 不,问题询问是否使用它。OP已经知道它可以使用。
CivFan

1
好吧,“如果method_b是必需的并且仅从method_a调用,我应该在method_a内声明method_b吗?” 很清楚,与闭包无关,对吗?是的,我同意,我以应得的方式使用了罐头。但这与闭包无关。当OP提出一个完全不同的问题时,令我惊讶的是,这么多有关闭包以及如何使用它们的答案。
Mayou36

@ Mayou36这可能有助于将问题的措辞重新表述,并提出另一个问题来解决。
CivFan

17

通常,不,不要在函数内部定义函数。

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

为什么不?

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

当您真正想要的是当当网


1
这个答案是给你的@ Mayou36。
CivFan

2
是的,谢谢,这是我一直在寻找的答案。这将以最佳方式回答问题。尽管它没有严格说一句,但通过说明原因极大地阻止了内联定义。这就是(pythonic :))的答案!
Mayou36

10

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

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


2
有可能,但我同意,您需要这样做的充分理由。对于只打算在您的类中使用的函数,使用一个下划线会更好一些。
chmullig

3
在类中,是的,但是仅在函数中呢?封装是分层的。
Paul Draper 2013年

@PaulDraper“封装是分层的”-不!谁说的?封装是一个更广泛的原理,而不仅仅是某些继承。
Mayou36

7

我找到了这个问题,因为我想提出一个问题,如果使用嵌套函数,为什么会对性能产生影响。我在带有四核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

我对平方1,平方2和平方5进行了以下20次测量:

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个嵌套函数,则该函数的执行时间是没有嵌套函数的函数的两倍。我认为使用嵌套函数时应谨慎。

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


5
您所做的比较并没有真正的用处。这就像在函数中添加伪语句并说它更慢。martineau的示例实际上使用了封装的函数,通过运行他的示例,我没有发现任何性能差异。
kon心理

-1,请重读该问题。尽管您可能会说它可能会稍微慢一些,但他问这样做是否是一个好主意,这主要不是因为性能,而是因为通常的做法。
Mayou17年

@ Mayou36对不起,问题和答案发布三年后我没有。
miracle173

好的,很不幸,仍然没有可用的(或好的)答案。
Mayou36

4

这只是有关暴露API的原则。

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

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

  1. 内部函数由外部函数使用。
  2. 内部函数具有很好的名称来解释其用途,因为代码可以说明。
  3. 代码无法被您的同事(或其他代码阅读器)直接理解。

即使滥用此技术也可能会引起问题并暗示设计缺陷。

仅根据我的经验,也许会误解您的问题。


4

因此,最后主要是一个关于python实现有多聪明的问题,尤其是在内部函数不是闭包而只是in函数仅需要帮助器的情况下。

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

并且当内部函数是一个闭包时,即使该函数没有从包含函数中返回以供其他地方使用,它也可以大大提高清晰度。

因此,我想通常会使用它们,但要在您真正关心性能时注意性能可能受到的影响,并且只有在进行实际性能分析后最好将它们删除才能删除它们。

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


如其他答案所示,您实际上并没有提高性能。
Mayou17年

1

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

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

否则,some_data将成为函数。

在模块级别拥有它会允许其他函数使用method_b(),如果您使用Sphinx(和autodoc)之类的文档进行记录,那么它也将允许您记录method_b。

如果您正在执行某个对象可以表示的操作,则可能还需要考虑将功能放在类的两个方法中。如果这就是您要查找的内容,那么它也包含了逻辑。


1

做类似的事情:

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

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

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


我感谢您上面的那些人为他们的回答付出的努力,但您的回答是直接和切题的。真好
C0NFUS3D

1
为什么?为什么不这样做?那不是很好的封装吗?我没有任何争论。-1
Mayou17年

@ Mayou36写评论时我不知道封装是什么,我也不知道现在是什么。我只是觉得这不好。您能解释一下为什么在另一个函数中定义一个函数而不是仅仅在它外部定义一个函数的好处吗?
mdlp0716 '09

2
我可以。您可能会查找封装的概念,但总而言之:隐藏不需要的信息,仅向用户公开需要知道的信息。这意味着,在外部定义some_other_function只会向名称空间中添加更多内容,而该名称空间实际上与第一个函数紧密绑定。还是从变量的角度思考:为什么需要局部变量还是全局变量?如果可能的话,在函数中定义所有变量要比仅对一个函数内部使用的变量使用全局变量更好。最终都是为了降低复杂性。
Mayou36

0

您可以使用它来避免定义全局变量。这为您提供了其他设计的替代方案。提供解决方案的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)允许在外部函数范围内使用变量,而无需在内部函数中声明它们。在某些情况下可能有用。


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))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.