python import 详解

当前目录 和 脚本目录

参考资料:https://techibee.com/python/get-current-directory-using-python/2790
current directory/working directory(工作目录):运行python时用户所在的目录,就是用户打shell命令的pwd看到的目录,与python脚本在哪里无关(比如脚本文件在../../script.py)
os.getcwd():可以获得current dir
工作目录的作用是:读取文件时,open('filename')指的是工作目录下的filename文件,open('../filename')指的就是在工作目录上一层的filename文件。

入口文件及所在目录:
__file__: 脚本文件路径
则脚本目录可以这样获得:os.path.dirname(os.path.realpath(__file__))

导入详解

参考资料:https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html

python的import一直对我是个黑洞,我只是大致了解sys.path的功能。但是python的导入实际上是有很多学问的,弄懂它对开发运维都有巨大帮助, 我从上面这位斯坦福大学大二学生的教程中受益匪浅,决定把它编译下来,顺带改正几处他的笔误。读懂了上文,只要你不准备写第三方库,那么基本能够应付几乎所有的导入问题了。

关键点:

  1. import 语句根据sys.path的路径进行搜索
  2. sys.path自动包含入口文件所在目录,而不是包含working directory
  3. import一个package概念上就是导入该package的__init__.py文件

基本概念

  • module: *.py文件,module的名字为该文件的名字
  • built-in module: 被编译进python解释器的模块(用C实现),因此没有对应的*.py文件
  • package: 包含__init__.py的目录,package的名字是目录的名字
    • python 3.3以后,任何目录都是package(即使没有__init__.py)

例子的目录结构

test/                      # root folder
    packA/                 # package packA
        subA/              # subpackage subA
            __init__.py
            sa1.py
            sa2.py
        __init__.py
        a1.py
        a2.py
    packB/                 # package packB (implicit namespace package)
        b1.py
        b2.py
    time.py
    random.py
    other.py
    start.py

import做了什么?

当import module时,python会运行其中的代码;当import package时,python会运行__init__.py中的代码

import的搜索路径

import spam时,先搜索built-in module中是否有spam模块,如果没有则按顺序搜索 sys.path下的是由有spam模块。

假设运行>> python script.pysys.path的路径列表是这样初始化的的:

  • 包含入口文件script.py的目录 (如果没有指定script.py,直接>> python, 则是当前目录).
  • PYTHONPATH (目录列表,格式同shell变量PATH)
  • 默认的package包(如标注库)

初始化完成后,python程序可以更改sys.path. 此外因为入口文件所在目录排在标准库之前,因此与标准库同名的在该目录下的自定义文件会替代标准库被优先导入。 Source: Python 2 and 3.

要注意的是python解释器会优先从built-in module中搜索module,找不到再从sys.path中搜索。built-in module可由命令sys.builtin_module_names获得,如
sys, time这些模块都是built-in module. (注意built-in modulebuilt-in function有区别, built-in function可以在builtins module找到,而builtins本身又是built-in module.)

如上面的目录结构中:timebuilt-in module, 而random是 standard module, 因此在start.py文件中import time会导入的python的内置module,但是 import random会导入我们自定义的module。

sys.path注意点

最后强调一遍:当运行一个python脚本时,sys.path不关心工作目录(当前目录)在哪里,只关心入口文件所在的目录在哪里。例如:如果当前目录在test/下,执行python ./packA/subA/subA1.py, sys.path[0]test/packA/subA/而不是test/

此外sys.path是导入模块所共享的。例如假设我们执行python start.py, 而start.py中执行了import packA.a1, 在a1中打印sys.path, 则它包含test/目录(入口文件start.py的路径), 而不是test/packA/(a1,py的路径); 因此在a1.py中导入other应该执行import other ,因为other.py在搜索路径test/中。

__init__.py详解

__init__.py有以下2个功能:

  1. 将目录转化为可导入的package(python3.3及其之前)
  2. 执行package的初始化代码

将目录转化为可导入的package

要导入不在入口文件所在路径的module或package,该module需要在package中。

而package在python3.3之前都需要包含一个__init__.py文件,该文件可以为空。比如用python2.7运行start.py,它可以import packA, 但是不能import packB, 因为在 test/packB中不存在__init__.py

