字节码处理说明
LOAD_FAST_AND_CLEAR
操作码, Expr.undef
IR 节点, UndefVar
类型
Python 3.12 引入了一个新的字节码 LOAD_FAST_AND_CLEAR
,它仅用于推导式(comprehensions)。常见模式是
In [1]: def foo(x):
...: # 6 LOAD_FAST_AND_CLEAR 0 (x) # push x and clear from scope
...: y = [x for x in (1, 2)] # comprehension
...: # 30 STORE_FAST 0 (x) # restore x
...: return x
...:
In [2]: import dis
In [3]: dis.dis(foo)
1 0 RESUME 0
3 2 LOAD_CONST 1 ((1, 2))
4 GET_ITER
6 LOAD_FAST_AND_CLEAR 0 (x)
8 SWAP 2
10 BUILD_LIST 0
12 SWAP 2
>> 14 FOR_ITER 4 (to 26)
18 STORE_FAST 0 (x)
20 LOAD_FAST 0 (x)
22 LIST_APPEND 2
24 JUMP_BACKWARD 6 (to 14)
>> 26 END_FOR
28 STORE_FAST 1 (y)
30 STORE_FAST 0 (x)
5 32 LOAD_FAST_CHECK 0 (x)
34 RETURN_VALUE
>> 36 SWAP 2
38 POP_TOP
3 40 SWAP 2
42 STORE_FAST 0 (x)
44 RERAISE 0
ExceptionTable:
10 to 26 -> 36 [2]
Numba 处理 LOAD_FAST_AND_CLEAR
字节码的方式与 CPython 不同,因为它依赖于静态而非动态语义。
在 Python 中,推导式可以遮蔽(shadow)来自外部函数作用域的变量。为了处理这种情况,LOAD_FAST_AND_CLEAR
会快照(snapshot)一个可能被遮蔽的变量的值,并从作用域中清除它。这给人一种推导式在新作用域中执行的错觉,尽管在 Python 3.12 中它们是完全内联的。快照的值稍后会在推导式之后使用 STORE_FAST
恢复。
由于 Numba 使用静态语义,它无法精确地模拟 LOAD_FAST_AND_CLEAR
的动态行为。相反,Numba 会检查变量是否在之前的操作码中使用,以确定它是否必须已定义。如果是,Numba 会像处理常规的 LOAD_FAST
一样处理它。否则,Numba 会发出一个 Expr.undef IR 节点,将栈值标记为未定义。类型推断会将 UndefVar 类型分配给此节点,从而允许该值被零初始化并隐式转换为其他类型。
在对象模式下,Numba 使用 _UNDEFINED 哨兵对象来指示未定义的值。
如果使用了未定义的值,Numba 不会引发 UnboundLocalError
。
特殊情况 1:LOAD_FAST_AND_CLEAR
可能会加载一个未定义的变量
In [1]: def foo(a, v):
...: if a:
...: x = v
...: y = [x for x in (1, 2)]
...: return x
在上面的示例中,变量 x
在列表推导式之前可能已定义,也可能未定义,这取决于 a
的真值。如果 a
为 True
,则 x
已定义,执行按照常见情况进行。然而,如果 a
为 False
,则 x
未定义。在这种情况下,Python 解释器会在 return x
行引发 UnboundLocalError
。Numba 无法确定 x
是否之前已定义,因此它假定 x
已定义以避免错误。这偏离了 Python 的官方语义,因为即使 x
之前未定义,Numba 也会使用一个零初始化的 x
。
In [1]: from numba import njit
In [2]: def foo(a, v):
...: if a:
...: x = v
...: y = [x for x in (1, 2)]
...: return x
...:
In [3]: foo(0, 123)
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
Cell In[3], line 1
----> 1 foo(0, 123)
Cell In[2], line 5, in foo(a, v)
3 x = v
4 y = [x for x in (1, 2)]
----> 5 return x
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
In [4]: njit(foo)(0, 123)
Out[4]: 0
如上例所示,Numba 不会引发 UnboundLocalError
,并允许函数正常返回。
特殊情况 2:LOAD_FAST_AND_CLEAR
加载未定义变量
如果 Numba 能够静态确定一个变量必须是未定义的,类型系统将引发 TypingError
,而不是像 Python 解释器那样引发 NameError
。
In [1]: def foo():
...: y = [x for x in (1, 2)]
...: return x
...:
In [2]: foo()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 1
----> 1 foo()
Cell In[1], line 3, in foo()
1 def foo():
2 y = [x for x in (1, 2)]
----> 3 return x
NameError: name 'x' is not defined
In [3]: from numba import njit
In [4]: njit(foo)()
---------------------------------------------------------------------------
TypingError Traceback (most recent call last)
Cell In[4], line 1
----> 1 njit(foo)()
File /numba/numba/core/dispatcher.py:468, in _DispatcherBase._compile_for_args(self, *args, **kws)
464 msg = (f"{str(e).rstrip()} \n\nThis error may have been caused "
465 f"by the following argument(s):\n{args_str}\n")
466 e.patch_message(msg)
--> 468 error_rewrite(e, 'typing')
469 except errors.UnsupportedError as e:
470 # Something unsupported is present in the user code, add help info
471 error_rewrite(e, 'unsupported_error')
File /numba/numba/core/dispatcher.py:409, in _DispatcherBase._compile_for_args.<locals>.error_rewrite(e, issue_type)
407 raise e
408 else:
--> 409 raise e.with_traceback(None)
TypingError: Failed in nopython mode pipeline (step: nopython frontend)
NameError: name 'x' is not defined