前言

Python项目的路径管理是一个让人头疼的问题。在写python项目的时候,明明 import了文件A,代码运行时却收到 ModuleNotFoundError,仔细一看,是引用路径不对,很是气人。又或者,当项目中出现了重名的packages时,发现引用的函数并不是自己想要的,而是其他同名packages中的函数。这些问题归根结底都是Python路径管理的问题。那么今天我们一起来看看Python的路径管理到底是怎么做的,了解原理后,以后自然不会被路径问题所困扰了!

路径索引顺序

如我们在Python专题(二)Python二三事中讲的,Python2和Python3在路径索引的顺序上有些许不同,感兴趣的同学可以参考上篇专题内容。本文仅讲Python3版本的情况。首先,在Python中有内建函数(built-in)、第三方库(site-packages)以及自义库三种可以 import的模块。然后,在 import模块时,Python解释器的搜索顺序是先搜索built-in模块,然后搜索 sys.path这个路径列表中的模块。那么Python的built-in模块又有哪些呢?我们可以在Python中用如下命令查看:

import sys
print(sys.builtin_module_names)

你会看到一长串builtin模块的名字,这些模块名称是 import动作最先搜索到的。

我们在来看看sys.path中又有哪些东西呢?

sys.path是一个路径列表,里面保存了解释器可以索引的所有路径。这个路径列表可分为如下部分:

  • 当前脚本路径
  • PYTHONPATH路径
  • 虚拟环境路径
  • site-packages路径

一般来说,第三方库会安装在site-packages路径下,当前脚本路径则是一些自定义模块,而PYTHONPATH和虚拟环境路径则是当前系统的环境变量和Python虚拟环境保存的路径。

所以当来了一个 import命令时,Python解释器的搜索顺序就是:

当然,这个sys.path中的索引顺序只是一个默认顺序,你完全可以在代码中通过sys模块修改这个顺序,在后文中你会看到如何对这个索引顺序进行修改。当完成 import动作后,Python会把这些模块的名字和所在路径保存在一个字典里,相当于一个缓存,在后面需要运行这个模块代码时可以迅速查找到该部分代码。你可以通过 print(sys.modules)来查看当前Python解释器缓存(导入)了哪些模块。

from 和 import

老生常谈的话题了,但是很容易忽略。多数情况下,

from module import fun
a = fun()

import module
a = module.fun()

在效果上是等价的。区别是第一种方式只引用了 module中的 fun函数,而第二种方式引用了整个 module。只引用 fun函数,可能造成代码中的变量名混乱,譬如你的代码中本来就有一个名为 fun的函数,这时候用第一种方式导入,会悄无声息地替换掉代码中原本的 fun函数,从而引起命名空间混乱。而引用整个 module时,解释器会运行 module中的所有代码,如果 module中有很耗时而我们又不需要的运算,第二种方式会存在冗余资源消耗。

还有一种导入模块的方式:

from module import *
a = fun()

这种导入模块的方式是官方不提倡的,因为刚才们提到用 fromimport的方法会产生变量名混乱,但是 frommoduleimportfun毕竟还是指定了导入的函数名,开发者还是可以很容易地察觉到问题。而 frommoduleimport*这种方式会让开发者导入 module中的所有公有类,函数,变量,从而使当前脚本中被导入了很多未知的变量名,让代码的管理变得更加复杂和不可控。不过,我们还是有办法控制 frommoduleimport*的行为的——用 __all__属性。如果在 module脚本中定义了 __all__属性,那么 frommoduleimport*就只会导入 __all__中的变量名:

# module.py
__all__ = ["fun"] # from module import * 只会导入fun
def fun():return True
def fun1(): # from module import * 不会导入fun1pass
var1 = False # from module import * 不会导入var1

sys.argv[0]和_file_

sys.argv[0]用来获取入口执行文件的路径。__file__用来获取当前脚本文件的路径。为了加深理解,做个小试验:

假设我的目录:

|-Users

|--myname

|----test1.py

|----test2.py

test1.py:

import sys
print(__file__)
print(sys.argv[0])

test2.py:

import test1

执行test1.py:

python test1.py

得到结果:

test1.py test1.py

执行test2.py:

python test2.py

得到结果:

/Users/myname/test1.py test2.py

在上一级目录执行test1.py:

python myname/test1.py

得到结果:

myname/test1.py myname/test1.py

