我们在学 python的时候,大多数都是从print("hello,world")开始,这一行代码,敲开了每一位工程师新世界的大门!
然后我们开始学语法、变量、函数、条件控制、数据结构、面向对象,然后迫不及待的与bug过招。在这个过程中,有一位朋友一直默默陪伴着我们,但是我们却从来没有关注过它。他就是我们的 import 兄弟os
在我们的代码中,它扮演这不可或缺的角色。但是却很少有人真正懂它。正是因为有了他,python的强大之处才能得以发挥,今天让我们一起好好了解一下它。
1. 模块化编程
在我们真实的项目中,代码量可能达到几十万、几百万行。如果我们把这几十万、几百万行代码都写在一个文件里面。那后果是非常严重的,首先我们的代码文件将会非常大,其次是想通过肉眼去找到我们的代码,难度也是非常大,更别说看懂其中的代码逻辑了。
出现了模块化编程的想法。模块化编程有助于开发者统筹兼顾和分工协作,并提升代码灵活性和可维护性。比如说众多工程师共同开发同一个系统。A做登录功能的逻辑,B做注册功能的逻辑,C做用户管理的逻辑。这些代码是分离的,通过模块化组装的方式把他继承到同一个系统中。
模块化编程:将一个完整系统的代码,拆分成一个一个小模块
举个例子:
(1)非模块化项目:项目里面只有一个py文件 main.pylogin():
#这里完成登录逻辑的代码编辑
pass
def register():
# 这里完成注册逻辑的代码编写
pass
def user_manage():
# 这里完成用户管理的代码编写
pass
if __main__ == "__main__":
login()
register()
user_manage()
(2)模块化的项目:项目中包含 main.py、login.py、register.py、user_manage.py
login.pylogin():
#这里完成登录逻辑的代码编辑
pass
register.pyregister():
# 这里完成注册逻辑的代码编写
pass
user_manage.pyuser_manage():
# 这里完成用户管理的代码编写
pass
main.pylogin import login
from register import register
from user_manage import user_manage
if __main__ == "__main__":
login()
register()
user_manage()
其实,Python之所以这么强大,这个特性发挥了很大的作用。我们需要的很多功能不用自己去写,通过导入别人写好的模块,我们可以直接使用,这样可以大大提供效率。比如我们想要写一个爬虫,假设没有模块化的助力,我们需要从0开始编写,http请求、TCP连接、返回处理、底层数据包封装、解析。但是有了模块化,我们只需要一行代码,开箱即用,是不是非常方便呢requests
2. Python中的模块
说了模块化编程,那么在Python中,我们的模块到底指的是什么呢?在Python中,我们需要区分几个概念:
模块:一个后缀为.py的代码文件就是一个模块
包:一个包含很多.py文件的文件夹。(Python3.3之前要求这个文件夹中必须含有 __init__.py文件)
库:可能由多个包和模块组成,可以认为是一个完整项目的打包。
3. import语句
3.1 从模块导入
(1)全量导入:
全量导入会将模块内的所有全局变量、函数、类等等全部都导入进来。
全量导入一个模块的所有内容有两种方式:
import xxxtest
print(dir(test)
# 查看导入了什么
# 发现我们定义的Hello类、hello函数、name变量全部都被导入进来了
# 并且还有一些其他的东西
['Hello', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'hello', 'name']
test.hello()
print(test.name)
# 上面方法需求使用:模块名.变量名(方法名)引用,因为只是引入模块整体,并没有把模块里面的内容单独引入
from xxx import <em>test import *
hello()
Hello()
print(name)
# 以上可以直接使用,因为 from test import * 已经将test模块的所有内容单独导入进来。
(2)局部导入
from xxx import xxxtest import hello # 只从test模块中导入hello函数,别的不导入
from test import hello, Hello # 导入多个
hello()
给导入的内容设置别名:from xxx import xxx as yyytest import hello as hello_func # 从test模块中导入hello函数,并且设置别名为:hello_func
hello_func()
3.2 从python包中导入
|——test_package
|——__init__.py
|——test.py
|——test2.py
|——main.py
main.py:
大家猜猜,下面这两段代码能运行吗?test_package
test_package.test.hello()
Traceback (most recent call last):
File "/app/util-python/python-module/main.py", line 8, in <module>
test_package.test.hello()
AttributeError: module 'test_package' has no attribute 'test'
test_package import *
test.hello()
Traceback (most recent call last):
File "/app/util-python/python-module/main.py", line 10, in <module>
test.hello()
AttributeError: module 'test' has no attribute 'hello'
看到这里,可能大家脸上都出现了三个问号???为什么导入模块的方法放在导入包这里不好使了呢?
其实啊,我们的python在导入一个模块的时候,会把我们的模块.py文件执行一遍,然后生成一个模块对象,模块中我们定义的函数、变量、类会添加到这个模块对象的属性里面:这就是为什么我们可以通过test.hello(),因为hello()是test的一个属性.
那导入包的时候呢?我们知道python的包本质上是一个文件夹,文件夹是不能被编译执行的。那为什么还能import呢?实际上,我们在import一个包的时候,执行的是这个包里面的 __init__.py文件,也可以理解为导入包的时候,只是导入了__init__.py
因为我们__init__.py是空的,所以我们导入了个寂寞。
(1)通过导入模块的方式:test_package import test
test.hello()
from test_package.test import hello
hello()
(2)通过添加__all__属性:
__init__.py文件: 添加 __all__ 属性__all__ = ["test", "test2"]
main.py文件: test_package import *
# 通过在 __init__.py中定义了 __all__属性,在导入的时候,可以把该属性列出的模块全部导入
test.hello()
test2.hello2()
__all__ 是针对模块公开接口的一种约定,以提供了”白名单“的形式暴露接口。如果定义了__all__,其他文件中使用from xxx import </em>导入该文件时,只会导入 __all__ 列出的成员
__all__ 仅对于使用from module import * 这种情况适用。
4. 动态导入
上面我们介绍了比较主流的import语句导入,其实在python中还有其他的导入方式
4.1 __import__()
__import__() 函数可用于导入模块。其实当我们使用import导入Python模块的时候,默认调用的是__import__()函数。直接使用该函数的情况很少见,一般用于动态加载模块。
__import__(name, globals=None, locals=None, fromlist=(), level=0)参数:
name:要导入的模块名,可使用变量
globals和locals:通常使用默认值。使用给定的globals和locals变量来决定如何在一个包上下文中解析name
fromlist:指定要导入的子模块名或对象名,它们会按名称从模块导入
leve:指定导入模块的方式。level为0则绝对导入; level为正值则表示相对于调用import ()的模块目录,要搜索的父目录数
_obj = __import__("os")
print(os_obj.getcwd())
_obj = __import__("test_package.test")
os_obj.test.hello()
4.2 importlib
importlib 是 Python 中的一个标准库,importlib 能提供的功能非常全面importlib
myos=importlib.import_module("os")
myos.getcwd()
4.3 一个使用场景
一个定时任务的场景,数据库存了许多这样的定时任务:cmdb.tasks.get_host_info,它表示的是调用cmdb包下的tasks模块下的get_host_info函数。我们怎么实现,通过这种格式的字符串,去调用相应的函数呢?
大家可以思考思考怎么做。exec_task(task_name):
if not task_name:
return -1, "task name must not None"
try:
module_name = task_name.rsplit(".", 1)[0]
method_name = task_name.rsplit(".", 1)[1]
# 动态导入tasks模块
module_obj = __import__(module_name)
if not hasattr(module_obj, method_name):
return -1, "function not found"
except:
return -1, "has Error"
task = getattr(module_obj, method_name)
task()
5. 搜索路径
不知道大家有没有思考过这样一个问题,当我们导入一个模块或者导入一个包的时候,python是去哪里寻找这个模块的呢?
Python搜索模块的路径是由四部分构成的:
程序的主目录、
PATHONPATH目录
标准链接库目录
.pth文件的目录,
这四部分的路径都存储在sys.path 列表中。
pth文件:pth文件用于添加额外的sys.path即python检索路径,一般在github上下载的程序包会有一个setup.py,执行该文件会在(当前python环境下的site-packages文件夹生成)一个.pth文件
sys
print(sys.path)
[
'/app/util-python/python-module', # 当前程序所在的目录
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/app/python-virtualenv/aioms-env/lib/python3.7/site-packages' # 下载的第三方包目录
]
当我们的 导入一个包的时候,python解释器依次在这些目录搜索。如果这些目录中没有找到,程序就会报错
假设有一个文件:/app/test_pack.py
我们现在的程序路径为:/app/util-python/python-module/main.pytest_pack
Traceback (most recent call last):
File "/app/util-python/python-module/main.py", line 5, in <module>
import test_pack
ModuleNotFoundError: No module named 'test_pack'
没有任何意外,报错了,找不到这个包sys
sys.path.append('/app')
import test_pack
test_pack.hello()
# 测试导入自定义路径
print(sys.path)
[
'/app/util-python/python-module',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/app/python-virtualenv/aioms-env/lib/python3.7/site-packages',
'/app' # 我们发现多了一个搜索目录,它在这个目录下找到了我们的test_pack.py
]
所以:模块与包的搜索路径不是固定不变的,我们可以自定义它,当然上面的方法只是暂时的
所以,当我们程序出现这个错误的时候:ModuleNotFoundError: No module named 'test_pack'
问题排查两部曲:
查看下载安装的包的路径在什么地方
使用sys.path看看,下载完成的包在不在这里面
6.相对导入与绝对导入
假设包结构:.py
packageA/
__init__.py
moduleA.py
moduleB.py
packageB/
__init__.py
moduleA.py
moduleB.py
对于packageA/moduleA.py:
绝对导入:所有的模块import都从“根节点”开始。根节点的位置由sys.path中的路径决定packageA import moduleB
相对导入:只关心相对自己当前目录的模块位置
. import moduleB
7. 交叉引用(导入循环)
什么叫做交叉导入,就是两个包互相导入.py
package/
__init__.py
moduleA.py
moduleB.py
. import moduleB
这里不想拓展太多了,推荐大家都使用绝对导入就完事了!免得给自己挖坑
moduleA.py: moduleB import hello_b
moduleB.py: moduleA import hello_a
在这里面:模块A调用了模块B的某方法、而模块B也调用了模块A的某方法,这个就叫交叉导入
首先我们看看这样会导致什么问题: cannot import name 'hello_b' from 'moduleB'
这样将会抛出异常。异常的原因是:在导入的时候,moduleA需要访问moduleB的hello_b,但是没有hello_b还没有初始化完成。所以就会抛出异常
通常来说:这是由于大型的Python工程中,架构设计不当,出现的模块间相互引用的情况
解决办法:
架构优化,解除相互引用的关系
import语句放置在模块的最后
import语句放置在函数中
8. 安装第三方包
什么是第三方包呢?在编程的圈子里面流行着这么一句话:“不要重复造轮子!”。这里的轮子其实就是我们的第三方包。当我们想要制造一辆小汽车,我们直接使用现有的零件拼装起来就行。就不需要再从0开始,造轮子、发动机等等。因为别人已经造好了。
在计算机行业里面,别人造好了轮子,总要有地方存起来,可以让其他用户看到和使用。这就需要一个权威的三方机构去管理这些轮子。
PyPI就是这么一个角色, PyPI (Python Package Index )是python官方的第三方库的仓库 ,所有人都可以下载第三方库或上传自己开发的库到PyPI。
那怎么去管理这些三方包呢?市面上有很多方法。但是使用最广泛的是pip
pip 是一个现代的,通用的 Python 包管理工具,该工具提供了对Python 包的查找、下载、安装、卸载的功能
7.1 pip 安装 # 查看pip版本,可以判断pip是否安装
pip -v
一般情况下,我们从官网上下载的python安装以后,会自带这个工具,我们无需过多操心,但是,如果很不巧,你的Python版本下恰好没有pip这个工具,怎么办呢?
对于linux用户:$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # 下载安装脚本
$ sudo python get-pip.py # 运行安装脚本
apt-get install python-pip
window用户类似,只需要去官网上下载pip安装包,再使用python安装即可
7.2 安装第三方包 install Django # 安装最新版本
pip install Django==1.0.4 # 指定版本
pip install Django>=1.0.1 # 指定最小版本
pip install Django<=1.0.1 # 指定最大版本
由于 PyPI镜像源是国外的,有时候下载会非常缓慢,这个时候我们可以使用国内镜像源install -i https://pypi.tuna.tsinghua.edu.cn/simple Django
国内镜像源地址:
清华:https://pypi.tuna.tsinghua.edu.cn/simple
阿里云:http://mirrors.aliyun.com/pypi/simple/
豆瓣:http://pypi.douban.com/simple/
......还有其他可自行搜索
7.3 其他操作 uninstall Django # 卸载已安装的库
pip install --upgrade Djano==2.0.1 # 升级已安装的包
pip list # 列出已安装的库
pip freeze > requirements.txt # 将当前的项目依赖导出的文本文件中
pip install -r requirements.txt # 根据上面导出的文本文件里面的依赖进行安装
关注小编公众号:偷偷学习,卷死他们</div>
<div id="asideoffset"></div>