[Python学习日记-56] Python 中的包与代码的跨模块代码调用
[Python学习日记-56] Python 中的包与代码的跨模块代码调用
简介
包(Package)
跨模块导入
简介
在前面软件开发目录设计规范当中我们知道了一个标准化项目会有多层结构,也会把一些功能相似的代码打包成一个 Python 包,这样就会形成一个结构分明的项目,但是我们发现这样的形式会出现不同文件之间调度的问题,这种问题需要怎么解决呢?下面我们一起来看看吧。
包(Package)
当你的模块文件越来越多时,就需要对模块文件进行划分了,例如把负责跟数据库交互的都放一个目录,把与页面交互相关的放一个目录,像下面那样
my_proj/
|—— pejoy_web # 代码目录
| |—— __init__.py
| |—— admin.py
| |—— apps.py
| |—— models.py
| |—— tests.py
| |—— views.py
|—— manage.py
|—— my_proj2 # 配置文件目录
| |—— __init__.py
| |—— settings.py
| |—— urls.py
| |—— wsgi.py
像上面这样,一个目录管理多个模块文件,这个文件夹就被称为包,一个包就是一个目录,但该目录下必须存在 __init__.py 文件,该文件的内容可以为空,__init__.py 是用于标识当前目录为一个包。这个 __init__.py 文件主要是用来对包进行一些初始化的,当当前这个包被别的程序调用时,__init__.py 文件会先执行,但它一般为空,如果有一些你希望只要包被调用就立刻执行的代码可以放在 __init__.py 里,下面来演示一下
my_proj2/__init__.py 文件的代码
print("------running my_proj2 package------")
my_proj2/settings.py 文件的代码
print("------running my_proj2.settings------")DATABASES = "this is Databases information."
pejoy_web/test.py 文件的代码
import syssys.path.append(r"G:\joveProject\my_proj") # 这个有点涉及到跨模块导入的知识,下面会说from my_proj2 import settingsprint(settings.DATABASES)
下面我们运行 pejoy_web/test.py 文件的运行结果如下:
从运行的结果来看,写在 my_proj2/__init__.py 中的代码在包被调用的时候就已经执行了。通常该文件并不会写一些功能性的代码,不过也可以写一些欢迎语,提示使用者进入了该模块,不过这里进行介绍的主要目的还是想告诉大家包和普通目录的区别。
跨模块导入
我们先来接一个目录结构,目录结构如下
my_proj/
|—— pejoy_web
| |—— __init__.py
| |—— admin.py
| |—— apps.py
| |—— models.py
| |—— tests.py
| |—— views.py
|—— manage.py
|—— my_proj2
| |—— __init__.py
| |—— settings.py
| |—— urls.py
| |—— wsgi.py
根据上面的结构,如何实现在 pejoy_web/views.py 里导入 my_proj2/settings.py 模块呢?我们先来试试直接导入会怎么样吧,代码如下
pejoy_web/views.py 文件的代码
from my_proj2 import settings
my_proj2/settings.py 文件的代码
print("------running my_proj2.settings------")DATABASES = "this is Databases information."
直接导入的话,会抛出 ModuleNotFoundError 报错,说找到不模块,如下图所示
然我们来分析一下为什么会出现这个报错呢?前面我们在学习模块的介绍与导入中模块查找路径的时候知道了 Python 是根据不同的路径去进行查找的,最先查找的是 .py 文件所在当前目录的路径,然后才是标准库和第三方库,但是回过头来我们发现,根据我们的目录结构,my_proj2/settings.py 相当于是 pejoy_web/views.py 的父亲(pejoy_web)的兄弟(my_proj2)的儿子(settings.py),settings.py 算是 views.py 的堂兄弟了,在 views.py 里只能导入同级别兄弟模块代码(当前目录),或者子级别包里的模块代码(当前目录的子目录),根本不知道堂兄弟的存在。这应该怎么解决呢?
答案是添加环境变量,把父亲级的路径添加到 sys.path 中,就可以了,这样导入就相当于从父亲级开始找模块了,其实在前面介绍包的代码当中就使用了这个方法,不过当时用的是固定的绝对路径,如果放到了别的计算机上面就会出错,下面的演示代码我们将会改进一下,使绝对路径自动生成使得代码拿到每一台机上面都可以自动生成当前目录的绝对路径,下面来看一下在 pejoy_web/views.py 中添加环境变量,代码如下
import sys,osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # __file__ 是打印当前被执行文件的相对路径
print("dir:::",BASE_DIR)sys.path.append(BASE_DIR)from my_proj2 import settingsdef home_page():print("welcome to Pejoy...")print(settings.DATABASES)home_page()
代码输出如下:
官方推荐的跨目录导入方法
虽然通过添加环境变量的方式可以实现跨模块导入,但是官方不推荐这么干,如果这么干就需要在每个目录下的每个程序里都写一遍添加环境变量的代码。这样你写的程序当中会有大量重复性的代码。
官方推荐的做法是在项目里创建个入口程序,整个程序调用的开始应该是从入口程序发起,这个入口程序一般放在项目的顶级目录,像上面的目录结构当中我们所设计的入口程序就叫做 manage.py 当然这个名字可以根据你自己的喜好来定义。
这样做的好处是项目中的二级目录 pejoy_web/views.py 中再调用他堂兄弟 my_proj2/settings.py 时就不用再添加环境变量了。我们来写一段代码来看看这样做能不能实现我们想要的效果,代码如下
manage.py 文件的代码
print("------running manage.py------")
from pejoy_web import views
pejoy_web/views.py 文件的代码
from my_proj2 import settingsdef home_page():print("welcome to Pejoy...")print(settings.DATABASES)home_page()
my_proj2/settings.py 文件的代码
print("------running my_proj2.settings------")DATABASES = "this is Databases information."
代码输出如下:
从输出可以看出,使用入口程序来发起调用整个程序是可以实现与上面我们自己生成路径是一样效果的,这是因为 manage.py 的当前目录是在顶层,当 manage.py 启动时项目的环境变量中的当前目录的路径就会自动变成 ....xx/my_proj/ 这一级别,为了方便理解我们看下图所示