【问题标题】:Is nested function a good approach when required by only one function? [closed]当只有一个函数需要时,嵌套函数是一种好方法吗? [关闭]
【发布时间】:2011-06-17 10:19:36
【问题描述】:

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

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

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

def method_b(arg):
    return some_data

在 Python 中,我们可以在另一个 def 中声明 def。所以,如果method_b 是必需的并且只能从method_a 调用,我应该在method_a 中声明method_b 吗?像这样:

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

    some_data = method_b(arg)

或者我应该避免这样做吗?

【问题讨论】:

  • 你不需要在另一个函数中定义一个函数,除非你正在做一些非常时髦的事情。但是,请详细说明您要做什么,以便我们提供更有帮助的答案
  • 您是否意识到第二个示例不同,因为您没有调用 method_b? (@inspector:严格来说,你确实需要,但是当你进入一些函数式编程,特别是闭包时,它非常有用)。
  • @delnan:我认为您的意思是“严格来说,您不需要,但是......”
  • 正如@delnan 提到的,这在闭包的情况下很常见,所以我不认为它有资格作为时髦;但是,除非需要闭包(我猜在这种情况下不需要闭包),否则将一个函数放入另一个函数似乎没有必要、高效或整洁。除非你需要闭包,否则我会坚持第一种模式。
  • 内部函数的用例在链接中总结得很精彩:https://realpython.com/blog/python/inner-functions-what-are-they-good-for/。如果您的使用不适合任何情况,最好避免使用。

标签: python coding-style nested-function


【解决方案1】:
>>> 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

这就是你要找的吗?它被称为closure

【讨论】:

  • 为什么不直接做 def sum(x,y): return x+y?
  • @mango:这只是一个传达概念的玩具示例——在实际使用中,do_it() 所做的可能比单个 @ 中的一些算术可以处理的要复杂一些987654325@声明。
  • @mango 用一个更有用的例子回答了你的问题。 stackoverflow.com/a/24090940/2125392
  • 它没有回答问题。
  • 很好的答案,但问题在哪里?不在这里。 OP询问如果method_b仅由method_a使用,是否可以执行上述操作。他没有问别的。没有关闭。
【解决方案2】:

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

【讨论】:

  • 它实际上并没有在每次调用外部函数时完全编译内部函数,尽管它确实需要创建一个函数对象,这确实需要一些时间。另一方面,函数名变成了局部的而不是全局的,所以调用函数更快。在我的试验中,从时间上讲,大部分时间基本上都是一次洗涤。如果您多次调用内部函数,它甚至可能会更快。
  • 是的,您需要多次调用内部函数。如果您在循环中调用它,或者只是多次调用,则为函数使用本地名称的好处将开始超过创建函数的成本。在我的试验中,当您调用内部函数大约 3-4 次时,就会发生这种情况。当然,您可以通过为函数定义一个本地名称来获得相同的好处(几乎没有那么多成本),例如method_b = self._method_b 然后调用method_b 避免重复的属性查找。 (碰巧我最近一直在做很多事情。:)
  • @kindall:是的,没错。我修改了我的计时测试,所以它对辅助函数进行了 30 次调用,结果发生了变化。在您有机会看到此回复后,我将删除我的答案。感谢您的启发。
  • 只是想解释一下我的 -1,因为这仍然是一个信息丰富的答案:我给这个 -1 是因为它关注一个微不足道的性能差异(在大多数情况下,代码对象的创建只需要函数执行时间的一部分)。在这些情况下需要考虑的重要一点是具有内联函数是否可以提高代码的可读性和可维护性,我认为它经常这样做,因为您不必搜索/滚动文件来查找相关代码。
  • @Blixt:我认为你的逻辑有缺陷(并且投票不公平),因为即使在它不是嵌套的(并且极不可能在不同的文件中)。我的回答中的第一句话也是“你这样做并没有真正获得太多”,这表明这是一个微不足道的区别。
【解决方案3】:

函数内部的函数通常用于closures

(有一个lot of contention 超过what exactly 使a closure a closure。)

这是一个使用内置 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 是一个闭包示例。

From the 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 公平地说,这个问题是相当开放的——与其试图回答所有可能的情况,我认为最好专注于一个。问题也不是很清楚。例如,它在第二个示例中确实意味着关闭,即使这可能不是本意。
  • @Mayou36 “他基本上是在问他展示的概念是否可以用于案例b。”不,问题是问是否应该被使用。 OP 已经知道它可以使用。
  • 好吧,“如果 method_b 是必需的并且只能从 method_a 调用,我应该在 method_a 中声明 method_b 吗?”很清楚,与闭包无关,对吧?是的,我同意,我以 should 的方式使用了 can。但这与闭包无关......当OP提出一个完全不同的问题时,我很惊讶有这么多关于闭包以及如何使用它们的答案。
  • @Mayou36 改写你如何看待这个问题可能会有所帮助,并提出另一个问题来解决它。
【解决方案4】:

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

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

为什么不呢?

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

当你实际上想要的是叮当closure

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

    • 这是可能的,但我同意,你需要一个充分的理由这样做。为仅在您的类中使用的函数使用前面的下划线会更像 python。
    • 在一个类中,是的,但是只在一个函数中呢?封装是分层的。
    • @PaulDraper “封装是分层的” - 不!谁说的?封装是一个更广泛的原则,而不仅仅是一些继承。
    【解决方案6】:

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

    【讨论】:

    • 你做的比较并没有真正的用处。这就像在函数中添加虚拟语句并说它更慢。 martineau 的示例实际上使用了封装的函数,运行他的示例我没有注意到任何性能差异。
    • -1 请重新阅读问题。尽管您可能会说它可能会稍微慢一些,但他问这样做是否是一个好主意,主要不是因为性能,而是因为一般实践。
    【解决方案7】:

    因此,最终主要是一个关于 python 实现是否智能的问题,特别是在内部函数不是闭包而只是 in 函数只需要帮助器的情况下。

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

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

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

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

    【讨论】:

    • 如其他答案所示,您并没有真正遇到性能问题。
    【解决方案8】:

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

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

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

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

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

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

    【讨论】:

      【解决方案9】:

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

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

      否则, some_data 将是函数。

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

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

      【讨论】:

        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          执行以下操作:

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

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

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

          【讨论】:

          • 我很欣赏您在上面的回答中付出的努力,但您的回答直截了当。不错。
          • 为什么?为什么你不应该这样做?这不是很好的封装吗?我缺少任何论据。 -1
          • @Mayou36 在写我的评论时我不知道封装是什么,我现在也不知道它是什么。我只是觉得这样做不好。你能解释一下为什么在另一个函数内部定义一个函数而不是在它外部定义它是有益的吗?
          • 是的,我可以。您可能会查找封装的概念,但简而言之:隐藏不需要的信息,只向用户公开需要知道的信息。这意味着,在外部定义 some_other_function 只是在命名空间中添加了更多内容,而命名空间实际上与第一个函数紧密绑定。或者从变量的角度思考:为什么需要局部变量和全局变量?如果可能的话,在一个函数中定义所有变量要比为仅在这个函数中使用的变量使用全局变量要好得多。这一切都是为了最终降低复杂性。
          【解决方案12】:

          函数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))
          

          【讨论】:

            猜你喜欢
            • 2015-02-18
            • 1970-01-01
            • 2015-08-03
            • 2011-02-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-09-25
            • 2011-03-13
            相关资源
            最近更新 更多