Skip to content

模块和包

python 的模块都是天然的单例

模块

import 加载的模块分为四个通用类别:

  • 使用 python 编写的代码(.py 文件)
  • 已被编译为共享库或 DLL 的 C 或 C++扩展
  • 包好一组模块的包
  • 使用 C 编写并链接到 python 解释器的内置模块

我们可以从sys.modules中找到当前已经加载的模块,
sys.modules是一个字典,内部包含模块名与模块对象的映射,
该字典决定了导入模块时是否需要重新导入。

示例

自定义模块文件名my_module.py,模块名my_module

python
# my_module.py
print("from the my_module.py")

money = 1000


def read1():
    print("my_module->read1->money", money)


def read2():
    print("my_module->read2 calling read1")
    read1()


def change():
    global money
    money = 0

每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,
这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突
同时模块也是单例类,多次导入,'from the my_module.py'也只被 print 一次。

python
# 测试:执行my_module.change()操作的全局变量money仍然是my_module中的
# demo.py

import my_module

money = 1
my_module.change()  # 改变的是my_module.money

print(money)
"""
执行结果:
from the my_module.py
1
"""

在一行导入多个模块

python
import sys, os, re

别名

python
import my_module as sm

from ... import...

python
from my_module import read1, read2

my_module.read1()  # 可用
read1()  # 可用

如果当前命名空间有重名 read1 或者 read2,那么会有覆盖效果。

别名

python
from my_module import read1 as read

全部导入

python
from my_module import *  # 将模块my_module中所有的名字都导入到当前名称空间

此时需要在在my_module.py中新增一行

python
__all__ = ["money", "read1"]

这样在另外一个文件中用from my_module import *就这能导入列表中规定的两个名字

模块的加载与修改

每个模块相当于单例类,导入一次立被实例化,放入字典sys.modules中了。
python 不支持重新加载或卸载之前导入的模块
如果只是你想交互测试的一个模块,使用importlib.reload(modulename)
而且这只能用于测试环境。

python
# aa.py
def func1():
    print("func1")
python
import time, importlib
import aa

time.sleep(20)
# importlib.reload(aa)
aa.func1()

在 20 秒的等待时间里,修改 aa.py 中 func1 的内容,等待 test.py 的结果。
打开 importlib 注释,重新测试

把模块当做脚本执行

我们可以通过模块的全局变量__name__来查看模块名:
当做脚本运行:
__name__ 等于'__main__'
当做模块导入:
__name__= 模块名
作用:用来控制.py 文件在不同的应用场景下执行不同的逻辑

if __name__ == '__main__':

模块搜索路径

python 解释器在启动时会自动加载一些模块,可以使用sys.modules查看
模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path 路径中包含的模块
在初始化后,python 程序可以修改sys.path,路径放到前面的优先于标准库被加载

编译 python 文件

为了提高加载模块的速度,强调强调强调:提高的是加载速度而绝非运行速度。
python 解释器会在__pycache__目录中下缓存每个模块编译后的版本,
格式为:module.version.pyc。
通常会包含 python 的版本号。

例如,在 CPython3.3 版本下,
my_module.py模块会被缓存成__pycache__/my_module.cpython-33.pyc
这种命名规范保证了编译后的结果多版本共存。

dir()函数

内置函数dir()可以用来查找模块中定义的名字,返回一个有序字符串列表

包是一种通过使用.模块名来组织 python 模块名称空间的方式。

  • 无论是 import 形式还是 from...import 形式,凡是在导入语句中(而不是在使用时)遇到带点的,都是关于包独有的导入语法,如from moviepy.editor import *
  • 包是目录级的(文件夹级),包的本质就是一个包含__init__.py文件的目录
    • import 导入文件时,产生名称空间中的名字来源于文件,
    • import 包时,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件

强调:

  1. 而在 python2 中,包下没有__init__.py文件,import 包会报错 2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块

绝对导入和相对导入

内部互调

我们的最顶级包 glance 是写给别人用的,(结构图见最下)
然后在 glance 包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:

  • 绝对导入:以 glance 作为起始
  • 相对导入:用.或者..的方式为起始(只能在一个包中使用,不能用于不同目录内)
python
# 绝对导入
from glance.cmd import manage

manage.main()

# 相对导入
from ..cmd import manage

manage.main()

外部测试结果:注意一定要在于 glance 同级的文件中测试

python
from glance.api import versions

注意:

在使用 pycharm 时,有的情况会为你多做一些事情
因而在测试时,一定要回到命令行去执行

__init__.py决定的外部用例

单独导入包名称时,不会导入包中所有包含的所有子模块,
即不会自动导入同级所有目录和 python 文件(子模块)。
模块内方法的调用方式取决于各级的__init__.py文件。

默认的

很多第三方模块的各级__init__.py文件是如下书写的

python
# glance/__init__.py
from . import cmd

# glance/cmd/__init__.py
from . import manage

此时外部可以这样调用:

python
import glance

glance.cmd.manage.main()

惯例的

也可以这样书写使无论什么方式都可以调用:

shell
glance/
├── __init__.py     from .api import *
                   __version__ = "1.0.0"
                   __all__ = ['api','cmd','db']
├── api

   ├── __init__.py   __all__ = ['policy','versions']
                from .versions import *

   ├── policy.py

   └── versions.py  def get_now_version():
                        print("1.0.0")

├── cmd

   ├── __init__.py      __all__ = ['manage']

   └── manage.py

└── db

    ├── __init__.py   __all__ = ['models']

    └── models.py

此时外部怎样调用都行:

shell
>>> import glance
>>> glance.api.versions.get_now_version()
"1.0.0"
>>> glance.versions.get_now_version()
"1.0.0"
>>> glance.get_now_version()
"1.0.0"

也就是说,这里的__init__.py里写了
from .api import * 它的效果是含有 from . import api
当多重调用方式有重名的时候,深度优先
如果get_now_version函数就叫versions

shell
>>> import glance
>>> glance.api.versions.versions()
AttributeError: 'function' object has no attribute 'versions'
>>> glance.versions.versions()
AttributeError: 'function' object has no attribute 'versions'
>>> glance.api.versions()
"1.0.0"
>>> glance.versions()
"1.0.0"

虚拟环境

创建虚拟环境并进入虚拟环境需要使用 Python 自带的venv模块。首先,确保安装了 Python 3.3 版本及以上,然后打开终端(MacOS/Linux)或命令行(Windows),执行以下命令:

bash
# 创建一个名为my_env的虚拟环境
python3 -m venv my_env

# 进入虚拟环境
# MacOS/Linux
source my_env/bin/activate

# Windows
my_env\Scripts\activate

在进入虚拟环境后,可以使用pip安装各种包,这些包都会安装到当前虚拟环境中,不会影响全局环境。要退出虚拟环境,可以在终端或命令行中执行deactivate命令。可以使用conda env list查看已创建的虚拟环境列表。pip freeze来查看当前环境已安装的包的版本