❖ Python Package Import 之痛

参考Python官方:Packages
▶参考:Python相对导入一处不解
参考:使用相对路径名导入包中子模块

理解Package

Python里,就像所有的.py文件被称为Module模块一样,所有的文件夹都被称为Package包。前提是,这个文件夹里有一个__init__.py文件,可以是空文件也可以有一些方便都内容。

一旦一个文件夹可以被视为Package,那么其中的所有文件都会有独立的Namespace命名空间,即变量都不共享,与其它的package完全独立。一个项目里,可以有很多个子文件夹、子子文件夹,一旦变成package,那么它们都能互相独立,方便我们引用。

理解sys.path

Python的目录结构中,“向当前脚本以下的目录import”永远不会出问题,出问题的,总是“向上一层目录import”

在Python的运行一个.py脚本的时候,会自动将sys.path设置为脚本所在目录。然后凡是这个sys.path之下的所有的文件模块,都能直接import导入。
但是,很明显的,只有这个.py脚本之内的目录才包括在path里,也就是说它之外的所有事情它都一无所知。相当于Linux shell中的PATH。

在Python的导入逻辑里,非常/非常/非常重要的一点必须时时刻刻记在心上:
启动脚本所在的目录,将持续作为当前目录来运行所有导入的模块。

也就是说,如果你要导入另一文件夹的模块,而那个模块依赖于它同级目录的一个模块,这个时候它是不能直接导入同级目录模块的!因为当前的path已经变了!

比如,目录结构如下:

Project
├── __init__.py
├── main.py
├── sub1
│   ├── __init__.py
│   ├── common1.py
│   ├── mod1.py *
└── sub2
    ├── __init__.py
    ├── common2.py
    └── mod2.py *

假设,我们在mod1.py中使用from common1 import *来引用同级目录的sub1/common1.py
同时,我们在mod2.py中使用from common2 import *来引用同级目录的sub2/common2.py
另外,我们在mod1.py中需要导入mod2.py。(先略过具体实现方法)

那么问题出现了:
当我用$ python mod1.py时候,当前的path是project/sub1/,导入mod2.py后,它在执行from common2 ...的时候,理所当然的是找不到的,因为sub1中没有common2.py这个文件!

理解Python模块的相对引用和绝对引用

那么现在,我们应该做什么呢?改变mod2.py中的导入路径吗?
当然不行!我们不能随便改一个Package的导入逻辑,如果这个package是别的作者写的怎么办?如果改了以后,那个package以自身为入口时候运行怎么办?这些全都会乱套。

所以,解决方案是:

强制要求从整个项目的顶层用python -m project.sub1.mod1来设置端正的PATH路径。 然后其它所有子模块、子包都用from project.sub2 import mod2这样的完整项目引用的语句来导入。

这个做法是PEP官方推荐的,也是合逻辑的,即:
一个完整的项目运行就应当以项目为入口来运行所有的子module子package
如果又想让某个子package被同项目的其它子package引用,又想单独运行,那就应当彻底把它从文件夹里抽离出来变成一个单独的“第三方库”来用。

那么具体应该怎么修改各个文件中的导入语句呢:

  • 删除每个文件中的相对引用,如from common1 import *
  • 改为项目级别的绝对引用,如:from project.sub2 import mod2
  • 如果需要运行/测试某个子模块的文件,需要cd切换到项目目录的再上一层中,执行:python -m project.sub1.mod1。注意,这里的命名是包/模块式的,不能以.py结尾!

Sibling Package Imports 父目录中的同级目录导入

具体问题来了:怎么导入父级目录中的其它模块或包呢?

再复习一下我们的目录结构:

Project
├── main.py
├── sub1
│   ├── common.py
│   ├── mod1.py *
└── sub2
    ├── common.py
    └── mod2.py *

目的:在sub1/mod1.py中,导入sub2/mod2.py。同时,mod2.py中还需要导入同目录的common.py才能工作。

这是一个项目里再正常不过的操作了,可是如果你尝试google一下的话,可能会花费你数小时,结果还是让你自己的大脑Stack-overflow.

目前stackoverflow会有人提供这几种hacks来实现我们的目的:

  • mod1.py中用sys.path.append('..')来把父级目录加到path中引用
  • from ..sub2 import mod2来实现相对引用
  • 直接from project.sub2 import mod2
  • __init__.py进行一些path设定或import导入。

经过不断的实践,发现他们大都没说清楚上下文,甚至没有告诉完整的解决方案。
总结出的经验就是:以上的那些hacks终究是hacks,不是官方推荐的,也不能真正派上用场。

比如sys.path.append()方法,一开始似乎走通了不报错。但是真实项目走起来,比不可能给每个文件都加一句sys.path.append()。走到最后,你用print( sys.path )会发现path中莫名其妙多了很多很多路径。这肯定不行,也没见过任何项目源码里这么写的。

再比如from ..sub2 import *这样的相对引用,能这么用的上下文必须是:执行脚本时要是从文件夹顶级入口执行,如果直接从mod1.py执行脚本,那么这句话是会报错的。

所以还是用官方推荐的方法和逻辑,丢弃那些hacks吧。

要达到sibling imports,在这个例子里,具体做法是:

  • 修改所有的模块mod1.py和mod2.py,把导入语句改为from project.sub? import mod?这样的。
  • 如果要导入具体某个模块中的类或函数,则:from project.sub?.mod? import MyClass
  • 执行子包中的某个子模块时,cd到project目录再往上一层,输入python -m project.sub1.mod1执行
  • 执行整个项目的话,就python -m project

__init____all__ 限制导入模块

参考Python官方: Importing ✱ From a Package

Python规定:
如果在一个package包中的__init__.py中写上__all__ = ['模块1', '模块2', '模块3']的话,
那么在其它模块引用这个package包使用from PACKAGE import *这种用法的时候,
不会真的引用包中所有的模块(那样会很耗内存),而只能导入作者在__all__里规定的模块。

__init____path__ 多层次目录导入

参考Python官方:Packages in Multiple Directories

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 当前目录 和 脚本目录 参考资料:https://techibee.com/python/get-current-...
    ThomasYoungK阅读 10,770评论 0 11
  • IO密集型程序、深拷贝和浅拷贝、模块导入、with 语句 1.1 GIL 学习目标 1. 能够说出 GIL 是什...
    Cestine阅读 740评论 0 0
  • 模块和包 一 模块 1 什么是模块? 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是...
    go以恒阅读 2,261评论 0 4
  • 目录 本文将在学习Python中模块的概念的基础上,通过一些示例来继续学习模块标准模板、import、from…i...
    TensorFlow开发者阅读 769评论 0 0
  • “采菊东篱下,悠然见南山。” 轻捧一杯香茗,看着在悠悠南山下自得耕种的你,该是怎样一种恬淡? 那个名叫陶潜...
    未央L阅读 665评论 0 4