python 模块的导入和包的构建

python 模块的导入和包的构建

  1. 模块

一个文件即模块

一个文件夹即一个包

python包中需要声明一个__init__.py文件,包被导入的时候,默认会先执行这个文件的代码

  1. 导入方式

有了包的机制,既可以把项目拆分封装,也可以实现独立的功能给别的项目使用。python提供了import指令,鉴于历史原因,import有相对导入和绝对导入两种方式。相对导入有隐式的导入和显示导入。

模块的导入都是相对包而言。

import指令是加载包的模块,通过from import 语句不仅可以加载模块,也可以导入python对象(类、函数、变量等)。

模块是最小组织单位是文件,import后面的如果是文件则被认为是模块,若是文件中的python对象,则加载的不是模块。

  1. 模块搜索

1)首先搜索sys.modules:这是一个列表,它存储了之前导入的所有模块,新导入的模块也会追加到这个列表中。若搜索不到,那么就会进行第二步。

2)其次搜索build-in modual:python的标准库和安装的第三方软件包,如果还搜索不到,就进行最后一步。

3)最后搜索sys.path:被执行的python文件,其所在的目录会被追加到sys.path列表,也就是相对于被执行的文件目录夹和系统在sys.path的也会被搜索。若还找不到,最终会抛出ModualNotFound错误。

  1. python文件加载方式

python文件加载方式有两种,直接运行和以模块方式加载。

1)以top-level方式直接加载

python+文件名。如python filename.py 或者 python dir/filename.py,这样的python文件是作为top-label脚本运行,脚本文件的__name__属性会被设置成__main__,同时__package__属性设置为None。因为此时的文件作为顶层模块,他不属于任何一个包。直接运行脚本,会把脚本所在的目录追加到sys.path之中。

2)以模块方式加载

使用-m 解释参数,然后跟着文件路径,其中/替换成.。如python -m filename 或者 python dir.filename,这种方式,不会把脚本所在位置加载sys.path之中,而是会把执行python命令所在的目录加载sys.path中。但是被执行的脚本肯定也在执行命令所在的文件或者子文件中,因此效果类似于也属于sys.path之中。

  1. 相对导入和绝对导入

上文介绍了python脚本加载和模块搜索的基本方式。基于此,python提供了以相对和绝对导入包、模块的import方式。因为python2和python3分裂的历史,2默认是相对导入,3则是绝对导入,并且日常开发也推荐使用绝对导入方式。

它们有什么区别呢?

导入都是针对包而言
1)绝对导入: 文件的impor或者from import 导入语句,都是从包的根路径开始
2)相对导入: 导入的起始模块未必是从包路径开始,使用. 或者…的方式是显示的相对导入,否则是隐式的;python3针对隐式相对导入会直接抛错。

示例:

文件的目录结构如下,myproj项目中,有一个pkg的包,包里面有两个文件夹subpkg_a和subpkg_b两个子包,子包分别有几个py模块文件。

–> myproj tree

.|____ pkg

​ |— __init__.py

​ |— main.py

​ |— subpkg_a

​ | |— __init__.py

​ | |— hello.py

​ | |— world.py

​ |— subpkg_b

​ |— __int__.py

​ |— welcome.py

hello.py的模块名为__main__.py.作为top-level 执行的文件,其__package__是None,__name____main__.对于world.py文件,因为他是被加载的模块,__name__就是文件名。

hello.py很好理解,作为top-level脚本执行,其本身不属于任何一个包。world.py是作为模块被加载的,但是hello.py 里也没指定从哪个包里加载。因此加载时候就按照模块搜索方式。因为hello.py执行的目录被加入了sys.pathworld.pyhello.py 同级,因此自然能被搜索并成为模块。

隐式相对导入

由于上面的导入方式,模块都不属于任何一个包,自然就没有相对和绝对导入的说法。删掉subpkg_a/__init__.py文件也不会有影响。正如前面所介绍,python脚本若不是top-level,才有包的概念。修改执行的方式如下:

python -m subpkg_a.hello

('subpkg_a.__init__.py: ', 'subpkg_a')
('hello.py: ', 'subpkg_a.__main__')
('world.py: ', 'subpkg_a.world')

可以看到,subpak_a 包的__init__.py 文件也被加载执行了,这表示包 subpak_a 被导入了。-m 的语法告诉了解释器,把当前执行命令的目录加入到 sys.path。以模块的方式加载 hello.py 文件,并且指定了hello 模块的父级是 subpkg_a,同理,处于同级的 world.py 文件也被隐式的包含在 subkag_a 包里,它的模块名subpkg_a.world

