从`python -m`的用法理解python导入模块的机制

执行python脚本比较常见的一种做法是python your_script.py,在应用中,还有一种写法是指定-m参数,例如:python -m module_name,本文来讨论一下它们的异同。

1、介绍

首先,通过在控制台执行python -h来看一下-m参数的介绍:
在这里插入图片描述

run library module as a script (terminates option list)

将包中的模块当做脚本来执行,并且会终止其他选项(即指定了-m参数后,其他选项失效,也就是说它具有非常高的优先级)。

2、示例

2.1 完整示例代码下载

本文所有的示例代码都上传到代码仓库中,如果读者想复现一下的话,可以先将其克隆到本地,然后按照提示切换到不同的commit版本从而实现对应的功能。

获取代码:

git clone git@codechina.csdn.net:SunJW_2017/use-python-dash-m.git

进入工作目录:

cd use-python-dash-m

至此,读者已经获取到了本文用到的所有代码,只需要在执行示例代码时按照说明检出至指定的提交即可。

2.2 基本用法

在工作目录执行git checkout 2cbd4ec1检出本示例需要的代码

检出代码后,其结构应该如下:

# 结构
use-python-dash-m
├── my_pkg
│   ├── __init__.py
│   └── inner.py
└── outer.py

然后,在工作目录执行python -m my_pkg.inner,控制台应该打印以下内容:

This is my_pkg
This is an outer function
This is an inner function

但如果不指定-m参数:python my_pkg/inner.py,控制台应该会报以下错误:

Traceback (most recent call last):
  File "my_pkg/inner.py", line 1, in <module>
    from outer import outer_fn
ModuleNotFoundError: No module named 'outer'

错误信息很明显,解释器找不到模块outer。如果你熟悉python导入模块的原理,那么大概会有以下感觉:不指定-m参数时,解释器搜索包的目录从当前工作目录下移了一级,即test/my_pkg,因此找不到outer;而指定-m参数时,解释器搜索包的目录就是当前执行命令的目录。

我们验证一下上述想法。

2.3 验证

在工作目录执行git checkout 02dc3c43检出本示例需要的代码

我对inner.py做了修改,在执行python my_pkg/inner.py时,由于由于第三行才会触发错误,因此,可以在控制台打印以下内容(只取错误之前的内容):

current work directoy: /Users/<myname>/Desktop/use-python-dash-m
module search paths: ['/Users/<myname>/Desktop/use-python-dash-m/my_pkg', '/Users/<myname>/opt/anaconda3/lib/python37.zip', '/Users/<myname>/opt/anaconda3/lib/python3.7', '/Users/<myname>/opt/anaconda3/lib/python3.7/lib-dynload', '/Users/<myname>/opt/anaconda3/lib/python3.7/site-packages', '/Users/<myname>/opt/anaconda3/lib/python3.7/site-packages/aeosa']

由此可知,尽管当前的工作目录仍然是use-python-dash-m,但增加的解释器导入包的搜索目录(sys.path第一个值)却下移了一级,自然就找不到outer了。

接下来执行python -m my_pkg.inner,控制台会打印以下内容:

This is my_pkg
current work directoy: /Users/<myname>/Desktop/use-python-dash-m
module search paths: ['/Users/<myname>/Desktop/use-python-dash-m', '/Users/<myname>/opt/anaconda3/lib/python37.zip', '/Users/<myname>/opt/anaconda3/lib/python3.7', '/Users/<myname>/opt/anaconda3/lib/python3.7/lib-dynload', '/Users/<myname>/opt/anaconda3/lib/python3.7/site-packages', '/Users/<myname>/opt/anaconda3/lib/python3.7/site-packages/aeosa']
This is an outer function
This is an inner function

从第三行打印内容可以知道,新增的包搜索目录为当前工作目录,因此可以正常运行。

3、运行机制

简单来说,-m参数的运行机制就是:从当前的sys.path搜索指定的包或模块,然后执行它。如果是模块,则将该模块当做__main__.py来执行,且不需要写.py的后缀;如果为包,则该包目录下必须有一个__main__.py文件。

在这里,模块指的就是一个python脚本,而指的是包含普通python脚本以及特殊文件__init__.py(有可能还包含其他包)的目录。
这种说法的准确性还有待商榷,不过对于理解本文的内容有帮助。

回到2.2节中的例子,在执行python -m my_pkg.inner时,由于inner本身就是一个模块,因此就直接执行它,需要注意的是,在执行inner.py里面的内容之前,先会执行inner.py所在包的__init__.py的内容,这也是控制台先打印“This is my_pkg”的原因。

