当前目录 和 脚本目录
参考资料: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的导入实际上是有很多学问的,弄懂它对开发运维都有巨大帮助, 我从上面这位斯坦福大学大二学生的教程中受益匪浅,决定把它编译下来,顺带改正几处他的笔误。读懂了上文,只要你不准备写第三方库,那么基本能够应付几乎所有的导入问题了。
关键点:
-
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
)
- python 3.3以后,任何目录都是package(即使没有
例子的目录结构
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
<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 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.
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
中导入了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
,不过它设置的地方比较多,细节待整理:
image2.png
image3.png
python2与python3的区别
- python2支持
implicit relative import
, python3不支持 - python2的package要包含
__init__.py
,python3.3和更高版本把所有目录都认为是package(implicit namespace packages) -
from <module> import *
语法在python2中可以写在函数中,python3只允许写在module一级。
其他散落的知识点
-
__init__.py
中使用__all__
-
pip install -e <project>
将project的root目录加入sys.path
-
from <module> import *
不会将'_'开头的名称导入
- 使用
if __name__ == '__main__'
检测脚本是直接运行还是被导入的
总结
对于一般的开发工作,上文介绍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
文件如下:
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
网友评论