Python(14)-模块与import

Python中import机制可以导入我们需要使用的库,避免编写重复代码。此篇我们来看看什么事模块,又该如何导入模块。

1. 模块 与 import

【模块Module】在.py文件中定义的函数、全局变量都是可以提供给外界使用的工具,因此一个.py文件可以称为一个模块, 模块名为源码文件名。Python标准库/第三方库都由一个个模块组成的,我们也可以编写属于自己的个人工具库,该工具库由若干个人模块组成。 相关概念:

  1. 模块:一个以.py为后缀的文件就是一个模块
  2. 常规包:__init__.py所在目录就是一个常规包
  3. 命名空间包:命名空间包是一种虚拟的概念,它由多个子包构成,这些子包可以在任意位置,可以为 zip 中的文件或网络上的文件,这些子包在概念上是一个整体,这个整体就是一个命名空间包.「常规包」与「命名空间包」的概念在 PEP420 被提出,在 Python3.3 及之后的 Python 版本中实现,此前只有「常规包」这一种包。

【导入import】在导入模块的过程中,首先会搜索 sys.modules中内容,sys.modules 是模块的缓存,其中包括了所有此前导入的模块。 import 一个新的模块时,实际上完了两个操作:

  1. 搜索操作:在sys.path定义的路径中搜索指定名称的模块
  2. 绑定操作:将搜索到的结果绑定到当前作用域对应的名称上 (即创建 module对象,并初始化),[如果未找到,则抛出 ModuleNotFoundError]

【import 模块的两种方式】

# 模块导入的两种方式
import os                # 方式1:import后面只能接模块或包 (常规包或命名空间包)
from os.path import join # 方式2:from后只能接模块或包,而此时import后可以接任何变量 (模块、包或模块中具体的方法)

# 方式1 import A时,会先检查 sys.moduels 中是否存在模块A,如果存在,则不会再去搜索加载;如果没有,则搜索模块并进行绑定操作,随后便将其加载到 sys.modules 中
# 方式2 from A import B时,此时依旧会先检查 A 并进行相同的操作,随后获得 A 的 module对象后,从中解析并寻找 B 然后再填充到 A 的__dict__结构中,从而实现不用标明来自于 A,直接通过 B 的名称就可直接使用的 B 的效果。

Python2.6 之前使用 relative import (相对导入) 作为默认 import 机制,
Python2.6 之后使用 absolute import (绝对导入) 作为默认 import 机制;

# 1.相对导入格式为:其中.表示当前模块,.. 表示上层模块 (与相对路径概念类似)
from . import B 
from ..A import B
# 2. 绝对导入格式为
import A.B.C        
form A.B import C   
# 相对导入/绝对导入 这两个概念与相对路径/绝对路径的概念有些相似
# 在开发第三方模块时,通常使用相对导入的形式,这可以有效避免硬编码带来的问题,比如在开发过程中,修改了某个文件的名称,
# 此时,使用绝对导入形式的import就需要修改相应的名称。
# 这其实也不是绝对的,当包的结构发生改变时,相对导入就会出现问题,当相对模块名称的改变而言,目录结构的改变出现概率会小一些。

Tips && Todo:

  1. import 是最常用的导入机制,但并不是唯一的方式,importlib 模块以及内置的__import__() 方法都可以实现模块的导入。
  2. Python中定义了多种搜索策略去搜索相应的模块,而这些策略可以通过 importlib 等提供的各类 hook 进行修改实现一些有趣的效果。在 Python3.3 之后,所有的模块导入机制都会通过「sys.meta_path」暴露,不会在有任何隐式导入机制
  3. .pyc是编译过的二进制文件,Python作为一个解释性的语言,解释一行执行一行。import模块时,在执行的时,解释器将模块文件编译成.pyc文件,方便快速使用。因为库文件一般很少变动,如果对修改了模块内容,解释器会重新编译。.pyc文件是解释器加快运行速度,而对库文件进行统一编译而生成的一个二进制文件

参考博文:详解 Python import 机制 (一):import 中的基本概念

2. sys.path

sys.path: A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default. [From python document]

sys.path为import模块时的查询路径列表,为一个[List]。使用环境变量PYTHONPATH 、默认安装依赖 初始化。sys.path列表默认包含的路径有:

  1. current working directory/the script’s directory/an empty string
  2. 环境变量 PYTHONPATH 中指定的路径列表
  3. Python安装路径的lib目录所在路径

sys.path[0]: sys.path列表的第一项,与程序启动方式有关。在import模块时,sys.path[0]它将引导Python首先搜索当前目录中的模块。

【python 启动方式】

python script.py command line: prepend the script’s directory. If it’s a symbolic link, resolve symbolic links.
python -m module command line: prepend the current working directory.
python -c code and python (REPL) command lines: prepend an empty string, which means the current working directory.

我们可以将需要的路径(个人工具库/第三方库)添加到sys.path中,修改sys.path的3种方式如下:

2.1 sys.path.append() 和 sys.path.insert()

动态修改 sys.path:因为 sys.path为list,所以list操作append()和insert()可很轻松实现搜索路径修改,这种方式只会对当前项目临时生效。

(base) chenyingying@cyy-2 import_test % python
>>> import sys
>>> sys.path  # (...手动修改省略显示)
['', '/home/libtestpath/path1', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ...]
>>> sys.path.append("cyy_test1")
>>> sys.path.insert(0, "cyy_test0")
>>> sys.path
['cyy_test0', '', '/home/libtestpath/path1', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ..., 'cyy_test1']