上面的 import 语句中没有出现包 subpkg_a,所以是一种相对导入,也没有使用.或者.. 符号,所以是隐式的导入。隐式导入在python3下不支持,会抛错:

python3 -m subpkg_a.hello
subpkg_a.__init__.py:  subpkg_a.subpkg_a
hello.py:  subpkg_a.__main__
Traceback (most recent call last):
  File "/data/home/niulijun/python3/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/data/home/niulijun/python3/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/data/home/niulijun/my_proj/subpkg_a/hello.py", line 7, in <module>
    import world
ModuleNotFoundError: No module named 'world'

显示相对导入:

subpkg_a包的层级很清晰,因此改为显示相对导入也很简单,即:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

if __package__:
    print('hello.py: ', '{}.{}'.format(__package__, __name__))
else:
    print('hello.py: ', '{}'.format(__name__))

from . import world

运行结果

python -m subpkg_a.hello
('subpkg_a.__init__.py: ', 'subpkg_a')
('hello.py: ', 'subpkg_a.__main__')
('world.py: ', 'subpkg_a.world')

需要注意的是,显示的相对导入只有以模块加载的方式才能使用,否则会抛:

python subpkg_a/hello.py
('hello.py: ', '__main__')
Traceback (most recent call last):
  File "subpkg_a/hello.py", line 7, in <module>
    from . import world
ValueError: Attempted relative import in non-package

正如前文所述,以top-level的运行hello.py,hello.py的模块名是__main__,world.py的模块名是world,两者不属于任何一个包,自然也就没有模块的层级。.是相对于包下面的模块的路径进行导入。正如这样没有包的概念,因此抛错。

对于subpkg_b包里的模块,需要使用…操作符,修改hello.py如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import world
from ..subpkg_b import welcome

if __package__:
    print('hello.py: ', '{}.{}'.format(__package__, __name__))
else:
    print('hello.py: ', '{}'.format(__name__))

运行结果如下:python -m subpkg_a/hello

python -m subpkg_a/hello 
Traceback (most recent call last):
  File "/opt/python2710/lib/python2.7/runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/opt/python2710/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/data/home/niulijun/my_proj/pkg/subpkg_a/hello.py", line 3, in <module>
    from . import world
ValueError: Attempted relative import in non-package

cd …/

python -m pkg.subpkg_a.hello 时运行结果如下:

python -m pkg.subpkg_a.hello
('subpkg_a.__init__.py: ', 'pkg.subpkg_a')
('world.py: ', 'pkg.subpkg_a.world')
('hello.py: ', 'pkg.subpkg_a.__main__')

综上所述,相对导入,导入的路径中,都没有出现包名

绝对导入

隐式相对导入在py3被禁止了,显示相对导入也不是默认的,那么最好还是使用绝对导入。即从包的起始位置书写import路径

修改main.py,将import从跟包开始书写路径

cat pkg/main.py

if __package__:
    print('main.py: ', '{}.{}'.format(__package__, __name__))
else:
    print('main.py: ', '{}'.format(__name__))

from pkg.subpkg_b import welcome
from pkg.subpkg_a import hello

因此使用绝对导入开发一个lib是更好的实践,可是正如上面subpkg_a所面临的问题,开发过程中,直接运行,可能会报错,不得不使用-m 的方式。为了更好的开发,可以使用下面介绍的包结构。

  1. 小结

1) 程序规模变大变复杂,通常进行模块拆分和封包复用。python文件及模块的基本组织单位,文件夹则是基础包。包或者模块的引用可以使用 import 或者 from import 语法。

2)Import有相对导入和绝对导入,相对导入又有显式和隐式两种。显式则使用.或者..操作符。相对还是绝对,针对的是python文件被加载的方式。

3)直接运行python文件则是以top-level方式,当前文件模块名是__main__,它本身就是顶级模块,不存在包的概念。若使用-m参数,则以模块方式加载,模块方式加载都是相对包而言。. 表示在同一个包内,被相对被加载文件的路径进行加载导入的模块。

4)相对导入的文件里不会出现包名,绝对导入的文件里,import语句必须包含包名。同时所导入的包都必须从包名的根路径开始,写出完整的模块路径。

5)Python3不在支持隐式相对导入。官方也更推荐使用绝对导入。
文件则是以top-level方式,当前文件模块名是__main__,它本身就是顶级模块,不存在包的概念。若使用-m参数,则以模块方式加载,模块方式加载都是相对包而言。. 表示在同一个包内,被相对被加载文件的路径进行加载导入的模块。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值