Python 可以通过几种不同的方式运行代码:你可以给它一个运行脚本,或者一个带有-m 的模块,或者一个带有-c 的命令。但是如果你不给它任何一个,它会读取标准输入并一次执行一条语句,直到 EOF。
您已经习惯使用交互式解释器看到这一点:
$ 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
$
它读取print('hello') 关闭标准输入并执行它。然后它将 ctrl-D 读取为 EOF 并退出。
如果标准输入不是交互式控制台(实际上是if not sys.stdin.isatty():),它不会打印横幅、显示>>> 提示、启用readline 命令行编辑等。但它仍然显示并一一执行语句,直到 EOF。
当您执行python < something.py 时,您的shell 会将文件something.py 传送到Python 的标准输入中。由于该文件不是交互式控制台,因此 Python 不会执行所有交互式操作;它只是从脚本中读取并执行语句。
这类似于运行python something.py,但不完全相同。
最大的不同是 Python 不知道你给它的是什么脚本。它只能看到文件的内容,而不是文件名或其他任何内容,它甚至无法判断它们来自文件,而不是来自另一个程序的管道。
如果你看看sys.path 的工作原理:
在程序启动时初始化,此列表的第一项 path[0] 是包含用于调用 Python 解释器的脚本的目录。如果脚本目录不可用(例如,如果交互调用解释器或从标准输入读取脚本),path[0] 是空字符串,它指示 Python 首先搜索当前目录中的模块。
因此,实际上,python folder_a/File_a.py 将 ./folder_a 放在 sys.path 上,但 python < folder_a/File_a.py 将 . 放在 sys.path 上。
这确实不是解决您的问题的好方法,但它不能解释为什么事情大部分都在工作。
一个更好的解决方案是重新组织您的代码,以便您拥有包含您想要导入的模块的包,然后是您想要在这些包之外运行的任何顶级脚本。像这样:
project/
script.py
-pkg_a/
-__init__.py
-module_a.py
-pkg_b/
-__init__.py
-module_b.py
那些__init__.py 文件在 Python 3 中实际上并不是必需的,但它们向 Python 解释器和您的读者发出信号,表明这些是“普通包”(与命名空间包或目录相反)根本不打包)。
现在,script.py 可以通过import 运行来自module_a.py 的代码,就像运行任何其他 Python 代码一样。例如,而不是这个:
# pkg_a/module_a.py
print('hello')
……这样做:
# pkg_a/module_a.py
def run():
print('hello')
# script.py
from pkg_a.module_a import run
run()
如果您打算使用setuptools 使您的代码可通过pip 安装,您可以走得更远——将pkg_a.module_a.run 指定为“入口点”,pip 将为您创建script.py ,确保它可执行,为用户的特定 Python 设置 shbang 行,将其安装在用户路径的某个位置,等等。
如果您的设计导致无法或不适合将“脚本”代码从模块中移出并放入单独的脚本中,您始终可以将其作为模块运行,就像在标准库:
$ 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>