Loading

一、包介绍

随着模块数目的增多,把所有模块不加区分地放到一起也是极不合理的,于是Python为我们提供了一种把模块组织到一起的方法,即创建一个包。

包就是一个含有_ _ init _ _.py文件的文件夹,文件夹内可以组织子模块或子包,例如:

pool/                # 顶级包
├── __init__.py     
├── futures          # 子包
│   ├── __init__.py
│   ├── process.py
│   └── thread.py
└── versions.py      # 子模块

需要强调的是:

  1. 在Python3中,即使包下没有_ _ init_ _.py文件,import 包仍然不会报错,而在Python2中,包下一定要有该文件,否则import 包报错;
  2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块;

接下来我们就以包pool为例来介绍包的使用,包内各文件内容如下:

  • process.py:

    # process.py
    class ProcessPoolExecutor(object):
        def __init__(self, max_workers):
            self.max_workers = max_workers
    
        def submit(self):
            print('ProcessPool submit')
    
  • thread.py:

    # thread.py
    class ThreadPoolExecutor:
        def __init__(self, max_workers):
            self.max_workers = max_workers
    
        def submit(self):
            print('ThreadPool submit')
    
  • versioons.py:

    # versions.py
    def check():
        print('check versions')
    

_ _ init _ _.py文件内容均为空

二、包的使用

2.1 导入包与_ _ init_ _.py

包属于模块的一种,因而包以及包内的模块是用来被导入使用的,而绝非被直接执行,首次导入包(如import pool)同样会做三件事:

  1. 执行包下的_ _ init_ _.py文件;
  2. 产生一个新的名称空间用于存放_ _ init_ _.py执行过程中产生的名字;
  3. 在当前执行文件所在的名称空间中得到一个名字pool,该名字指向_ _ init_ _ .py的名称空间,例如pool.xxx和pool.yyy中的xxx和yyy都是来自于pool下的_ _ init_ _.py,也就是说导入包时并不会导入包下所有的子模块与子包:
import pool

pool.versions.check()  # 抛出异常AttributeError
pool.futures.process.ProcessPoolExecutor(3)  # 抛出异常AttributeError

pool.versions.check()要求pool下有名字versions,进而pool.versions下有名字check。pool.versions下已经有名字check了,所以问题出在pool下没有名字versions,这就需要在pool下的_ _ init_ _.py中导入模块versions。

强调:

  1. 关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如import 顶级包.子包.子模块,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性);
  2. 包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间;
  3. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的_ _ init_ _.py,导入包本质就是在导入该文件;

2.2 绝对导入与相对导入

针对包内的模块之间互相导入,导入的方式有两种:

1、绝对导入:以顶级包为起始:

# pool下的__init__.py
from pool import versions

2、相对导入:. 代表当前文件所在的目录,.. 代表当前目录的上一级目录,依此类推:

# pool下的__init__.py
from . import versions

同理,针对pool.futures.process.ProcessPoolExecutor(3),则需要:

  1. 操作pool下的_ _ init_ _.py,保证pool.futures:

    from . import futures  # 或from pool import futures
    
  2. 操作futrues下的_ _ init_ _.py,保证pool.futures.process:

    from . import process  # 或from pool.futures import process
    

在包内使用相对导入还可以跨目录导入模块,比如thread.py中想引用versions.py的名字check。

import也能使用绝对导入,导入过程中同样会依次执行包下的_ _ init _ _.py,只是基于import导入的结果,使用时必须加上该前缀。

例1:

import pool.futures  # 拿到名字pool.futures指向futures下的__init__.py
 
pool.futures.xxx  # 要求futures下的__init__.py中必须有名字xxx 

例2:

import pool.futures.thread  # 拿到名字pool.futures.thread指向thread.py
 
thread_pool = pool.futures.thread.ThreadPoolExecutor(3)
thread_pool.submit()

相对导入只能用from module import symbol的形式,import ..versions语法是不对的,且symbol只能是一个明确的名字

from pool import futures.process  # 语法错误
from pool.futures import process  # 语法正确

针对包内部模块之间的相互导入推荐使用相对导入,需要特别强调:

  1. 相对导入只能在包内部使用,用相对导入导入不同目录下的模块是非法的;
  2. 无论是import还是from … import …,但凡是在导入时带点的,点的左边必须是包,否则语法错;
img

2.3 from 包 import *

在使用包时同样支持from pool.futures import * ,毫无疑问*代表的是futures下__ init__.py中所有的名字,通常是用变量 __all__来控制*代表的意思:

# futures下的__init__.py
__all__ = ['process', 'thread']

最后说明一点,包内部的目录结构通常是包的开发者为了方便自己管理和维护代码而创建的,这种目录结构对包的使用者往往是无用的,此时通过操作__ init__.py可以“隐藏”包内部的目录结构,降低使用难度,比如想要让使用者直接使用pool下的所有名字:

import pool
 
pool.check()
pool.ProcessPoolExecutor(3)
pool.ThreadPoolExecutor(3)

需要操作pool下的__ init__.py:

from .versions import check
from .futures.process import ProcessPoolExecutor
from .futures.thread import ThreadPoolExecutor
img
posted @ 2021-11-30 16:09  JZEason  阅读(52)  评论(0编辑  收藏  举报