在上一级目录执行test2.py:

python myname/test2.py

得到结果:

/Users/myname/test2.py myname/test2.py

05令人困扰的自引用问题

上面介绍了很多关于Python内部的路径管理的规则和原理。其实介绍这些规则和原理的终极目标就是解决令人困扰的自引用问题。在你写一个项目时,假设你的文件结构如下:

|-myproject

|----tools

|--------_init_.py

|--------trainer.py

|----utils

|--------_init_.py

|--------trans.py

|----test.py

如果在 test.py中需要用到 tools/trainer.py中的函数,那么在 test.py中直接引用即可:

import tools.trainer

这种方式其实是用了前面我们聊到的当前路径索引,解释器从当前目录出发,查找相对路径为 tools/trainer的模块。这时候我们可以完美运行 test.py

注意:文件夹下必须有_init_.py文件,解释器才能够找到模块。

但是如果在 utils/trans.py中需要用到 tools/trainer中的函数:

import tools.trainer

就会收到 ModuleNotFoundError的错误信息。因为解释器查找当前脚本路径时,找不到 tools/trainer.py这个文件,正确的路径应该是 ../tools/trainer.py,因为当前的执行脚本是 utils/trans.py。那么我们修改一下路径:

import sys
sys.path.insert(0, "../")
import tools.trainer

sys.path.insert(0,"../")会把上级目录 ../插入 sys.path列表的首位,这也就是前文说的,通过sys模块来修改默认的索引路径。这样会强制解释器搜索当前脚本路径的上级路径,解释器就可以找到我们需要的模块。

上文所述的方法是一种比较方便但是并不是很规范的方式,因为如果按照PEP-8的规范, import语句要在代码最前面, sys.path.insert(0,"../")这条语句插在两个 import语句中间,其实是违反了PEP-8规范。所以,更加规范的方式是什么呢?

笔者自己也很困惑这个问题,于是参考了一些开源的Python项目,发现他们一般用 site_packages路径索引而非用当前脚本路径索引,由于 site_packages路径索引会直接把 myproject路径加入索引列表,所以 tools/trainer.py可以在系统的任何路径下被索引到,问题自然得到解决。那么如何让Python解释器知道某条 import语句用哪种索引方法呢?回顾一下 路径索引顺序这一节,我们提到解释器会在sys.path这个路径列表搜索模块,默认情况下,会先从当前路径开始搜索,然后搜索环境变量,最后搜索site-packages路径。所以要让解释器用 site_packages路径索引,我们需要确保:

  • 当前路径下没有与所导入模块重名的文件
  • 所导入的模块(文件)在 site_packages路径下

第一个条件很容易达成,第二个条件一般有两种办法实现。如果当前项目myproject是一个纯Python项目,可以直接把myproject文件夹复制到 site_packages目录下,虽然可以使用,不过这种方式非常不规范。更规范的方式是用 setup.py把项目安装在本地,关于 setup.py的使用我会在后面的专题中详细介绍。

到此,我们就解决了这个令人困扰的自引用问题。

结语

本专题从Python路径管理的原理出发,介绍了模块导入的路径索引顺序、from和import导入的区别、sys.argv[0]和_file_的含义。最后为大家提供了令人困扰的自引用问题的两种解决办法。其实对于笔者来说,在项目中导入自己的模块产生的路径问题困扰了我很久,直到看了一些开源的项目以及跟大佬们交流学习才知道如何处理这类问题。希望读者朋友们看完这篇专题后可以有所收获,如果你对本文有任何疑问或者建议,欢迎留言交流!

【Python专题(二)】Python二三事​mp.weixin.qq.com

【Python专题(一)】python环境搭建​mp.weixin.qq.com

