<返回更多

不仅仅要会导别人的包也要会导自定义的包——Python导包总结

2020-08-16    
加入收藏

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 np
np.sin(10)

这种方式看起来也是比较简单明了的。

3 优雅地使用自己写的函数包

上面导包方式还是比较简单的,但是导入自己的包还是会经常出错的。有时候我们会自己写一些工具函数,但是每每调用是总会出现不同的错误,我们看看如何优雅地导入自己写的函数,或者跨文件夹的文件。在此之前先看看当前测试项目的目录结构和几个路径相关函数。

3.1 项目目录结构和几个路径相关的函数

当前项目的结构如下:

不仅仅要会导别人的包也要会导自定义的包——Python导包总结

 


不仅仅要会导别人的包也要会导自定义的包——Python导包总结

 

下面来看看几个路径,有如下程序:

import os
import sys

def 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:testTempmain.py
temPath f:testTemp
temPath 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']

可以看到这时的项目路径变成了'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 sys

def model1_test1():
    print('model1_test1 is been called!')


if __name__ == "__main__":
    print(sys.path)

项目入口函数main.py代码如下:

import os
import sys
import model1.m1_test1 as m1t1

def 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修改成如下代码:

3.3 同级文件自写py文件引用
以model1模块为例,用m1_test1.py调用m1_test2.py中的model1_test2()函数,在3.2的基础上,为model1_test1.py修改成如下代码:

其中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:testTempmain.py", line 3, in <module>
    import model1.m1_test1 as m1t1
  File "f:testTempmodel1m1_test1.py", line 2, in <module>
    import m1_test2 as m1t2
ModuleNotFoundError: No module named 'm1_test2'

如果两个同级文件所处深度太深,例如m1_test1.py和m1_test2.py文件在model1/sum_model/sub_sub_model目录下,如果从model1开始写,那导包语句岂不是特别长,这时可以使用以下方式进行修改:

import sys
sys.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 os
import sys
import model2.m2_test1 as m2t1

def main():
    m2t1.model2_test1()

if __name__ == "__main__":
    main()

m2_test1.py程序如下:

import model1.m1_test2 as m1t2
def 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 sys
sys.path.append('model1/')
import m1_test2 as m1t2

def 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 os
import sys
import model2.m2_test1 as m2t1

def 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 os
import sys
import model1.m1_test2 as m1t2

def 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 总结

总的来说,上面的方法能够应付大多导包和读取文件的问题。但在实际开发过程中,还需要灵活运用,避免按图索骥,生搬硬套。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>