python中的import详解_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__))

导入详解

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

关键点:

import 语句根据sys.path的路径进行搜索

sys.path自动包含入口文件所在目录,而不是包含working directory

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.py ,sys.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 module与built-in function有区别, built-in function可以在builtins module找到,而builtins本身又是built-in module.)

如上面的目录结构中:time是built-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个功能:

将目录转化为可导入的package(python3.3及其之前)

执行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

运行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

absolute导入与relative导入:重点

absolute导入:使用全路径导入(从入口文件所在位置算起)

relative导入:以当前模块(即脚本)作为相对位置来导入

其中relative导入分为2种:

explicit relative imports: 以from . import X形式的导入,前面的点表示当前目录,两个点表示上一层目录。。。

implicit relative import: 就好像当前目录在sys.path中的导入,它只适用于python 2, python3中不再支持:

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

例子如下:

假设运行入口文件是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中导入了sa2,start.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种方案:

使用from packA.subA import sa2(中间一列),此时start.py当然没问题; 将命令行切换到test/目录下,运行python -m packA.a2,就等于直接运行a2了。

使用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)。

使用第三列的写法,可是只对python2有效

使用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.path或PYTHONPATH来做到,就是用方案2, 4。

但是我建议:写代码时,尽量不要导入父级目录的脚本。

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

dacbed54d063

image1.png

dacbed54d063

image2.png

dacbed54d063

image3.png

python2与python3的区别

python2支持implicit relative import, python3不支持

python2的package要包含__init__.py,python3.3和更高版本把所有目录都认为是package(implicit namespace packages)

from import *语法在python2中可以写在函数中,python3只允许写在module一级。

其他散落的知识点

__init__.py中使用__all__

documentation for Python 2 and 3

pip install -e 将project的root目录加入sys.path

from import *不会将'_'开头的名称导入

documentation for Python 2 and 3

使用if __name__ == '__main__'检测脚本是直接运行还是被导入的

documentation for Python 2 and 3

总结

对于一般的开发工作,上文介绍python的导入机制已经足够用了。

但是如果你想开发package并且发布出去,那么再深入研究一番也是必须的。事实上,本文有大量知识点都有待深入挖掘,此外我觉得还可以研究一下pkgutil和importlib标准库模块。

补充:

写此文已过去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文件如下:

dacbed54d063

image.png

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

如果只是使用getLogger,只导入logging而不导入logging.config也是可以的:

import logging

logger = logging.getLogger(__name__)

后记

导入更深入的解释可以看此文,有视频可以帮助理解:Modules and Packages: Live and Let Die!

http://www.dabeaz.com/modulepackage/index.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值