关于python3模块与包

之前写脚本基本上都是在一个目录中完成,导入方便, 短平快,现在自己写稍微大一点的程序,就栽在模块导入上,学艺不精,重新看《学习手册》和实践,做一次总结。

模块

模块是为了方便重用,通常都是放在一个目录下面,调用方便,因为所有文 件都在顶层,不存在路径问题。

  • import 获取一个模块的整体
  • from 获取一个模块的属性
# mod_4.py
def mod4():
    print("in mode_4")
mod_4 = 4
  • mod_4_1.py 调用 mod_4 模块,模块必需以py结尾,导入时文件名去掉后缀
# mod_4_1.py
import mod_4
from mod_4 import mod4
def mod41():
    print("in mod41")
	mod_4.mod4()               # import 导入整个模块
mod41 = 41
mod41()
mod4()                               # from 导入模块属性
--------------------------------------------
terry@terry:/test_dir0$ python3 dir1/dir3/dir4/mod_4_1.py 
in mod41
in mode_4
in mode_4

模块递归导入

模块递归导入是多个文件互相导入的情况,虽然在引用属性时有点,但与包导入的意义是不一样的

# mod_4_2.py
import mod_4_1
def mod42():
    mod_4_1.mod_4.mod4()        # 这边的点号没有目录结构,仅仅是属性引用
mod42()
----------------------------------------------------
terry@terry:/test_dir0$ python3 dir1/dir3/dir4/mod_4_2.py 
in mod41                         # 多余的输出
in mode_4                       # 多余的输出
in mode_4                       # 多余的输出
in mode_4

name == 'main'

这是一个讨巧的方法,当我们调用模块时我们只关心我们所需要的接口数据,而不需要一些多余的输出,如果要测试模块本身,我们又希望模块能直接运行,因此当执行为脚本时,name__会变成__main,此时模块当脚本来执行,当被模块调用时 __name__是模块名。

修改 # mod_4_1.py 减少不必要的输出,测试文件名
import mod_4
from mod_4 import mod4
def mod41():
    print("in mod41")
	mod_4.mod4()
mod_41 = 41
print('file name is %s' % (__name__))    # 测试文件名
if __name__ == '__main__':
    mod41()                        # 调用放在执行文件中,模块调用时不执行
	mod4()                          # 调用放在执行文件中,模块调用时不执行
--------------------------------------------------------
terry@terry:/test_dir0$ python3 dir1/dir3/dir4/mod_4_2.py 
file name is mod_4_1          # 当模块调用时 file name 是 'mod_4_1'
in mode_4                           # 没有多余的输出,只有接口输出
---------------------------------------------------
terry@terry:/test_dir0$ python3 dir1/dir3/dir4/mod_4_1.py 
file name is __main__         # 当执行文 件时,file name是 '__main__'
in mod41                             # 调用 file_name = main下面的程序
in mode_4
in mode_4

包导入

包导入是相对于包来说,在python2中包必需要有__init__.py, 在python3.4后没有这个要求

  • 绝对导入

使用路径语法,由顶层目录一直往下

  • 相对导入

包内导入相对于包,而不是完整路径

  • python2

默认search路径为包内

  • python3

默认为包外 sys.path, 如果包内search 需要 from . 显示定义

包结构
terry@terry:/test_dir0$ tree -L 5
.
├── dir1                                           # 第一 子层
│   ├── dir2                                      # 第二子层
│   │   └── mod_2.py
│   └── dir3                                      # 第二子层
│       ├── dir4                                  # 第三子层
│       │   ├── mod_4_1.py
│       │   ├── mod_4_2.py
│       │   └── mod_4.py
│       └── mod_3.py
└── top.py                                        # 顶层
顶层文件导入子模块

这是包导入的核心,一般应用程序会有一个主程序入口,其它模块为主程序调用,主程序处在包的顶层,如 top.py

# top.py
from dir1.dir3.dir4 import mod_4
from dir1.dir3.dir4.mod_4_1 import mod41
print(mod_4.mod_4)
mod41()
----------------------------------------------------
terry@terry:/test_dir0$ python3 top.py
Traceback (most recent call last):
  File "top.py", line 7, in <module>
    from dir1.dir3.dir4.mod_4_1 import mod41
  File "/test_dir0/dir1/dir3/dir4/mod_4_1.py", line 6, in <module>
    import mod_4
ImportError: No module named 'mod_4'

这个错误提示没有mod_4模块,因为当执行 dir1/dir3/dir4/mod_4_1.py可能找到同级目录下的mod_4.py,而现在执行脚本的目录在 test_dir0/, 该目录下没有 mod_4.py

# mod_4_1.py
import dir1.dir3.dir4.mod_4                         # import 完整路径
from dir1.dir3.dir4.mod_4 import mod4       # from 完整绝对路径
def mod41():
    print("in mod41")
	dir1.dir3.dir4.mod_4.mod4()                 # 包导入时用import是不明智的
mod_41 = 41
print('file name is %s' % (__name__))    # 测试文件名
if __name__ == '__main__':
    mod41()                        
	mod4()                          
----------------------------------------------------------
terry@terry:/test_dir0$ python3 top.py
file name is dir1.dir3.dir4.mod_4_1
4
in mod41
in mode_4

当执行顶层文件,子包导入时需标明从顶层文件开始寻找的目录路径,否则找不到模块

相对导入方式
# mod_4_1.py
from .mod_4 import mod4       # 相对导入只支持 from . 的显示定义
def mod41():
    print("in mod41")
	mod4()                 # 包导入时用import是不明智的