不过在python3.3及其以后的版本,所有目录都被认为是package。假如用pythno3.6运行以下命令,输入如下:

>>> import packB
>>> packB
<module 'packB' (namespace)>

运行package的初始化代码

每当import package时,python会先执行__init__.py中的代码。任何在__init__.py中定义的对象或文件,都在该包的namspace中。
例如:
test/packA/a1.py

def a1_func():
    print("running a1_func()")

test/packA/__init__.py

## this import makes a1_func directly accessible from packA.a1_func
from packA.a1 import a1_func

def packA_func():
    print("running packA_func()")

test/start.py

import packA  # "import packA.a1" will work just the same

packA.packA_func()
packA.a1_func()
packA.a1.a1_func()

python start.py输出为:

running packA_func()
running a1_func()
running a1_func()

导入package

导入包含__inti__.py的package概念上等效于导入__init__.py作为一个模块,这确实也是python的处理方式,从以下输出可以看出来:

>>> import packA
>>> packA
<module 'packA' from 'packA\__init__.py'>

absolute导入与relative导入:重点

absolute导入:使用全路径导入(从入口文件所在位置算起)
relative导入:以当前模块(即脚本)作为相对位置来导入
其中relative导入分为2种:

  • explicit relative imports: 以from .<module/package> import X形式的导入,前面的点表示当前目录,两个点表示上一层目录。。。
  • implicit relative import: 就好像当前目录在sys.path中的导入,它只适用于python 2python3中不再支持:

The only acceptable syntax for relative imports is from .[module] import name. All import forms not starting with . are interpreted as absolute imports.

Source: What’s New in Python 3.0

例子如下:
假设运行入口文件是start.py, 它导入了a1, 而a1又导入了other, a1和sa1, 则a1中的导入方式可以这样写:

  • absolute imports:
import other
import packA.a2
import packA.subA.sa1
  • explict relative imports:
import other  # absolute imports
from . import a2 # explict relative imports
from .subA import sa1  # explict relative imports
  • implicit relative imports(python3不支持)
import other  # absolute imports
import a2  # implicit relative imports
import subA.sa1  # implicit relative imports

需要注意的是, .只能上溯至入口文件所在目录(但不包括),因此from .. import other是不支持的,会报:ValueError: attempted relative import beyond top-level package。 因此只能用absolute import。

建议只使用absolute import , 不仅是因为明晰易懂,还因为相对导入的文件都无法直接运行,而绝对导入的module可以通过某种方式运行:将原来的入口文件所在目录添加到sys.path中,下文将做详细分析。

案例

Case1 不修改sys.path:

python start.py时,sys.path总包含test/目录,导入sa1.py中的helloWorld函数, 使用绝对路径导入:
from packA.subA.sa1 import helloWorld

Case2 可以修改sys.path:

假设start.py中导入了a2, a2中导入了sa2start.py永远需要直接运行;不过我们有时候也希望能够直接运行a2
但是问题是,当我们运行start.py时,sys.path中包含的目录是test/,但是当我们运行a2时,sys.path中包含的目录是test/packA/
当我们直接运行start.py时,在a2中要导入sa2, 导入语句是from PackA.subA import sa2; 但是直接运行a2时,上述导入方式就会报错,因为test/不在搜索路径中了,必须这样导入: from subA import sa2. 不过这样的导入语句如果运行start.py时又会报错(Python3),因为test/packA不在搜索路径中(不过python2的implicit relative import不报错,不过我们今后基本不再用python2, 而且根据python之禅:尽量使用唯一的最好方式,最好使用绝对导入)。
总结一下:

Run from packA.subA import sa2 from subA import sa2
start.py OK Py2 OK, Py3 fail (subA not in test/)
a2.py fail (packA not in test/packA/) OK

从上表看出,a2无论哪种导入sa2的方式,要么运行start.py时报错,要么运行a2时报错,没有同时都可以运行成功的方案。
下面提供了3种方案:

  1. 使用from packA.subA import sa2(中间一列),此时start.py当然没问题; 将命令行切换到test/目录下,运行python -m packA.a2,就等于直接运行a2了。
  2. 使用from packA.subA import sa2(中间一列),此时start.py当然没问题;我们可以在运行a2前更改sys.path,将test加入搜索路径, 这样直接运行a2也OK了。
# a2.py
import os, sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

