That depends. Loading a local variable in non-optimized code (e.g. module, class body, or exec) uses the LOAD_NAME opcode. For example:
>>> dis.dis(compile('x = 0; x', '', 'exec'))
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (x)
4 LOAD_NAME 0 (x)
6 POP_TOP
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
LOAD_NAME falls back on globals and builtins, which makes it easy to temporarily shadow a global or builtin name. For example:
>>> exec(r'''
... abs = 'temporary shadow'
... print(abs)
... del abs
... print(abs)
... ''')
temporary shadow
<built-in function abs>
This won’t work in optimized code (e.g. a function created by the def statement), which uses the LOAD_FAST opcode to load a local variable that’s in the fast locals array, which does not fall back on globals and builtins. For example:
>>> def f():
... abs = 'temp'
... del abs
... abs
...
>>> dis.dis(f)
2 0 LOAD_CONST 1 ('temp')
2 STORE_FAST 0 (abs)
3 4 DELETE_FAST 0 (abs)
4 6 LOAD_FAST 0 (abs)
8 POP_TOP
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in f
UnboundLocalError: local variable 'abs' referenced before assignment
In optimized code, one has to explicitly reset back to a known global or builtin reference, such as abs = builtins.abs.