I have a python script with the following contents:
# foo.py
__builtins__ = 3
del __builtins__
print(int) # <- this still works
Curiously, executing this script with the -i
flag prevents only the REPL from accessing builtins:
aran-fey@starlight ~> python3 -i foo.py
<class 'int'>
>>> print(int)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined
How come the script can access builtins, but the REPL can't?
CPython doesn't look up __builtins__
every time it needs to do a built-in variable lookup. Each frame object has an f_builtins
member holding its built-in variable dict, and built-in variable lookup goes through there.
f_builtins
is set on frame object creation. If a new frame has no parent frame (f_back
), or a different global variable dict from its parent frame, then frame object initialization looks up __builtins__
to set f_builtins
. (If the new frame shares a global dict with its parent frame, then it inherits its parent's f_builtins
.) This is the only way __builtins__
is involved in built-in variable lookup. You can see the code that handles this in _PyFrame_New_NoTrack
.
When you delete __builtins__
inside a script, that doesn't affect f_builtins
. The rest of the code executing in the script's stack frame still sees builtins. Once the script completes and -i
drops you into interactive mode, every interactive command gets a new stack frame (with no parent), and the __builtins__
lookup is repeated. This is when the deleted __builtins__
finally matter.