2.2 PYTHONPATH

修改 PYTHONPATH 环境变量,这种方式会永久生效,而且所有的 Python 项目都会受到影响,因为 Python 程序启动时会自动去读取该环境变量的值。

# 默认情况下PYTHONPATH是空的, sys.path是一个列表,包括有所有查找包的目录 (...手动修改省略显示)
(base) chenyingying@cyy-2 import_test % echo $PYTHONPATH 

(base) chenyingying@cyy-2 import_test % python
>>> import sys
>>> sys.path
['', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ....]

# 修改环境变量PYTHONPATH(以:分隔各个路径),  sys.path中新增了PYTHONPATH的内容
(base) chenyingying@cyy-2 import_test % export PYTHONPATH=/home/libtestpath/path1   
(base) chenyingying@cyy-2 import_test % echo $PYTHONPATH
/home/libtestpath/path1
(base) chenyingying@cyy-2 import_test % python
>>> import sys
>>> sys.path
['', '/home/libtestpath/path1', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ...]

Linux export 命令用于设置或显示环境变量。在 shell 中执行程序时,shell 会提供一组环境变量。export 可新增,修改或删除环境变量,供后续执行的程序使用。export 的效力仅限于该次登陆操作。
学习如何在Linux中使用export命令

TODO:如何删除环境变量

2.3 增加.pth后缀的文件

增加.pth 后缀的文件。在 sys.path 已有的某一个目录下添加.pth 后缀的配置文件,该文件的内容就是要添加的搜索路径,Python 在遍历已有目录的过程中,如果遇到.pth 文件,就会将其中的路径添加到 sys.path 中。

例如:在 /usr/lib/python2.6/site-packages 下面新建一个.pth 文件(以pth作为后缀名);将模块的路径写进去,一行一个路径:

 vim pythonmodule.pth
 
/home/liu/shell/config
/home/liu/shell/base

参考博文:
Buildout自动化构建打包工具–闲侃 sys.path 与 buildout 实践
启动方式差异给sys.path带来的差异–Python sys.path详解
增加.pth文件–python之sys.path介绍

3.__ init__.py

__ init__.py文件的一个作用

  1. 是标识一个文件夹是一个python常规包
  2. 定义模糊导入时要导入的内容,当我们使用from package import * 导入语句时候,即在使用模糊导入,这时包的编写者需要再在__init__.py文件中定义 all 的内容来限制模糊导入的内容。这样可以避免将一些只在包内使用的方法或变量暴露给用户。

如果目录中包含了 __ init__.py 时,当用 import导入该目录时,会执行 __ init__.py 里面的代码,init.py简化导入过程:假设我们有一个 models文件夹,文件夹里有一个 User.py文件,这个文件里保存着我们的 User表的类。

# __init__.py中未填写内容时的导入方式
from app.models.User import User   

#  __init__.py中填写如下内容时的导入方式
# from .User import User
from app.models import User

参考博文:-- python包中__init__.py文件的作用
todo :模糊导入__ init__.py __ all__ 填写Python Tips: init.py的作用

4.导入用户模块demo

(base) chenyingying@cyy-2 import_test % tree
.
├── branch
│   ├── m3.py
│   ├── m4.py
│   └── sub_branch
│       ├── __init__.py
│       └── m5.py
├── m1.py
├── m2.py
└── m2.pyc
# __init__.py
from branch.sub_branch.m5 import print_myself

# m5.py
#coding=utf-8
def print_myself():
    print("I am m5")

# m4.py
#coding=utf-8
import m3
def print_myself():
    print("I am m4")

# m3.py
#coding=utf-8
def print_myself():
    print("I am m3")

# m2.py
# coding = utf-8
def print_myself():
    print("I am m2")
# m1.py
#coding=utf-8
"""
@brief : test import module
"""
import sys # 导入标准库
import m2  # 导入同路径下m2.py文件,使用其中定义的函数/类, 模块名为文件名
from branch import m3 # python2这么写是有问题的,python3没问题

if __name__ == "__main__":
    for _ in sys.path:
        print("sys.path", _)
    m2.print_myself()
    m3.print_myself()
--------------------------
(base) chenyingying@cyy-2 import_test % python3 m1.py     
sys.path /pathxxx/import_test
sys.path /home/libtestpath/path1
sys.path /opt/homebrew/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python39.zip
sys.path /opt/homebrew/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9
sys.path /opt/homebrew/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload
sys.path /opt/homebrew/lib/python3.9/site-packages
I am m2
I am m3

--------------------------
(base) chenyingying@cyy-2 import_test % python2 m1.py
Traceback (most recent call last):
  File "m1.py", line 7, in <module>
    from branch import m3         # python2这么写是有问题的,python3没问题
ImportError: No module named branch
# m1.py
#coding=utf-8
import m2  # 导入同路径下m2.py文件,使用其中定义的函数/类, 模块名为文件名
m2.print_myself()

from branch import m3 # python2这么写是有问题的,python3没问题
m3.print_myself()

from branch.sub_branch.m5 import print_myself
print_myself()

import branch.sub_branch 
branch.sub_branch.m5.print_myself()

branch.sub_branch.print_myself() # 需要配合__init__.py 文件使用,可以省略中间m5 文件名

----------------------
(base) chenyingying@cyy-2 import_test % python3 m1.py     
I am m2
I am m3
I am m5
I am m5
I am m5

demo演示来源-实验结论和该博文给出的有出入–Python中import的用法
demo 中__ init__.py 文件演示参考–Python Tips: init.py的作用
Python中import的用法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值