那如果模块所在的包存在嵌套的情况,这是的执行逻辑是什么呢?

3.1 包嵌套时的执行逻辑

在工作目录执行git checkout 9927e816检出本示例需要的代码

这时代码目录应该如下:

use-python-dash-m
├── my_pkg
│   ├── __init__.py
│   ├── inner.py
│   └── inner_pkg
│       ├── __init__.py
│       └── inner.py
└── outer.py

执行python -m my_pkg.inner_pkg.inner,控制台打印的内容应该如下:

This is my_pkg
This is inner_pkg
This is an outer function
This is an inner function in inner_pkg

由此可见,如果要执行的模块位于嵌套的包内,那么解释器会从外向内逐层执行每个包的__init__.py文件中的内容。

了解了通过-m运行模块时的原理后,再来看如果通过-m运行包时的机制。

前面说,如果需要执行的是一个包,那么该包的一级目录下必须要有一个__main__.py文件才行。接着3.1中例子的代码,执行python -m my_pkg.inner_pkg,控制台应该打印以下内容:

This is my_pkg
This is inner_pkg
/Users/<myname>/opt/anaconda3/bin/python: No module named my_pkg.inner_pkg.__main__; 'my_pkg.inner_pkg' is a package and cannot be directly executed

这些内容足够的明确了:由于执行的一个包而不是模块,且该包中没有一个名为__main__.py的模块,因此,解释器无法直接执行。这也就意味着,执行一个包时,其实是要执行该包的一级目录下的__main__.py模块。

3.2 执行一个包

在工作目录执行git checkout b82f505f检出本示例需要的代码

这时再执行python -m my_pkg.inner_pkg,控制台应该打印以下内容:

This is my_pkg
This is inner_pkg
I'm a main function in package inner_pkg

4、用处

关于-m参数的用处,参考内容2中列出了一些非常棒的例子,这里不再重复,只列举一个我工作中遇到的问题。

问题的背景是需要用多进程来将一项耗时任务分配到子进程中来执行,而主进程立即返回。

耗时任务的入口是一个可以接收命令行参数的python脚本,且位于某个包中——这样它就成了一个模块。其结构大概如下:

my_project
├── my_pkg
│   ├── __init__.py
│   └── long_time_task_script.py
└── config.py

而且这个耗时脚本中还需要读取config.py中的某些参数,如果不使用-m命令,在my_project目录下执行python ./my_pkg/long_time_task_script.py,那么它将无法找到config.py这个模块(原因如上所述)(虽然我通过向Subprocess.Popen方法中的env参数新增PYTHONPATH变量也能解决路径问题,但我总觉得这不够pythonic)。

这时,-m参数就排上用场了,可以在my_project目录下执行python -m my_pkg.long_time_task_script优雅地解决这个问题。

5、参考内容

  1. Python -m 参数解析
  2. 关于 Python 命令中的 -m 参数
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python-turtle模块Python语言的一个标准库,它是一个基于Tkinter的Python图形库,可以用来实现海龟绘图功能。Python-turtle模块使用海龟绘图语言来绘制图形,这种语言是由Seymour Papert在上世纪80年代发明的,旨在帮助儿童学习编程。Python-turtle模块提供了一种交互式的方式来绘制图形,比较适合初学者学习。 Python-turtle模块通过创建一个画布和一个海龟对象来实现图形绘制。画布是用来显示绘制的图形的窗口,海龟对象则是用来绘制图形的工具。Python-turtle模块中提供了许多绘图函数和方法,例如`forward()`、`backward()`、`right()`、`left()`、`circle()`等,可以用来绘制线条、形状、图案等。 Python-turtle模块不仅可以进行基本的绘图,还提供了一些高级绘图功能。例如,可以使用`t.begin_fill()`和`t.end_fill()`方法来填充图形,使用`t.penup()`和`t.pendown()`方法来控制画笔的起始和结束位置,使用`t.dot()`方法来绘制点等。 总的来说,Python-turtle模块是一个非常有用的工具,它可以帮助初学者了解图形绘制的基础知识,也可以用来进行简单的图像处理。在使用Python-turtle模块时,需要注意以下几点: 1. Python-turtle模块不是Python的内置模块,需要单独安装。 2. 在使用Python-turtle模块时,需要导入turtle模块。 3. 在创建海龟对象之前,需要先创建一个画布。 4. Python-turtle模块的绘图函数和方法非常多,需要仔细学习和理解。 5. 在进行图形绘制时,需要注意海龟对象的位置和方向。 综上所述,Python-turtle模块是一个非常有用的工具,可以帮助初学者学习图形绘制和编程基础知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芳樽里的歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值