【问题标题】:Python: encapsulation in frequently called functionPython:封装在经常调用的函数中
【发布时间】:2025-12-30 21:00:16
【问题描述】:

我有这个 Python 3 伪代码:

def f1():
    a, b, c, d, e, f = some_other_fn()
    if (condition):
        f2(a, b, c, d, e, f)

def f2(a, b, c, d, e, f):
    complex_set_of_operations_with(a, b, c, d, e, f)

for i in range(1000):
    f(1)

现在,我对f2() 中冗长的签名和重复感到有些恼火,并想将其封装到f1() 中:

def f1():
    def f2():
        complex_set_of_operations_with(a, b, c, d, e, f)

    a, b, c, d, e, f = some_other_fn()
    if (condition):
        f2()

for i in range(1000):
    f(1)

现在,我的问题是:如果我运行 f1() 一千次,解释器是否必须解析 f2() 一千次,或者它是否足够聪明以创建可重用的引用?

【问题讨论】:

  • 为什么不def f2(*args): complex_set_of_operations_with(*args)
  • 您是递归调用f2,还是在f1 的多个地方调用?如果没有,您可以完全删除 f2 并将其代码内联到 f1
  • @Chris_Rands:我必须使用abcdeff2() 中的不同位置,我找到了明确声明它们更具可读性。
  • @tobias_k 我只在f1 中调用它,这也是我想封装它的原因。我可以内联它,但我喜欢将操作代码与触发它的条件分开。这主要是一个风格和可读性问题——不可否认,还增加了一些学术好奇心。

标签: python performance encapsulation


【解决方案1】:

Python 评估是懒惰的。只有在实际需要时才会对其进行评估。

https://swizec.com/blog/python-and-lazy-evaluation/swizec/5148

Lazy evaluation python

【讨论】:

  • 这能回答问题吗? OP 没有询问是否会在运行 f1 之前评估 f2,而是询问是否每次运行 f1 时都会再次评估它。此外,博客文章和链接的答案都是关于生成器的,这是完全不同的。
【解决方案2】:

让我们看看(使用我手头的 Python 3.5)。我们将使用dis 模块来反汇编函数并检查其字节码:

>>> def f1():
...     def f2():
...         complex_set_of_operations_with(a, b, c, d, e, f)
...     a, b, c, d, e, f = some_other_fn()
...     if (condition):
...         f2()
... 
>>> import dis
>>> dis.dis(f1)
  2           0 LOAD_CLOSURE             0 (a)
              3 LOAD_CLOSURE             1 (b)
              6 LOAD_CLOSURE             2 (c)
              9 LOAD_CLOSURE             3 (d)
             12 LOAD_CLOSURE             4 (e)
             15 LOAD_CLOSURE             5 (f)
             18 BUILD_TUPLE              6
             21 LOAD_CONST               1 (<code object f2 at 0x7f5d58589e40, file "<stdin>", line 2>)
             24 LOAD_CONST               2 ('f1.<locals>.f2')
             27 MAKE_CLOSURE             0
             30 STORE_FAST               0 (f2)
             ...  # the rest is omitted for brevity

在运行时,Python 解释器会一一解释这些原始字节码指令。这些说明在documentation 中进行了解释。

正如上面示例中的最后四个指令所暗示的那样,Python 确实每次都构建内部函数(并将其存储在名称 f2 下),但它似乎通过加载预编译常量来有效地做到这一点 f2 (21 LOAD_CONST) 行的>代码对象,即它不会一遍又一遍地编译f2 的主体。

【讨论】:

  • 出色的洞察力。谢谢。顺便说一句,我也在使用 3.5,所以这就像手套一样。