Python中闭包的可变默认参数的实例化时间

Python中闭包的可变默认参数的实例化时间,python,python-3.x,closures,bytecode,default-arguments,Python,Python 3.x,Closures,Bytecode,Default Arguments,我的理解是,Python解析函数的源代码时,会将其编译成字节码,但在调用函数之前不会运行字节码(这就是为什么函数中的非法变量名不会引发异常,除非调用函数) 默认参数在函数的初始设置期间不会实例化,但仅在第一次调用函数时才会实例化,无论是否提供参数。默认参数的这个实例将用于所有未来的调用,这可以通过使用可变类型作为默认参数来查看 但是,如果我们将函数放在另一个函数中,那么每次调用外部函数时,默认参数似乎都会重新实例化,如下代码所示: def f(x): def g(y, a=[]):

我的理解是,Python解析函数的源代码时,会将其编译成字节码,但在调用函数之前不会运行字节码(这就是为什么函数中的非法变量名不会引发异常,除非调用函数)

默认参数在函数的初始设置期间不会实例化,但仅在第一次调用函数时才会实例化,无论是否提供参数。默认参数的这个实例将用于所有未来的调用,这可以通过使用可变类型作为默认参数来查看

但是,如果我们将函数放在另一个函数中,那么每次调用外部函数时,默认参数似乎都会重新实例化,如下代码所示:

def f(x):
    def g(y, a=[]):
        a.append(y)
        return a

    for y in range(x, x + 2):
        print('calling g from f:', g(y))
    return g(y + 1)

for x in range(2):
    print('calling f from module scope:', f(x))
这是打印出来的

calling g from f: [0]
calling g from f: [0, 1]
calling f from module scope: [0, 1, 2]
calling g from f: [1]
calling g from f: [1, 2]
calling f from module scope: [1, 2, 3]

这是否意味着每次调用
f
,都会重建
g
的字节码?这种行为似乎没有必要,也很奇怪,因为
f
(包括
g
?)的字节码只构建了一次。或者,它可能只是
g
的默认参数,在每次调用
f

内部函数时,都会使用内部函数的现有字节码重新实例化该参数。使用
dis
很容易看到

