i have the following situation which i don't understand maybe you can point me to the answer or explain it to me.
I have the following python file structure structure:
project/
-folder_a/
-File_a
-folder_b/
-File_b
File_a is importing File_b. File_a is the main file but i can only run it from the project folder if i call it like this.
python < folder_a/File_a
Otherwise i get an import error that the File_b can not be imported. I know that the "<" symbol is a rediret of the stdin but what is it doing to the python interpreter and why is it only working this way.
Thanks a lot, make-ing
Python can run code a few different ways: you can give it a script to run, or a module with -m
, or a command with -c
. But if you don't give it any of those, it reads standard input and executes one statement at a time until EOF.
You're used to seeing this with the interactive interpreter:
$ python
Python 2.7.10 (default, Oct 6 2017, 22:29:07)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print('hello')
hello
>>> ^D
$
It read that print('hello')
off standard input and executed it. Then it read the ctrl-D as an EOF and exited.
If standard input isn't an interactive console (effectively, if not sys.stdin.isatty():
), it doesn't print the banner, show the >>>
prompts, enable readline
command-line editing, etc. But it still reads and executes statements one by one until EOF.
When you do python < something.py
, your shell is piping the file something.py
into Python's standard input. Since that file isn't an interactive console, Python doesn't do all the interactive stuff; it just reads and executes statements out of the script.
This is similar to running python something.py
, but not identical.
The biggest difference is that Python has no idea what script you're giving it; it can only see the contents of the file, not the filename or anything else, and it can't even tell they're coming from a file rather than being, e.g., piped from another program.
If you look at how sys.path
works:
As initialized upon program startup, the first item of this list,
path[0]
, is the directory containing the script that was used to invoke the Python interpreter. If the script directory is not available (e.g. if the interpreter is invoked interactively or if the script is read from standard input),path[0]
is the empty string, which directs Python to search modules in the current directory first.
So, in effect, python folder_a/File_a.py
puts ./folder_a
on sys.path
, but python < folder_a/File_a.py
puts .
on sys.path
.
This is really not a good solution to your problem, but it doesn't explain why things are mostly working.
A better solution is to reorganize your code so that you have packages full of modules you want to import, and then any top-level scripts that you want to run outside of those packages. Like this:
project/
script.py
-pkg_a/
-__init__.py
-module_a.py
-pkg_b/
-__init__.py
-module_b.py
Those __init__.py
files aren't actually necessary in Python 3, but they signal (both to the Python interpreter, and to your reader) that these are "ordinary packages" (as opposed to namespace packages, or directories that aren't packages at all).
Now, script.py
can import
and run the code from module_a.py
the same as any other Python code. For example, instead of this:
# pkg_a/module_a.py
print('hello')
… do this:
# pkg_a/module_a.py
def run():
print('hello')
# script.py
from pkg_a.module_a import run
run()
If you plan to use setuptools
to make your code installable via pip
, you can go even farther—specify pkg_a.module_a.run
as an "entry point", and pip
will create that script.py
for you, make sure it executable, set up the shbang line for the user's particular Python, get it installed somewhere on the user's path, etc.
If something about your design makes it impossible or not appropriate to move the "script" code out of your module and into a separate script, you can always just run it as a module, the same way you do with the ones in the stdlib:
$ python -m pip install spam
<installs the spam package>
$ echo '[{"dense":"json"}]' | python -m json.tool
[
{
"dense": "json"
}
]
$ python -m pkg_a.module_a
<runs the code in pkg_a/module_a.py as a module>