native.loadlibrary获取路径不对_【Python专题(三)】Python模块导入与路径管理相关推荐

  1. python获取按键状态_谁在用 python 弹奏一曲菊花台

    转自:Crossin的编程教室 想必各位在家已经闲得快发疯了吧,鄙人现在的状态如下: 但是我不想这么萎靡下去,我想做点高雅的事情,看到朋友圈有人在手机app上弹<菊花台>,简直太好听了.于 ...

  2. php从内存中获取源码_【PHP7源码分析】PHP内存管理

    作者: 顺风车运营研发团队 李乐 第一章 从操作系统内存管理说起 程序是代码和数据的集合,进程是运行着的程序:操作系统需要为进程分配内存:进程运行完毕需要释放内存:内存管理就是内存的分配和释放: 1. ...

  3. python基础之import模块导入和包的调用

    模块概念 在Python中,一个.py文件就称之为一个模块(Module).使用模块组织代码,最大的好处是大大提高了代码的可维护性 模块一共三种:python标准库.第三方模块.应用程序自定义模块. ...

  4. python 获取网页元素_记一次python提取网页标签元素的坑

    最新在学习Python抓取网页时,遇到一个坑,同一个方法提取两个网页,有一个网页提取不到正确的信息.先贴原始的代码 url = 'https://xxxx.com/' req = request.Re ...

  5. python简单爬虫程序分析_[Python专题学习]-python开发简单爬虫

    掌握开发轻量级爬虫,这里的案例是不需要登录的静态网页抓取.涉及爬虫简介.简单爬虫架构.URL管理器.网页下载器(urllib2).网页解析器(BeautifulSoup) 一.爬虫简介以及爬虫的技术价 ...

  6. python做路径图_初学者福利:python路线图

    原标题:初学者福利:python路线图 编程思维简单地分成了两个主要部分,一个是建模,一个是算法优化. 举个例子,比如我们想要做一个程序,这个程序会自动把大象装进冰箱里.那么建模是什么呢?就是我们要知 ...

  7. python如何过获取双色球信息_【编程】Python爬虫获取双色球数据

    #爬虫获取双色球的全部开奖数据 #使用class, #格式: import urllib.request import platform from bs4 import BeautifulSoup i ...

  8. python中的glob 模块学习文件路径查找

    glob glob.glob(pathname), 返回所有匹配的文件路径列表.它只有一个参数pathname,定义了文件路径匹配规则,这里可以是绝对路径,也可以是相对路径. import glob ...

  9. 北京理工大学 python专题课程-Python语言程序设计

    Q1:Python语言.C语言.Java语言.VB语言--到底哪种适合作为入门编程语言呢? A1: 如果您是计算机.软件工程.信息类专业学生,毋庸置疑,入门编程语言请学习C语言:如果您是其他专业学生, ...

最新文章

  1. 面试中多说这么一句话,薪水直接涨5k
  2. mysql 的connect 设置 无法点next_技术分享 | MySQL 使用 MariaDB 审计插件
  3. php 获取数组最小值,php 获取数组中最小的值与键名的方法
  4. 共享单车开启混战模式,谁能笑到最后?
  5. JDBC学习笔记03【JDBC事务管理、数据库连接池、JDBCTemplate】
  6. 岁月在流逝,从阿里退下来接近70后程序猿带给我的启示
  7. 微服务负载均衡实现高可用_使用负载平衡实现大容量可用性
  8. 第三十三期:使用wireshark抓包分析-抓包实用技巧
  9. 南北非遗传承人齐聚北京 演绎非遗精巧
  10. 只腐蚀毛刺 腐蚀算法_去毛刺更省时省力的方式方法大全!
  11. Kaggle 官方教程:嵌入
  12. python排课问题_教育机构如何解决排课问题?
  13. 战略分析思路——沙盘推演逻辑
  14. 《遥感原理与应用》第三版——思维导图
  15. qcom usb驱动下载_艾肯Mobile Q驱动下载
  16. JAVA_数字转大写
  17. python调用pandas保存excel
  18. 2013年最后2个月的学习目标(成果)(上次更新2013年11月18日)
  19. python 计算月还款额度
  20. Whitelabel Error Page问题解决方案

热门文章

  1. LeetCode 1073. 负二进制数相加(负数进制)
  2. LeetCode 493. 翻转对(归并排序)
  3. c语言汇编混合编程写一个乘法,求通过C语言实现矩阵的加、减及乘法。要自己写的,不要复制过来...
  4. 数据库练习(二)三个数据库根据指定id获取name和存储数据库名称
  5. 总结python中基本的面试题
  6. Flink的时间语义和Watermark
  7. react获取id_解决React应用界面开发常见痛点(一)业务逻辑与UI分离
  8. 一阶电路暂态响应的结果分析。_阻尼比测试方法及谐响应分析
  9. 消息中间件系列(五):MQ消息队列的12点核心原理总结
  10. 推荐标星 100 K 的 GitHub 开源项目