转载 :https://blog.csdn.net/u011089523/article/details/52931844

本文主要介绍如何在一个Python项目中,优雅的实现项目内各个package的模块(module)之间的相互引用。

之所以写这篇文章,是因为网上流传的各种奇技淫巧简直五花八门(包括stackoverflow等知名社区),极易误导对python的import机制不熟悉的人。比如我就曾一度因为找不到优雅的import方式,而认为python是一门愚蠢的语言。所以,我把近一上午的学习结果总结出来,希望大家不要误入歧途。

本文参考了如下两篇博客: 
habnab关于python package的精彩总结:地址点我 
Jean-Paul Calderone关于python项目结构的建议:地址点我

demo project

本文以一个demo project为例,来介绍python的包管理机制。

这个demo project我放到github上了:地址点我

其中,项目根目录有三个文件夹:

  • data:存放项目数据
  • doc:存放项目文档
  • package:一个demo python package

其中,package中的文件结构如下图: 

python package

基础知识

当你import的时候,python只会在sys.path这个变量(一个list,你可以print出来看)里面的路径中找可能匹配的package和module。

而一个package跟一个普通文件夹的区别在于,package的文件夹中多了一个__init__.py文件。换句话说,如果你在某个文件夹中添加了一个__init__.py文件,则python就认为这个文件夹是一个package。

__init__.py文件可以是空的(也推荐者这么做),它只是告诉python当前文件夹是一个package。当然,也可以在里面添加一些代码,这些代码会在import这个包的时候运行。

所以,请确保你要import的文件所在的文件夹有__init__.py文件(除非它在sys.path中某个文件夹下)。

错误的import做法

如上述project中,如果你想让subpackage2中的foo2来import subpackage1中的foo1,便会出现找不到subpackage1的情况。

目前网络上大部分的做法都是通过sys.path.append(yourpath)之类的方法,将你需要import的module的目录添加到sys.path中。或者,通过修改PYTHONPATH这个环境变量来将添加(跟修改sys.path效果相同)。

但是,这种做法有如下几个缺点:

  • 如果你用PYTHONPATH,那么当有多个项目时,你需要把每个项目的根目录都加入到PYTHONPATH中,会使得PYTHONPATH变得十分臃肿
  • 如果你使用sys.path,由于文件夹是动态添加的,所以当你使用相对路径的时候,实际路径会十分依赖于你的入口函数,当入口函数改变很可能就会导致代码无法运行
  • 如果你使用绝对路径,将你的代码在其他机器上运行的时候需要重新配置这些变量,十分麻烦

正确的做法

代码中正常import

首先,在代码中按照正常方式导入你需要的包

比如,你需要在app.py中导入foo1,则:

from package.subpackage1 import foo1 

虽然你可能发现from subpackage1 import foo1也可以正常运行,但是请避免这种使用相对路径的方法。因为这在python3中将不再支持,同时也有可能会引起奇怪的问题。同时,虽然PEP 328中也给出了 from .subpackage1 import foo1这样的形式,但是还是不要自己给自己制造麻烦,统一使用完整路径(绝对路径)为好。

再比如,如果你需要在foo2.py中导入foo1.py(在不同的subpackage中),则:

from package.subpackage1 import foo1

跟上面一模一样,这就是使用绝对路径的好处,各处的引用高度统一。同时,如果你的package被安装在其他用户的机器中,其他用户也会使用这种绝对路径来import你package中的模块(回想你自己import第三方package的情景)。

创建__main__.py文件

在package的根目录中创建__main__.py文件,可以使得你的package可以通过python -m直接运行。

demo中的__main__.py文件十分简单:

from package.app import main
main()

即import真正的主函数app.py中的main方法,然后调用main()

用python -m运行你的python文件

python的-m参数官方说法是: 
Searches sys.path for the named module and runs the corresponding .py file as a script.

在下面的例子中,加上-m参数后,所运行的.py文件便会识别其顶层的package

回到刚才的例子。创建完__main__.py之后,cd到项目的根目录,运行

python -m package

即可实现直接运行__main__.py,即直接运行了package这个包

如果你想直接运行package内的某个.py文件,比如foo1,则:

python -m package.subpackage1.foo1

当然,你要确保foo1中存在判断其是否是入口函数的逻辑,如下:

if __name__ == "__main__":
speak()

总结

至此,我们已经实现了你所希望的所有功能:

  • 在project内部实现各个模块之间的import
  • project中的各个.py文件可以直接运行
  • 将project迁移到其他机器时,不用进行额外配置

如果还有不明白的,可以将github上的源码下载下来看一看,然后用python -m运行一下