>>> import dis
>>> def make_func():
...     def my_func():
...         pass
...     return my_func
>>> dis.dis(make_func.__code__)
  3       0 LOAD_CONST               1 (<code object my_func at [...]", line 3>)
          3 MAKE_FUNCTION            0
          6 STORE_FAST               0 (my_func)

  5       9 LOAD_FAST                0 (my_func)
         12 RETURN_VALUE

内部函数是使用内部函数的现有字节码重建的。使用
dis
很容易看到

>>> import dis
>>> def make_func():
...     def my_func():
...         pass
...     return my_func
>>> dis.dis(make_func.__code__)
  3       0 LOAD_CONST               1 (<code object my_func at [...]", line 3>)
          3 MAKE_FUNCTION            0
          6 STORE_FAST               0 (my_func)

  5       9 LOAD_FAST                0 (my_func)
         12 RETURN_VALUE
第一个误解:“当Python解析函数的源代码时,它会将其编译成字节码,但在调用函数之前不会运行此字节码(这就是为什么函数中的非法变量名不会引发异常,除非您调用函数)。”要澄清的是,您的误解是“除非调用函数,否则函数中的非法变量名不会引发异常”。在函数执行之前,不会捕获未分配的名称

看看这个简单的测试:

In [1]: def g(a):
   ...:     123onetwothree = a
  File "<ipython-input-5-48a83ac30c7b>", line 2
    123onetwothree = a
对于您的示例,每次运行
f
时,都会重新实例化默认参数,因为您在
f
中定义了
g
。最好的方法是将
def
语句看作
函数
对象的构造函数,而默认参数则类似于此构造函数的参数。每个运行
def some_function
时,就像再次调用构造函数一样,函数被重新定义,就像在
f
的主体中编写了
g=function(a=[])
一样

回应评论

[11]中的
def(x=h()):通过
---------------------------------------------------------------------------
NameError回溯(最近一次呼叫上次)
在()
---->1 def f(x=h()):通过
NameError:未定义名称“h”
第一个误解:“当Python解析函数的源代码时,它会将其编译成字节码,但在调用函数之前不会运行此字节码(这就是为什么函数中的非法变量名不会引发异常,除非您调用函数)。”函数中的非法变量名不会引发异常,除非您调用该函数”。在执行该函数之前,不会捕获未分配的名称

看看这个简单的测试:

In [1]: def g(a):
   ...:     123onetwothree = a
  File "<ipython-input-5-48a83ac30c7b>", line 2
    123onetwothree = a
对于您的示例,每次运行
f
时,都会重新实例化默认参数,因为您在
f
中定义了
g
。最好的方法是将
def
语句看作
函数
对象的构造函数,而默认参数则类似于此构造函数的参数。每个运行
def some_function
时,就像再次调用构造函数一样,函数被重新定义,就像在
f
的主体中编写了
g=function(a=[])
一样

回应评论

[11]中的
def(x=h()):通过
---------------------------------------------------------------------------
NameError回溯(最近一次呼叫上次)
在()
---->1 def f(x=h()):通过
NameError:未定义名称“h”

只需使用
dis查看
f
的字节码即可:

dis(f)
  2           0 BUILD_LIST               0
              3 LOAD_CONST               1 (<code object g at 0x7febd88411e0, file "<ipython-input-21-f2ef9ebb6765>", line 2>)
              6 LOAD_CONST               2 ('f.<locals>.g')
              9 MAKE_FUNCTION            1
             12 STORE_FAST               1 (g)

  6          15 SETUP_LOOP              46 (to 64)
             18 LOAD_GLOBAL              0 (range)
             21 LOAD_FAST                0 (x)
             24 LOAD_FAST                0 (x)
             27 LOAD_CONST               3 (2)
             30 BINARY_ADD
             31 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             34 GET_ITER
        >>   35 FOR_ITER                25 (to 63)
             38 STORE_FAST               2 (y)
不包含任何可变结构,它只包含可执行代码和其他不可变信息。您也可以查看一下:

dis(f.__code__.co_consts[1])
  3           0 LOAD_FAST                1 (a)
              3 LOAD_ATTR                0 (append)
              6 LOAD_FAST                0 (y)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 POP_TOP

  4          13 LOAD_FAST                1 (a)
             16 RETURN_VALUE

每次调用
f
,都会调用
MAKE\u函数
,该函数将从已经存在的字节码重新创建函数。

只需使用
dis
查看
f
的字节码即可:

dis(f)
  2           0 BUILD_LIST               0
              3 LOAD_CONST               1 (<code object g at 0x7febd88411e0, file "<ipython-input-21-f2ef9ebb6765>", line 2>)
              6 LOAD_CONST               2 ('f.<locals>.g')
              9 MAKE_FUNCTION            1
             12 STORE_FAST               1 (g)

  6          15 SETUP_LOOP              46 (to 64)
             18 LOAD_GLOBAL              0 (range)
             21 LOAD_FAST                0 (x)
             24 LOAD_FAST                0 (x)
             27 LOAD_CONST               3 (2)
             30 BINARY_ADD
             31 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             34 GET_ITER
        >>   35 FOR_ITER                25 (to 63)
             38 STORE_FAST               2 (y)
不包含任何可变结构,它只包含可执行代码和其他不可变信息。您也可以查看一下:

dis(f.__code__.co_consts[1])
  3           0 LOAD_FAST                1 (a)
              3 LOAD_ATTR                0 (append)
              6 LOAD_FAST                0 (y)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 POP_TOP

  4          13 LOAD_FAST                1 (a)
             16 RETURN_VALUE

每次调用
f
,都会调用
MAKE\u函数
,该函数将从已经存在的字节码中重新创建函数。

您的理解是错误的。函数定义是可执行代码,Python在调用其作用域时会运行该代码。对于模块级函数,当模块不起作用时会发生这种情况rted;对于嵌套函数,调用外部函数时会发生这种情况。无论哪种方式,默认参数都会在该点实例化。很好,这恢复了对称性。内部函数对外部函数的作用与外部函数对模块的作用相同。字节码不会每次都重新生成,而是重用代码对象,如中所述。您的理解是错误的。函数定义是可执行代码,Python会在调用它所在的作用域时运行它。对于模块级函数,这会在导入模块时发生;对于嵌套函数,这会在调用外部函数时发生。无论哪种方式,默认参数都会在该点实例化。很好,这带来了b确认对称性。内部函数与外部函数的关系就像外部函数与模块的关系一样