Python模块基础
Python模块基础完整进阶笔记
一、模块核心概念
1.1 什么是模块
模块就是一系列相关功能的集合体,或者说模块就是存放一系列相关功能的地方。
模块有四种通用类别:
1、一个python文件就是一个模块,如果文件名为test.py,模块名为test
2、把一系列py文件组织到一个文件夹下,称之为包(注:文件夹下需要有一个__init__.py)
3、已被编译为共享库或者DLL的C或者C++扩展
4、用C语言编写并连接到python解释器的内置模块
模块有三种来源:
1、python为我们提供好的:内置模块,标准库
3、自定义的模块
1.2 为何要用模块
分为两类场景:
I:针对内置、标准库、第三方的模块,我们只需要拿来就用,无需自定义,这种拿来主义,可以极大地提升程序的开发效率
II:自定义的模块
程序模块化之后,组织结构会更加清晰、更方便维护
可以将程序各部分共用的功能提取到一个模块中来为大家共享使用,好处就是减少了代码冗余问题
二、import 导入方式详解
2.1 首次导入模块的三件事
首次导入模块会执行三个核心步骤,后续重复导入不会重复执行代码:
1、首次导入模块会触发模块对应文件的运行
2、会产生一个该模块的名称空间,用来将模块文件运行过程产生的名字都丢到该名称空间中
3、会在当前名称空间中产生一个与模块同名的名字,该名字指向模块的名称空间
示例:被导入的test.py模块内容
def f1():
pass
def f2():
pass
def f3():
pass
print("02 import导入模块的用法.py....")
x = 111
y = 333
z = 444
导入语句示例:
# 首次导入,执行上述三件事
import test
# 之后的导入,都是直接引用首次导入产生的模块名称空间,不会重复执行代码
# import test
# import test
# import test
# import test
2.2 模块名字的引用规则
使用「模块名.名字」的方式访问模块中的内容,有两个核心强调点:
强调1:模块名.名字,是指名道姓地向某一个名称空间/模块要名字,所以肯定不会与当前名称空间中的名字冲突。
# import spam
# money = 123
# print(spam.money)
# print(money)
# def read1():
# print('02 import导入模块的用法.py---read1')
# print(spam.read1)
# spam.read1()
# read1()
# print(spam.read2)
# print(spam.change)
强调2:名称空间的关系是以定义阶段为准的,与调用位置无关,即无论在哪调用,都要回到当初定义的位置去查作用域关系。
# 例1
# money = 123
# spam.read1()
# 例2
# def read1():
# print('02 import导入模块的用法.py...read1')
# spam.read2()
# 例3
# money = 666
# spam.change()
# print(money)
# print(spam.money)
2.3 多模块导入规范
可以以逗号为分隔符在一行导入多个模块,但不建议这么做,推荐分行导入。
# 建议的导入方式
# import time
# import os
# import sys
# 不建议在一行同时导入多个模块
# import time,os,sys
导入位置规范:通常导入模块的操作都放在文件最开头、顶格写,摆放顺序建议:
I:python自带的模块
II:第三方模块
III:自定义模块
# import time
# import sys
#
# import 第三方1
# import 第三方2
#
# import 自定义模块1
# import 自定义模块2
# import 自定义模块3
2.4 给模块起别名
使用 import ... as xxx 可以给导入的模块起别名,简化调用,也用于统一不同模块的调用接口。
# import spam as sm
# print(sm.money)
应用案例:根据不同数据库类型统一调用接口
# db_type = input(">>>: ")
# if db_type == "mysql":
# import mysql as db
# elif db_type == "oracle":
# import oracle as db
#
# db.sqlparse()
2.5 模块的其他特性
1、模块也是第一类对象,可以赋值、传递、作为参数使用。
# import spam # spam = 模块名称空间的地址
2、Python3中自定义模块的命名建议采用纯小写字母+下划线的风格。
3、可以在函数内导入模块,模块的名字会生成在函数的局部名称空间中,全局无法访问。
# def foo():
# import time
# print("foo====>",time.time())
#
# foo()
# time.time() # 全局无法访问,会报错
三、from ... import ... 导入方式详解
3.1 导入的三件事
from ... import ... 导入也会发生三件事:
1、触发模块对应文件的运行
2、会产生一个模块的名称空间,用于将模块文件运行过程中产生的名字都扔进去
3、在当前名称空间中拿到一个或多个名字,这些名字直接指向模块名称空间中对应的名字
# from spam import money
# from spam import money # 后续重复的导入不会重复执行文件,会直接引用之前产生的名称空间
# from spam import money
3.2 优缺点
优点:使用时不需要加前缀,代码更简洁
缺点:容易与当前名称空间中的名字冲突
# money = 666
# from spam import read1
# from spam import change
# from spam import money
#
# print(money)
# read1()
# change()
# print(money)
# read1()
3.3 导入多个名字与通配符导入
可以以逗号为分隔符在一行导入多个模块中的名字:
# from spam import money,read1,read2,change
# print(money)
使用 * 代表导入模块中的所有名字:
# from spam import *
# print(money)
# print(read1)
# print(read2)
# print(change)
补充:all 可以控制 * 导出的名字范围,在模块文件中定义__all__列表,列表中的名字才会被*导入。
示例:spam.py中定义
__all__ = ['money', 'read1']
money = 100
def read1():
pass
def read2():
pass
def change():
pass
此时 from spam import * 只能导入money和read1,read2和change不会被导入。
3.4 给导入的名字起别名
可以给导入的单个或多个名字起别名:
# from spam import money as m,read1 as r,read2,change
#
# print(m)
# print(r)
# print(read2)
# print(change)
3.5 两种导入方式的适用场景
1、如果程序中要使用模块中的某一个名字很多次,import方式每次调用都加前缀会让代码显得啰嗦,此时用from ... import ...比较合适。
2、使用from ... import ... 必须注意避免名字冲突,导入的名字不要和当前作用域的名字重名。
from time import sleep
sleep(1)
sleep(1)
sleep(1)
sleep(1)
sleep(1)
sleep(1)
sleep(1)
sleep(1)
sleep(1)
四、模块的两种用途与 name 变量
一个python文件有两种用途:
1、可以被当成模块导入使用,此时内置变量__name__等于"模块名字"
2、可以被当成执行程序直接运行,此时内置变量__name__等于"main"
# 作为模块导入时的示例
# import test
# test.f1()
# import m1
基于这个特性,通常会在模块文件中编写如下判断,用于区分文件是被导入还是直接运行:
if __name__ == '__main__':
# 直接运行文件时执行的代码,作为模块导入时不执行
f1()
print("文件直接运行")
五、循环导入问题
5.1 循环导入的根本原因
模块的循环/嵌套导入抛出异常的根本原因是:在python中模块被导入一次后,就不会重复导入了,只会在第一次导入时执行模块内的代码,后续的导入都是直接引用之前的结果。如果两个模块互相导入,而导入时对方的名字还没定义完成,就会报错。
补充说明:一个python文件有两种用途,一个是可以被当成执行文件执行,另外一个是可以被当成模块导入。python文件被当成执行文件首次执行时,并不代表它被导入了,一定是针对导入操作才有首次跟二次之分,所以一个文件是执行文件还是模块,背后的名称空间逻辑是不一样的。
5.2 循环导入的解决方法
1、真正的解决方案就是不要让程序有循环导入的设计,应该从架构上避免。
2、临时解决方案:
把引用的名字前提,保证导入之前已经有了,但这种方式解决不了根本问题。
把涉及到循环导入的语句都放到其使用的地方,比如某个函数内,延迟导入时机。
# import m1
# m1.f1()
六、模块查找路径与优先级
但凡导入模块,无论是import的导入方式,还是from...import的导入方式,都涉及到模块查找的问题。
6.1 查找路径与优先级
查找优先级从高到低依次为:
1、内存:已经加载到内存中的模块优先查找
2、内置模块:Python解释器内置的模块
3、sys.path:模块搜索路径列表
注意事项:
(1)执行程序所在的文件夹/目录默认就在sys.path的第一个位置
(2)pycharm为你添加到sys.path的路径应该当其不存在,生产环境不会有这些额外路径
(3)压缩包也相当于文件夹,可以作为模块路径
# import sys
# print(sys.path)
6.2 查找示例
例1:验证内存中已加载的模块不会重复加载
# import spam
# print("首次。。。:",spam.money)
#
# import time
# time.sleep(20)
#
# import spam
# print("二次。。。:",spam.money)
例2:sys.modules与模块名字删除
# import spam # spam -》模块的名称空间的地址
# import sys
#
# print("spam" in sys.modules)
# del spam # 只是删除当前名称空间中的名字,不是删除模块文件
# print("spam" in sys.modules)
#
# def func():
# import spam
# print(spam.money)
#
# func()
例3:手动添加模块搜索路径
# import sys
# sys.path.append(r"D:\project\day11")
# import spam
七、补充知识点
7.1 sys.modules
sys.modules可以用来查看已经加载到内存中的所有模块,是一个字典,key是模块名,value是模块对象。
# import sys
# print(sys.modules)
# print("time" in sys.modules)
# import spam
# print("spam" in sys.modules)
7.2 引用计数补充
可以通过sys.getrefcount查看对象的引用计数:
import sys
x = [1, 2, 3, 4]
print(sys.getrefcount(x))

浙公网安备 33010602011771号