涨见识了,在终端执行 Python 代码的 6 种方式!

原做:BRETT CANNONhtml

译者:豌豆花下猫@Python猫python

英文:https://snarky.ca/the-many-ways-to-pass-code-to-python-from-the-terminallinux

为了咱们推出的 VS Code 的 Python 插件 [1],我写了一个简单的脚原本生成变动日志 [2](相似于Towncrier [3],但简单些,支持 Markdown,符合咱们的需求)。在发布过程当中,有一个步骤是运行python news ,它会将 Python 指向咱们代码中的"news"目录。git

前几天,一位合做者问这是如何工做的,彷佛咱们团队中的每一个人都知道如何使用-m ?(请参阅个人有关带 -m 使用 pip 的文章 [4],了解缘由)(译注:关于此话题,我也写过一篇更为详细的文章github

这使我意识到其余人可能不知道有五花八门的方法能够将 Python 指向要执行的代码,所以有了这篇文章。shell

一、经过标准输入和管道

由于如何用管道传东西给一个进程是属于 shell 的内容,我不打算深刻解释。毋庸置疑,你能够将代码传递到 Python 中。windows

# 管道传内容给 python
echo "print('hi')" | python

若是将文件重定向到 Python,这显然也能够。缓存

# 重定向一个文件给 python
python < spam.py

归功于 Python 的 UNIX 传统,这些都不太使人感到意外。app

二、经过-c 指定的字符串

若是你只须要快速地检查某些内容,则能够在命令行中将代码做为字符串传递。函数

# 使用 python 的 -c 参数
python -c "print('hi')"

当须要检查仅一行或两行代码时,我我的会使用它,而不是启动 REPL(译注:Read Eval Print Loop,即交互式解释器,例如在 windows 控制台中输入python, 就会进入交互式解释器。-c 参数用法能够省去进入解释器界面的过程) 。

三、文件的路径

最众所周知的传代码给 python 的方法极可能是经过文件路径。

# 指定 python 的文件路径
python spam.py

要实现这一点的关键是将包含该文件的目录放到sys.path 里。这样你的全部导入均可以继续使用。但这也是为何你不能/不该该传入包含在一个包里的模块路径。由于sys.path 可能不包含该包的目录,所以全部的导入将相对于与你预期的包不一样的目录。

四、对包使用 -m

执行 Python 包的正确方法是使用 -m 并指定要运行的包名。

python -m spam

它在底层使用了runpy [5]。要在你的项目中作到这点,只须要在包里指定一个__main__.py 文件,它将被当成__main__ 执行。并且子模块能够像任何其它模块同样导入,所以你能够对其进行各类测试。

我知道有些人喜欢在一个包里写一个main 子模块,而后将其__main__.py 写成:

from . import main

if __name__ == "__main__":
    main.main()

就我我的而言,我不感冒于单独的main 模块,而是直接将全部相关的代码放入__main__.py ,由于我感受这些模块名是多余的。

(译注:即做者不关心做为入口文件的"main"或者“__main__”模块,由于执行时只需用它们的包名便可。我认为这也暗示了入口模块不应再被其它模块 import。我上篇文章 [6]比做者的观点激进,认为连那句 if 语句都不应写。)

五、目录

定义__main__.py也能够扩展到目录。若是你看一下促成此博客文章的示例,python news 可执行,就是由于 news 目录有一个 __main__.py 文件。该目录就像一个文件路径被 Python 执行了。

如今你可能会问:“为何不直接指定文件路径呢?”好吧,坦白说,关于文件路径,有件事得说清楚。😄在发布过程当中,我能够简单地写上说明,让运行python news/announce.py ,可是并无确切的理由说明这种机制什么时候存在。

再加上我之后能够更改文件名,并且没人会注意到。再加上我知道代码会带有辅助文件,所以将其放在目录中而不是单独做为单个文件是有意义的。

固然,我也能够将它变为一个使用 -m 的包,可是不必,由于 announce 脚本很简单,我知道它要保持成为一个单独的自足的文件(少于 200 行,而且测试模块也大约是相同的长度)。

何况,__main__.py 文件很是简单。

import runpy
# Change 'announce' to whatever module you want to run.
runpy.run_module('announce', run_name='__main__', alter_sys=True)

如今显然必需要处理依赖关系,可是若是你的脚本仅使用标准库或将依赖模块放在__main__.py 旁边(译注:即同级目录),那么就足够了!

(译注:我以为做者在此有点“炫技”了,由于这种写法的前提是得知道 runpy 的用法,可是就像前一条所写的用 -m 参数运行一个包,在底层也是用了 runpy。不过炫技的好处也很是明显,即__main__.py 里不用导入 announce 模块,仍是以它为主模块执行,也就不会破坏原来的依赖导入关系)

六、执行一个压缩文件

若是你确实有多个文件和/或依赖模块,而且但愿将全部代码做为一个单元发布,你能够用一个__main__.py ,放置在一个压缩文件中,并把压缩文件所在目录放在 sys.path 里,Python 会替你运行__main__.py 文件。

# 将一个压缩包传给 Python
python app.pyz

人们如今习惯上用 .pyz 文件扩展名来命名此类压缩文件,但这纯粹是传统,不会影响任何东西;你固然也能够用 .zip 文件扩展名。

为了简化建立此类可执行的压缩文件,标准库提供了zipapp [7]模块。它会为你生成__main__.py并添加一条组织行(shebang line),所以你甚至不须要指定 python,若是你不想在 UNIX 上指定它的话。若是你想移动一堆纯 Python 代码,这是一种不错的方法。

不幸的是,仅当压缩文件包含的全部代码都是纯 Python 时,才能这样运行压缩文件。执行压缩文件对扩展模块无效(这就是为何 setuptools 有一个 zip_safe [8]标志的缘由)。(译注:扩展模块 extension module,即 C/C++ 之类的非 Python 文件)

要加载扩展模块,Python 必须调用 dlopen() [9]函数,它要传入一个文件路径,但当该文件路径就包含在压缩文件内时,这显然不起做用。

我知道至少有一我的与 glibc 团队交谈过,关于支持将内存缓冲区传入压缩文件,以便 Python 能够将扩展模块读入内存,并将其传给压缩文件,可是若是内存为此服务,glibc 团队并不一样意。

可是,并不是全部但愿都丧失了!你可使用诸如shiv [10]之类的项目,它会捆绑(bundle)你的代码,而后提供一个__main__.py 来处理压缩文件的提取、缓存,而后为你执行代码。尽管不如纯 Python 解决方案理想,但它确实可行,而且在这种状况下算得上是优雅的。

(译注:翻译水平有限,不免误差。我加注了部份内容,但愿有助于阅读。请搜索关注“Python猫”,阅读更多优质的原创或译做。)

参考连接

[0] https://snarky.ca/the-many-ways-to-pass-code-to-python-from-the-terminal/

[1] https://marketplace.visualstudio.com/items?itemName=ms-python.python

[2] https://github.com/microsoft/vscode-python/tree/master/news

[3] https://pypi.org/project/towncrier

[4] https://snarky.ca/why-you-should-use-python-m-pip

[5] https://docs.python.org/3/library/runpy.html#module-runpy

[6] https://mp.weixin.qq.com/s/1ehySR5NH2v1U8WIlXflEQ

[7] https://docs.python.org/3/library/zipapp.html#module-zipapp

[8] https://setuptools.readthedocs.io/en/latest/setuptools.html#setting-the-zip-safe-flag

[9] https://linux.die.net/man/3/dlopen

[10] https://pypi.org/project/shiv/

相关文章
相关标签/搜索