mod_41 = 41
print('file name is %s' % (__name__))    # 测试文件名
if __name__ == '__main__':
    mod41()                        
	mod4()                          
----------------------------------------------------------
terry@terry:/test_dir0$ python3 top.py
file name is dir1.dir3.dir4.mod_4_1
4
in mod41
in mode_4

可见相对导入的方式比绝对导入写起来要方便,绝对导入以顶层文件作为基础 dir1.dir3.dir4.mod_4 相对导入以现在文件所在目录作为参考导入同级文 件 from .mod import mod4, 上级为 from ..dir4.mod4_4 import mod4, 上上级目录 from ...dir2 import mod_2

import的讨巧方式
# mod_4_1.py
import dir1.dir3.dir4.mod_4 as md      # 相对导入只支持 from . 的显示定义
def mod41():
    print("in mod41")
	md.mod4()                 # 用 as 可以替换 dir1.dir3.dir4.mod_4.mod4()
mod_41 = 41
print('file name is %s' % (__name__))    # 测试文件名
if __name__ == '__main__':
    mod41()                        
	mod4()                          
----------------------------------------------------------
terry@terry:/test_dir0$ python3 top.py
file name is dir1.dir3.dir4.mod_4_1
4
in mod41
in mode_4
执行包模块
terry@terry:/test_dir0$ python3 dir1/dir3/dir4/mod_4_1.py 
Traceback (most recent call last):
  File "dir1/dir3/dir4/mod_4_1.py", line 8, in <module>
    import dir1.dir3.dir4.mod_4 as md
ImportError: No module named 'dir1'
  • 找不到dir1是因为python3执行 dir1/dir3/dir4/mod_4_1.py,此时顶层在dir1/dir3/dir4下面,dir1已经超过顶层了
  • 如果要执行 mod_4_1.py必需改成 import mod_4, 如果要让top.py能正确运行则必需 import dir1.dir3.dir4.mod_4 as md 有路径信息,有什么方法可以既让top.py正确执行,又可以执行mod_4_1.py自测呢
terry@terry:/test_dir0$ python3 -m dir1.dir3.dir4.mod_4_1
file name is __main__
in mod41
in mode_4
in mode_4

通过执行模块的方式 python3 -m dir1.dir3.dir4.mod_4_1执行,此时相对导入也可行,

# mod_4_1.py
from .mod_4 import mod4       # 相对导入只支持 from . 的显示定义
def mod41():
    print("in mod41")
	mod4()                 # 包导入时用import是不明智的
mod_41 = 41
print('file name is %s' % (__name__))    # 测试文件名
if __name__ == '__main__':
    mod41()                        
	mod4()                          

相对导入更加灵活,执行路径不一定要在/test_dir0/下面,也可以在/test_dir0/dir1/,/test_dir0/dir1/dir2/, /test_dir0/dir1/dir2/dir3

terry@terry:~/learn/python/module/twentythree/test_dir0/dir1/dir3$ python -m 
dir4.mod_4_1
/usr/bin/python: No module named dir4

上面提示的错误是因为没有 init.py, python2不认为dir4是一个包,而提示错误, python3.5没有这个限制

在子模块中导入父级模块
terry@terry:/test_dir0$ tree -L 5
.
├── dir1                                           # 第一 子层
│   ├── dir2                                      # 第二子层
│   │   └── mod_2.py
│   └── dir3                                      # 第二子层
│       ├── dir4                                  # 第三子层
│       │   ├── mod_4_1.py
│       │   ├── mod_4_2.py
│       │   └── mod_4.py
│       └── mod_3.py
└── top.py                                        # 顶层
  • 如果要在mod_4_1.py中导入 dir2中的mod_2.py,要如何写
# mod_2.py
def mod2():
    print('in mod_2')
mod_2 =2 
# mod_4_1.py
from . import mod_4 as md   # 同级别包,或子级别包用相对更好
from ...dir2 import mod_2 (import dir1.dir2.mod2 as mod_2) 
def mod41():
    print('in mod41')
	md.mod4()
mod_41 = 41
if __name__ == '__main__':
    mod41()
	md.mod4()
	print(mod_2.mod_2)
-------------------------------------------------
terry@terry:/test_dir0$ python3 -m dir1.dir3.dir4.mod_4_1
file name is __main__
in mod41
in mode_4
in mode_4
2
-------------------------------------------------
terry@terry:/test_dir0/dir1/dir3/dir4$ python3 mod_4_1.py 
Traceback (most recent call last):
  File "mod_4_1.py", line 4, in <module>
    from ..dir4 import mod_4 as md
SystemError: Parent module '' not loaded, cannot perform relative import
包模块不允许直接运行

总结

  1. 包导入相对于模块具有目录路径信息,导入时使用相对导入较好,一方面执行路径可选择, 另一方面避免硬编码,一旦顶层目录更名,所有模块都要改写
  2. 无论是包绝对导入还是包相对导入, 都不能直接运行, 需要通过 python3 -m A.B.C方式运行
  3. 绝对导入确定了顶层,并需要写完整路径,相对导入则较为灵活
  4. python2要注意每个包必需有__init__.py,
  5. 包导入在执行模块时注意路径,相对导入不能超过顶层包attempted relative import beyond top-level package,说明执行的目录不够,还需要 cd ..

参考

  1. 《Python 学习手册》第四版
  2. https://segmentfault.com/q/1010000012365228 pep420
  3. https://www.cnblogs.com/soukingang/p/5517097.html python2/3不同

转载于:https://my.oschina.net/hding/blog/3007985

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值