zl程序教程

您现在的位置是:首页 >  Python

当前栏目

探寻Python导包路径机制

2023-04-18 13:02:07 时间

引言

为什么我们 import os, improt sys, improt math等模块,就可以成功导入其模块,而随便 import aaa,就不行呢。那是因为 Python 的导包路径原因,让我们来康康 Python 的导包路径,是怎样的机制。

查看导包路径

可以通过内置 sys 模块来查看导包路径。

In [1]: import sys

In [2]: sys.path
Out[2]:
['D:\Hui\DevelopEnv\Python\Python379\Scripts\ipython.exe',
 'd:\hui\developenv\python\python379\python37.zip',
 'd:\hui\developenv\python\python379\DLLs',
 'd:\hui\developenv\python\python379\lib',
 'd:\hui\developenv\python\python379',
 '',
 'd:\hui\developenv\python\python379\lib\site-packages',
 'd:\hui\developenv\python\python379\lib\site-packages\IPython\extensions',
 'C:\Users\Administrator\.ipython']

In [3]:

sys.path 返回的是一个路径列表,其代表 Python导包时搜素的路径

  • Python解释器sys.path 里依次查找要导入的模块文件或包
  • '' 表示当前路径
  • sys.path 列表中的路径的先后顺序代表了 Python解释器 在搜索模块时的先后顺序

内置模块、包存放路径

os, sys, json 等一些内置模块、包都存放在你下载 Python解释器 时,其保存路径的 Lib 目录

存放路径以我个人的举例:

D:HuiDevelopEnvPythonPython379Lib

下载的第三方库存放路径

requestsipython 这些自己下载的第三方库等都存放在 Lib 下的 site-packages 目录下

存放路径以我个人举例

D:HuiDevelopEnvPythonPython379Libsite-packages

然而导包路径 sys.path 就包含这两个路径

因此我们使用 import osimport sysimport jsonimport requests 等都可以找到相应的模块和包

如果导入模块和包时在 sys.path 中没有搜索到相对应的模块,则会报如下错误

ModuleNotFoundError: No module named 'xxx'
import aaa
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
<ipython-input-4-37ad1770aa41> in <module>
----> 1 import aaa

ModuleNotFoundError: No module named 'aaa'

其原理跟我们的电脑的 环境变量-Path 有点像。

我们可以在 cmd窗口输入 python 可以打开 python 交互解释器pip install xxx 可以下载第三方库。都是因为 ·系统环境变量-Path,有具体可执行文件的路径

追加新的导包路径

我们可以在程序运行时动态追加新的导包路径,代码如下

sys.path.append('D:HuiCodePythondemo')		# 追加到末尾

sys.path.insert(0, 'D:HuiCodePythondemo')   # 追加到开头位置,可以确保先搜索这个路径

ipython 测验

现在 D:HuiCodePythondemo 目录下有一个 aaa.py 模块。

aaa.py 模块内容如下

# aaa.py

def test():
	print('追加导包路径成功')

导包路径没追加 D:HuiCodePythondemo 时,import aaa 会报错

追加之后,在试试

追加导包路径之后就可以成功导入并使用了。

Django项目追加导包路径

来康康导包路径的具体应用场景。

Django 中我们通常把子应用模块统一放在 apps 包下,但在注册子应用的时候,该如何设置路径呢?

我们在配置文件 settings.py or develop.py 中添加打印导包路径的代码

import sys
from pprint import pprint

pprint(sys.path)

其中 pprintpretty print 美化输出的意思,这样输出的列表不会在一行上。

然后运行 Django 程序查看导包路径结果

['C:\Users\Administrator\Desktop\meiduo_project\meiduo_mall',
 'C:\Users\Administrator\Desktop\meiduo_project',
 'D:\Hui\DevelopTools\PyCharm '
 '2020.2.3\plugins\python\helpers\pycharm_display',
 'd:\hui\developenv\python\python379\python37.zip',
 'd:\hui\developenv\python\python379\DLLs',
 'd:\hui\developenv\python\python379\lib',
 'd:\hui\developenv\python\python379',
 'D:\Hui\VirtualEnv\meiduo_mall',
 'D:\Hui\VirtualEnv\meiduo_mall\lib\site-packages',
 'D:\Hui\DevelopTools\PyCharm '
 '2020.2.3\plugins\python\helpers\pycharm_matplotlib_backend']

