Invoking the python 3.10.6 interpreter with no arguments produces the following output in the presence of a (possibly empty) file called dis.py
in the working directory.
Python 3.10.6 (main, Aug 30 2022, 04:58:14) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin Type "help", "copyright", "credits" or "license" for more information. Failed calling sys.__interactivehook__ Traceback (most recent call last): File "/opt/homebrew/Cellar/python@3.10/3.10.6_2/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site.py", line 447, in register_readline import rlcompleter File "/opt/homebrew/Cellar/python@3.10/3.10.6_2/Frameworks/Python.framework/Versions/3.10/lib/python3.10/rlcompleter.py", line 34, in <module> import inspect File "/opt/homebrew/Cellar/python@3.10/3.10.6_2/Frameworks/Python.framework/Versions/3.10/lib/python3.10/inspect.py", line 59, in <module> for k, v in dis.COMPILER_FLAG_NAMES.items(): AttributeError: module 'dis' has no attribute 'COMPILER_FLAG_NAMES' >>>
Clearly I shouldn’t have my own files called dis.py
lying around in the working directory! Are the names of all of the other modules included by
inspect.py
also “reserved”? Shouldn’t the modules inspect.py imports be somehow fully qualified to stop this from happening? Any suggestions on what best practice might be would be helpful. This is a surprising behaviour and I am not aware of any warnings about it.
Advertisement
Answer
I was equally surprised and alarmed after reading this question. I often run python with no arguments just to do some arithmetic or call help(), and I’ve never stopped to worry about what directory I’m in and whether I trust the code in it.
I did some digging and found that it’s because of the order of initialization of the interpreter: A bunch of stuff is imported, then an empty string (meaning the current working directory) is prepended to sys.path, then site.py is imported. And for interactive shells, site.py imports readline and rlcompleter, which imports inspect, which imports many things, like dis and ast. If the current working directory contains readline.py or rlcompleter.py or ast.py or dis.py etc. it will be executed. In Python 3.8 and earlier rlcompleter did not import inspect, and so the number of problematic names was smaller (just readline and rlcompleter as far as I can tell).
This has been addressed in Python 3.11.0 beta 1, in two ways:
gh-92345: pymain_run_python() now imports readline and rlcompleter before sys.path is extended to include the current working directory of an interactive interpreter.
gh-57684: Add the -P command line option and the PYTHONSAFEPATH environment variable to not prepend a potentially unsafe path to sys.path.
Note that -P and PYTHONSAFEPATH address a broader issue. Even non-interactive python interpreters normally prepend something to sys.path, either the current working directory or the directory containing the main script, and that can have malicious or accidental effects. Disabling that sys.path modification avoids those pitfalls but is backward-incompatible with some existing code. Still, I hope -P can become the default behavior someday.
What can you do today, with Python 3.10 (or earlier)? One idea would be to set your PYTHONSTARTUP environment variable (which affects only interactive interpreters) to point to a script that removes any empty string from the front of sys.path, like so:
import sys if sys.path and not sys.path[0]: sys.path.pop(0)
On occasions where you need to interactively import things from the current directory, you can either start the python shell with PYTHONSTARTUP= python3
or interactively enter sys.path.insert(0,'')
.