# now this works, even when a2.py is run directly
from packA.subA import sa2

需要注意的是,如果python没有配置好,__file__有时候不准确,可以使用 built-in inspect包来解决(this StackOverflow answer)。

  1. 使用第三列的写法,可是只对python2有效
  2. 使用from packA.subA import sa2(中间一列),同时将test/加入到PYTHONPATH环境变量。

根据python之禅,我推荐使用第二种方案。

There should be one-- and preferably only one --obvious way to do it.

Case3

a2还是要导入sa2, 但这次我想直接运行a1,而非a2(start.py当然也要直接运行);则仍然可以用2,3,4的方案,但方案1不再起作用。

Case4:导入父级目录的module

例如,想直接运行sa1, 而sa1想导入a1, 此时只能通过修改sys.pathPYTHONPATH来做到,就是用方案2, 4。
但是我建议:写代码时,尽量不要导入父级目录的脚本。

其实pycharm的导入方式,就是修改了PYTHONPATHsys.path,不过它设置的地方比较多,细节待整理:

image1.png

image2.png

image3.png

python2与python3的区别

  1. python2支持implicit relative import, python3不支持
  2. python2的package要包含__init__.py,python3.3和更高版本把所有目录都认为是package(implicit namespace packages)
  3. from <module> import *语法在python2中可以写在函数中,python3只允许写在module一级。

其他散落的知识点

  1. __init__.py中使用__all__
  • documentation for Python 2 and 3
  1. pip install -e <project>将project的root目录加入sys.path
  1. from <module> import *不会将'_'开头的名称导入
  • documentation for Python 2 and 3
  1. 使用if __name__ == '__main__'检测脚本是直接运行还是被导入的
  • documentation for Python 2 and 3

总结

对于一般的开发工作,上文介绍python的导入机制已经足够用了。
但是如果你想开发package并且发布出去,那么再深入研究一番也是必须的。事实上,本文有大量知识点都有待深入挖掘,此外我觉得还可以研究一下pkgutilimportlib标准库模块。

补充:

写此文已过去2个月,我在import logging的时候又遇到了新的问题:

import logging
logging.config.fileConfig('logging.ini')

上文报AttributeError: module 'logging' has no attribute 'config'.
而我这样导入就没问题, 而且可以直接使用loggin.getLogger:

import logging.config
logging.config.fileConfig('logging.ini')
logger = logging.getLogger(__name__)

其实看一下logging模块就知道了:

logging $tree
.
├── __init__.py
├── __pycache__
│   ├── __init__.cpython-37.opt-1.pyc
│   ├── __init__.cpython-37.pyc
│   ├── config.cpython-37.opt-1.pyc
│   ├── config.cpython-37.pyc
│   ├── handlers.cpython-37.opt-1.pyc
│   └── handlers.cpython-37.pyc
├── config.py
└── handlers.py

__init__.py文件如下:

image.png

config是一个单独的模块,即使导入了logging,该模块也未导入,因此不能使用;而getLogger定义在__init__.py中,导入logging.config的时候也导入了logging,因此__init__.py中的任何函数都可以直接使用。
如果只是使用getLogger,只导入logging而不导入logging.config也是可以的:

import logging

logger = logging.getLogger(__name__)

这篇stackoverflow的回答也是这么说的:https://stackoverflow.com/questions/2234982/why-both-import-logging-and-import-logging-config-are-needed

后记

导入更深入的解释可以看此文,有视频可以帮助理解:Modules and Packages: Live and Let Die!
http://www.dabeaz.com/modulepackage/index.html

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

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,322评论 0 10
  • 今天我要分享的书是“你的灯亮着吗?”,副标题是“发现问题的真正所在”。本书第一版发表于1982年,全书12.2万字...
    肖颖阅读 291评论 0 0
  • “对欲望不理解,人就永远不能从桎梏和恐惧中解脱出来。如果你摧毁了你的欲望,可能你也摧毁了你的生活。如果你扭曲它,压...
    庄主与五少爷阅读 232评论 0 1
  • 天空飘着一朵奇怪的云 我有一个奇怪的笑容 像一朵白云 蓦然而至 聚成白雾 化成烟雨 轻轻滴落在叶上 溜入滔滔的江湖...
    小妮子vi阅读 612评论 1 2