python项目内import其他内部package的模块的正确方法的更多相关文章

  1. python中split()及os.path模块的使用方法

    返回path规范化的绝对路径path=os.path.abspath('test.py')print(path)输出 D:\Caps\test.pypath=os.path.abspath('D:\\ ...

  2. Python的list循环遍历中,删除数据的正确方法

    在遍历list,删除符合条件的数据时,总是报异常,代码如下: num_list = [1, 2, 3, 4, 5] print(num_list) for i in range(len(num_lis ...

  3. python项目实现配置统一管理的方法

    一个比较大的项目总是会涉及到很多的参数,最好的方法就是在一个地方统一管理这些参数.最近看了不少的python项目,总结了两种很有意思的配置管理方法. 第一种 基于easydict实现的配置管理 首先需 ...

  4. Python 集合内置函数大全(非常全!)

    Python集合内置函数操作大全 集合(s).方法名 等价符号 方法说明 s.issubset(t) s <= t 子集测试(允许不严格意义上的子集):s 中所有的元素都是 t 的成员   s ...

  5. 【Python】 关于import和package结构

    关于import语句 python程序需要使用某个第三方模块的话要用import语句,其实就是把目标模块的内容加载到内存里.当然,在加载之前,python会按照一定的顺序寻找sys.path中的目录. ...

  6. python import 其他 package的模块

    https://blog.csdn.net/luo123n/article/details/49849649 http://blog.habnab.it/blog/2013/07/21/python- ...

  7. Python项目中如何优雅的import

    Python项目中如何优雅的import 前言 之前有一篇关于Python编码规范的随笔, 但是写的比较杂乱, 因为提到了import语句, 在篇文章中, 我专门来讲Python项目中如何更好的imp ...

  8. Python在项目外更改项目内引用

    前言 目前有一个奇葩的需求, 将某个开源项目整合进自己的项目里去调度, 还需要在每次启动这个开源项目时, 加载不同的配置文件进去, 问题是配置文件并不是一个 conf 或者是其他的什么, 而是以 .p ...

  9. Jenkins构建Python项目提示:'python' 不是内部或外部命令,也不是可运行的程序

    问题描述: jenkin集成python项目,立即构建后,发现未执行成功,查看Console Output 提示:'Python' 不是内部或外部命令,也不是可运行的程序,如下图: 1.在 Windo ...

随机推荐

  1. dubbo中的group与version的存在意义

    公司每周五都要给线上系统发布一个版本,我将本周新开发的业务模块直接提交到svn的主干上(当然本机已经测试通过),在公司的测试环境部署运行正常,测试人员业务测试通过.但是在部署到准生产环境上后出现了意想 ...

  2. crawler 使用jQuery风格实现

    以前写过java版的crawler,最近看了Groovy的XmlSlurper,效果还是不太满意,直到这篇文章启发了我:how-to-make-a-simple-web-crawler-in-java ...

  3. Java并发包同步工具之Exchanger

    前言 承接上文Java并发包同步工具之Phaser,讲述了同步工具Phaser之后,搬家博客到博客园了,接着未完成的Java并发包源码探索,接下来是Java并发包提供的最后一个同步工具Exchange ...

  4. Python新利器之pipenv

    前言 之前学习异步asyncio库的时候,因为asyncio库支持Python3.5以上的版本,而我的Ubuntu14.04只有Python3.4,虽然下载了Python3.6,但是想直接利用ipyt ...

  5. 从源码看 Vue 中的 Mixin

    最近在做项目的时候碰到了一个奇怪的问题,通过 Vue.mixin 方法注入到 Vue 实例的一个方法不起作用了,后来经过仔细排查发现这个实例自己实现了一个同名方法,导致了 Vue.mixin 注入方法 ...

  6. 增强for循环的简单总结

    整体来说:增强型for循环使用起来比较方便,代码也比较简单,如果只是操作集合中元素的而不使用索引的话,建议用此方法.对于普通for循环,如果需要使用索引进行其它操作的话,建议用这个. 详细来说:1,区 ...

  7. MongoDB集群-主从复制(副本集)、failover

    1.概念 主从复制的目的:数据冗余.备份.读写分离 主从方式:一主一从(不推荐,只能实现复制,主节点挂掉且未重新启动的时候,无法提升从节点为master),一主一从一裁判,一主多从 复制方式:主节点记 ...

  8. vue定义global.js,挂载在vue原型上面使用

    首先在src目录下创建global目录,在global目录下创建index.js. export default { install(Vue) { var that = this // 1. 添加全局 ...

  9. vue中 keep-alive 组件的作用

    原文地址 在vue项目中,难免会有列表页面或者搜索结果列表页面,点击某个结果之后,返回回来时,如果不对结果页面进行缓存,那么返回列表页面的时候会回到初始状态,但是我们想要的结果是返回时这个页面还是之前 ...

  10. js有序数组中插入一个元素,并有序的输出

    题目:比较传入函数的参数,将参数组成数组,从小到大排序,返回新的数组. 如: insert();console.log(arr); //[] insert(-1,-2); console.log(ar ...