已知导包路径

  • meiduo_project/meiduo_mall

已知 users应用所在目录

  • meiduo_project/meiduo_mall/meiduo_mall/apps/users

因此导入users 应用的路径可以写为:meiduo_mall/apps/users

知道导包路径我们就好在配置文件 settings.py or develop.py 中注册子应用

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'meiduo_mall.apps.users',	# 注册用户模块
]

是否可以将注册 users 应用做的更加简便?按照如下形式,直接以应用名 users 注册

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'users',	# 注册用户模块
]

分析:

  • 已知导包路径
    • meiduo_project/meiduo_mall
  • 已知 users 应用所在目录
    • meiduo_project/meiduo_mall/meiduo_mall/apps/users
  • 若要直接以应用名 users 注册
    • 需要一个导包路径:meiduo_project/meiduo_mall/meiduo_mall/apps

解决办法

  • 追加导包路径:meiduo_project/meiduo_mall/meiduo_mall/apps

在配置文件 settings.py or develop.py 中追加导包路径

sys.path.insert(0, r'meiduo_project/meiduo_mall/meiduo_mall/apps/users')

在项目中一般不会写死路径,因此利用 BASE_DIR 来动态拼接路径

打印 BASE_DIR 内容如下

'C:\Users\Administrator\Desktop\meiduo_project\meiduo_mall\meiduo_mall'

其路径怎么得来的呢,我们来分析一下代码

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  • 其中 __file__ 是指向当前模块
  • os.path.abspath(__file__) 是取当前模块的绝对路径
  • os.path.dirname(os.path.abspath(__file__)) 则是根据当前模块路径获取其所在目录

因此:

os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

就是获取当前模块的所在目录的上一层目录。

我这里的当前模块是 develop.py,所在目录为 settingssettings 的上一层目录则是 meiduo_mall

因此我们可以通过 BASE_DIR 动态拼接路径,来添加导包路径

# 追加子应用导包路径
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))

注册子应用直接写应用名就可以了。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'meiduo_mall.apps.users'

    'users'
]

导包路径的作用

通过查看导包路径,可以快速的知道项目中各个包该如何的导入。 接手项目时,可以尽快的适应项目导包的方式。 通过追加导包路径,可以简化某些目录复杂的导包方式。

重新导入模块

模块被导入后,import module 不能重新导入模块,重新导入需用 imp 下的 reload

from imp import reload

我们还是已上文提到的 aaa.py 模块举例

# aaa.py

def test():
	print('追加导包路径成功')

ipython 测验

In [21]: import aaa

In [22]: aaa.test()
追加导包路径成功

In [23]:

这时不要关掉 ipython 然后修改 aaa.py 模块的如下

# aaa.py

def test():
	print('重新导入模块测试')

然后回到 ipython 中测验

# 没修改前
In [21]: import aaa

In [22]: aaa.test()
追加导包路径成功

# 修改之后
In [23]: aaa.test()
追加导包路径成功

In [24]: import aaa

In [25]: aaa.test()
追加导包路径成功

In [26]:

其实 aaa.py 已经修改了,只是当前 ipython 交互器不知道,我们可以再开一个 ipython 交互器验证一下

In [3]: import sys

# 我的aaa.py 模块不在当前导包路径下,因此要动态追加一下
In [4]: sys.path.insert(0, 'D:HuiCodePythondemo')

In [5]: import aaa

In [6]: aaa.test()
重新导入模块测试

In [7]:

因此 aaa 模块被导入后,import aaa 不能重新导入模块,重新导入需使用如下方式

# 没修改前
In [21]: import aaa

In [22]: aaa.test()
追加导包路径成功

# 修改之后
In [23]: aaa.test()
追加导包路径成功

In [24]: import aaa

In [25]: aaa.test()
追加导包路径成功

In [27]: from imp import reload

In [28]: reload(aaa)
Out[28]: <module 'aaa' from 'D:\Hui\Code\Python\demo\aaa.py'>

In [29]: aaa.test()
重新导入模块测试