1 前言
导包这个词我相信编程人员不会陌生。如何很好地在Python中导入别人的包以及自己写的工具函数?这时需要分清楚和用好的,特此总结以飨读者。
2 优雅地导入别人的包
当然这里主要指你使用pip(conda)安装到Python环境中的包。这里导包就很简单了,因为在Python解释器在解释程序时会在系统中扫描相关包的路径,不至于找不到。例如你安装了NumPy包,你可以这样导入:
import numpy
但是在写Python程序时,你的程序会显得繁琐,在Python编程社区中,我们通常会为其设置一个简称,如:
import numpy as np
这样在程序中代码会简洁一些,别人看到np也会知道你导的包是numpy,这已经是行业通用的习惯了。有时候一个工具包会有多级工具,或者说一个py文件下会有多个函数,假如你想使用numpy中的sin()函数,你可以使用这种方式导入:
from numpy import sin
如果你想sin、cos()、tan()等函数,你也可以这样使用通配符“*”导入:
from numpy import *
也就是说你把numpy下所有函数导入,虽然可以像往常的时候那样使用,但是并不建议,最好是需要什么函数,你就导入什么函数。需要补充的是:这个“*”所导入的函数都在对应包的__init__.py
文件中的__all__ = ['module_1', 'module_2']
中。
除此之外,还需要注意的是,不仅numpy下有sin函数,系统的math函数也是有sin函数的,如果你的程序报错了也需要检查一下,特别是在使用from numpy import *
。如果两个函数的输入和输出是相同的还好,如果两个函数功能有些差异等就可能报错,如果必须的导入的话,就为对应的函数起一个别名,如:
from numpy import sin as np_sin
或者不导入具体的函数,使用下面的方式:
import numpy as npnp.sin(10)
这种方式看起来也是比较简单明了的。
3 优雅地使用自己写的函数包
上面导包方式还是比较简单的,但是导入自己的包还是会经常出错的。有时候我们会自己写一些工具函数,但是每每调用时总会出现不同的错误,我们看看如何优雅地导入自己写的函数,或者跨文件夹的文件。在此之前先看看当前测试项目的目录结构和几个路径相关函数。
3.1 项目目录结构和几个路径相关的函数
当前项目的结构如下:下面来看看几个路径,有如下程序:
import osimport sysdef main(): absPath = os.path.abspath(__file__) # 返回运行当前程序py文件的路径 print('absPath', absPath) temPath = os.path.dirname(absPath) # 往上返回一级目录,得到文件所在的路径 print('temPath', temPath) temPath = os.path.dirname(temPath) # 在往上返回一级,得到文件夹所在的路径 print('temPath', temPath) print(sys.path) # Python解释器查找Python包路径有哪些(Python解释器安装地址因人而异) if __name__ == "__main__": main()
运行结果如下:
absPath f:\test\Temp\main.pytemPath f:\test\TemptemPath f:\test['f:\\test\\Temp', 'C:\\Development\\Python\\Anaconda3\\python37.zip', 'C:\\Development\\Python\\Anaconda3\\DLLs', 'C:\\Development\\Python\\Anaconda3\\lib', 'C:\\Development\\Python\\Anaconda3', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages\\win32', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages\\Pythonwin']
需要注意的是,在使用print(sys.path)
的结果中,在「默认main.py为整个项目入口」时,当前项目路径f:\\test\\Temp
已经被添加到程序的环境中了,假如以model1中的m1_test1.py为整个程序入口时,即有如下程序时:
import sysdef model1_test1(): print('model1_test1 is been called!')if __name__ == "__main__": print(sys.path)
其运行结果如下:
['f:\\test\\Temp\\model1', 'C:\\Development\\Python\\Anaconda3\\python37.zip', 'C:\\Development\\Python\\Anaconda3\\DLLs', 'C:\\Development\\Python\\Anaconda3\\lib', 'C:\\Development\\Python\\Anaconda3', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages\\win32', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Development\\Python\\Anaconda3\\lib\\site-packages\\Pythonwin']
可以看到这时的项目路径变成了'f:\\test\\Temp\\model1'
,也就是说「一个项目的主程序(自己总结,方便理解)」 在运行时就决定了整个项目的路径,「这为后面的导包提供了依据」。需要注意的是导入自写的包后,自写的包被调用时,其整个项目路径依然是项目入口程序那个py文件所对应的路径(这个py文件所处的文件夹)。知道这个后,我们看看以下各种导包情况如何解决,默认只执行main.py这个文件,也就是说确定了这个项目路径(main.py调用其他模块的包)。
3.2 程序入口文件调用各个模块的包
因为默认只执行main.py这个文件,这里以main.py文件调用model1中m1_test1.py的model1_test1()函数为例,看看如何调用,示例程序如下:其中m1_test1.py的程序如下:
import sysdef model1_test1(): print('model1_test1 is been called!')if __name__ == "__main__": print(sys.path)
项目入口函数main.py代码如下:
import osimport sysimport model1.m1_test1 as m1t1def main(): m1t1.model1_test1()if __name__ == "__main__": main()
程序运行结果:
model1_test1 is been called!
因为项目路径是在main.py所在的文件夹,而model1又与main.py处于同一文件级别,则在调用model1模块下的py文件时可以直接从model1开始书写。也需要注意 ,调用model1_test1.py文件时, model1.m1_test1 中的 m1_test1 为对应py文件的文件名。
3.3 同级文件自写py文件引用
以model1模块为例,用m1_test1.py调用m1_test2.py中的model1_test2()函数,在3.2的基础上,为model1_test1.py修改成如下代码:
import sysimport model1.m1_test2 as m1t2def model1_test1(): print('model1_test1 is been called!') # 调用同级的py文件 m1t2.model1_test2()if __name__ == "__main__": print(sys.path)
其中m1_test2.py文件中的代码如下:
def model1_test2(): print('model1_test2 is been called!')
可以看出,我们在main.py中去运行时,即使两个文件在同一级在导包时也需要从model1开始书写,如果写成这样import m1_test2 as m1t2
的话,会出现:
Traceback (most recent call last): File "f:\test\Temp\main.py", line 3, in import model1.m1_test1 as m1t1 File "f:\test\Temp\model1\m1_test1.py", line 2, in import m1_test2 as m1t2ModuleNotFoundError: No module named 'm1_test2'
如果两个同级文件所处深度太深,例如m1_test1.py和m1_test2.py文件在model1/sum_model/sub_sub_model目录下,如果从model1开始写,那导包语句岂不是特别长,这时可以使用以下方式进行修改:
from . import m1_test2 as m1t2
或者使用之前我们介绍的sys.path
来解决(注:先加入路径):
import syssys.path.append('model1/')import m1_test2 as m1t2
当你想测试m1_test2.py文件函数时(直接运行m1_test1.py,即将m1_test1.py所在文件夹当做项目文件路径)这种sys.path
导入方式也不会影响程序的运行。
3.4 跨模块导入包
现在我们的程序保持的状态是使用sys.path
解决model1中m1_test1.py导入m1_test2 .py的方式。现在我们看看在model1中py文件如何使用model2模块中的py文件。这里我可以想到使用如下导入方式:
import model1.m1_test2 as mt2
这种方式就行了嘛。现在我们来看看实际是不是这样的,main.py文件修改如下:
import osimport sysimport model2.m2_test1 as m2t1def main(): m2t1.model2_test1()if __name__ == "__main__": main()
m2_test1.py程序如下:
import model1.m1_test2 as m1t2def model2_test1(): print('model2_test1 is been called!')if __name__ == "__main__": m1t2()
运行main.py文件查看结果:
model2_test1 is been called!
结果正是我们估计的。如果你猜到这么做了,基本上已经理解整个导包的流程。但想使用sys.path
改进,即m2_test1.py程序如下:
import syssys.path.append('model1/')import m1_test2 as m1t2def model2_test1(): print('model2_test1 is been called!')if __name__ == "__main__": m1t2()
对于在main.py运行时,结果正常,但是,直接运行m2_test1.py程序时,就会出错,出错结果可想而知,除非你把model1的全路径添加到sys.path
中。
3.5 让自写包变得更加标准
如果让你的包变得更加标准的话,这时就需要在对应的模块下添加__init__.py
文件了,这个文件中的内容可以什么都没有,但会告诉解释器,这个文件所在文件夹就是一个标准的包了。可以通过配置__init__.py
文件限制from module import *
中可以导入的py文件等。
3.6 读取文件
在实际的开发过程中,也可能会出现读取同级文件,或跨包文件。类似于包,读取文件的操作则有所不同。读取同级文件如下,把main.py文件修改如下:
import osimport sysimport model2.m2_test1 as m2t1def main(): m2t1.read_file()if __name__ == "__main__": main()
m2_test1.py添加如下函数即可(绝对路径方式):
def read_file(): import os import sys file_path = 'm2_file.txt' abs_path = os.path.abspath(__file__) # 获取当前py文件路径 dir_path = os.path.dirname(abs_path) # 获取当前py文件所在目录路径 file_path = os.path.join(dir_path, file_path) # 拼接 with open(file_path) as reader: print(reader.read())
读取跨包的文件方法如下(使用项目路径定位),修改main.py文件代码如下:
import osimport sysimport model1.m1_test2 as m1t2def main(): m1t2.read_file()if __name__ == "__main__": main()
修改m1_test2.py代码如下:
def model1_test2(): print('model1_test2 is been called!')def read_file(): import os project_root = os.getcwd() # 获取项目根目录 file_path = 'm2_file.txt' file_path = os.path.join(project_root, 'model2', file_path) with open(file_path) as reader: print(reader.read())
运行main.py文件即可。
4 总结
总的来说,上面的方法能够应付大多导包和读取文件的问题。但在实际开发过程中,还需要灵活运用,避免按图索骥,生搬硬套。
剑指Offer刷题集| Python基础
更多精彩文章
回归树分析与sklearn决策树案例!
决策树算法——集成学习的基础!
如何用Python书写计算任一多变量函数任一点的偏导导数值?
一个绘制决策树的工具——graphviz,但你忽视了它的其他功能,如结构句法分析结果!
VSCode再添新功能——绘制流程图,来看看如何操作吧!
如何批量安装和导出Python项目的依赖库?
如何解决下载Github源码慢的问题
分类算法其实也可做回归分析——以knn为例
K近邻(knn)算法是如何完成分类的?
sklearn机器学习入门案例——使用k近邻算法进行鸢尾花分类
Python中的迭代器和生成器
最优化问题:拉格朗日乘子法、KKT条件以及对偶问题
又到了一年一度的秋招了,想快点让部门技术主管看到你的简历吗?附送上一小米朋友的内推码,祝你“码”到成功,不谢!