模块与包
# 模块介绍
# 介绍
模块是一系列功能(例如类、函数、配置等)的集合,他一共分为三类:
- 内置模块 - python解释器内置的模块。
- 第三方模块 - 别人写好的模块。
- 自定义模块 - 自己编写的模块,可以使用(python/c/c++)语言编写。
事实上一个python文件就是一个模块,例如文件名func.py,则模块名就叫func。
# 模块的四种形式
使用Python编写的.py文件。
已被编译为共享库或DLL的C或C++扩展。
把一系列模块文件放到一个的文件夹中,文件夹下有一个__init__.py
文件,该文件夹称之为包。
使用C编写并链接到python解释器的内置模块。
# 模块的作用
- 内置与第三方模块
- 拿来就用,无需定义,极大地提高效率
- 自定义模块
- 将各种功能函数代码等放到模块中为大家共享使用,减少代码冗余,程序组织结构更加清晰
# 模块导入
# 模块的导入方法
- 导入模块
import [模块名]
- 一行导入一个模块
- 调用时需使用模块名如:
fooooo.time()
import [模块名],[模块名],[模块名]... ...
- 一行导入多个模块(不推荐,可读性不高)
import [模块名] as [模块别名]
- 导入模块时,给模块添加别名
- 调用时可直接使用别名如:
foo.time()
- 导入模块的功能
- 会在当前名称空间拿到导入的名字,该名字与模块名称空间中名字一样,指向同一个内存地址
- 只是将名字复制一份到当前py文件的名称空间
- 其所指向的内存地址仍然是不变的
from [模块名] import [模块中功能]
- 单独导入模块内的功能
- 调入时,可直接使用功能名如:
time()
from [模块名] import *
- 导入模块中的所有名字
- 但一般不会用,因为不知道导入了什么名字,所以极容易与当前名称空间的名字混淆
__all__
- 可以用
__all__
去控制*的名字代表哪些- 默认是全都有
- 在模块文件内
__all__ = [名字1,名字2...]
- 可以用
- 会在当前名称空间拿到导入的名字,该名字与模块名称空间中名字一样,指向同一个内存地址
- 导入模块方式的优缺点
- 直接导入模块
- 优点
- 不会与当前py文件名称空间的名字起冲突
- 缺点
- 需要加前缀,调用不方便
- 优点
- 只导入模块中功能
- 优点
- 不需要加前缀,调用方便
- 缺点
- 可能与当前py文件名称空间的名字起冲突
- 优点
- 直接导入模块
# 模块导入的规范
约定俗成的导入顺序
- 先导入python内置模块
- 再导入第三方模块
- 最后导入自定义模块
模块名的命名应该采用纯小写+下划线的风格。
# 模块导入的过程
- 首次import导入模块后,会执行模块文件中的代码
- 将模块代码运行过程中产生的名字丢到模块文件的名称空间中
- 在当前文件中产生一个名称 (以导入的模块名为名),该名称指向模块文件的名称空间
- 之后的导入会直接引用首次导入产生的名称空间,不会再执行模块文件中的代码
# 函数内导入模块
- 函数内导入的模块只对函数内有效,但再次调用该也仍然是直接引用首次导入产生的名称空间,不会再执行模块文件中的代码
# 模块导入查找顺序
- 先查找内存
- 如果内存有则直接用内存的
- 只要程序没结束,即便del解绑名称,内存空间也不会被释放,这是python为了防止再次导入而造成性能消耗做的优化
- 只有程序运行结束才会释放
- 可用过
print(sys.modules)
查看已经加载到内存的模块
- 再查找硬盘
- 按照 sys.path 变量下的路径按顺序进行查找
print(sys.path)
- 第一是在当前路径下找
- 按照 sys.path 变量下的路径按顺序进行查找
# 模块的引用
[模块名].[模块中名称]
- 如:
func.times
- 变量func.time()
- 函数
- 如:
- 无论是查看还是修改变量,操作的都是模块的名称空间中的名称
- 也就是调用模块中的函数时,函数如果调用了变量,调用的也是模块名称空间中的变量名称
# 模块名称空间的回收
- 当模块的内存空间没有被人引用时,就会被回收,也就是引用计数为0时
# 测试模块功能
# name
- 每一个Python文件都内置的变量
- 直接运行时值为:
__main__
- 当被作为模块导入时值为:
[模块名]
- 可以在测试模块功能时使用 if 判断,以防在被作为模块导入时,测试代码被执行
# 例如:
if __name__ == '__main__':
print('test')
2
# 循环导入问题
# 原因
- 在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字
- 由于第一个模块尚未加载完毕,所以引用失败、抛出异常
- 同一个模块只会在第一次导入时执行其内部代码,再次导入该模块时,即便是该模块尚未完全加载完毕也不会去重复执行内部代码
# 解决方式
- 不要出现模块之间的互相调用
- 或者如果只是一个函数需要用到,可以只在函数内导入,而非全局中导入
# 模块编写规范
# 模块编写规范顺序
- 模块的描述说明
- 导入引用的模块
- 全局变量 (尽量不要用)
- 类 (要有描述说明)
- 函数 (要有描述说明)
- 测试
# 例如
#!/usr/bin/env python #通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。
"The module is used to..." #模块的文档描述
#导入模块
import sys
#定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能
x=1
#定义类,并写好类的注释
class Foo:
'Class Foo is used to...'
pass
#定义函数,并写好函数的注释
def test():
'Function test is used to…'
pass
#主程序
if __name__ == '__main__':
#在被当做脚本执行时,执行此处的代码
test()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 包(模块)
# 介绍
- 包就是一个包含有
__init__.py
文件的文件夹 - 反之有
__init__.py
文件的文件夹,就是一个包 - 包本质上就是模块的一种形式,用来被当做模块导入
- 多个模块放在包中
- 然后用
__init__.py
文件导入
# 包定义方式
- 模块放在文件夹中
- 同时要有
__init__.py
文件
- 同时要有
- 在
__init__.py
文件中导入其他模块 - 导入模块方式
- 包名 即是 文件夹名
__init__.py
文件中导入模块- 绝对导入
import
直接导入模块- 在包下找到模块并导入
import 包名.模块名
from...import...
导入模块中的功能- 在包下找到模块并导入其中的功能
from 包名.模块名 import 功能名
- 例如:
from func.test import get
- 相对导入
- 包内模块之间的导入,推荐用相对导入
- 用
.
和..
- 如:
/aa/bb
- 在bb的init中调用aa中的模块
from ..get import get
- 绝对导入
- 因为
sys.path
是以运行文件为准,所以想要让它找到,就必须指定什么包名下的什么模块
- 导入子包
from [包] import [子包]
import [包].[子包]
- 调用也要
[包].[子包]
- 同时
[包]
的函数也可以调用- 因为在导入子包时包的
__init__.py
会被执行
- 因为在导入子包时包的
- 调用也要
- 即便只导入子包,找到子包所路过的包的
__init__
都会被执行一遍,因为要检索其中是否有- 如:
pack.a.b.c.d
- 如果没有就会去子文件夹找
- 如:
# 包功能调用方式
__init__.py
文件以import
方法导入包名.模块名.功能名()
- 如
__init__
中import foo.get
- 运行文件中
import foo
foo.mod.get()
__init__.py
文件以import...as...
方法导入__init__.py
导入时添加别名- 调用时可直接:包名.别名.功能名()
__init__.py
文件以from...import...
方法导入包名.功能名()
- 因为在包中,以
from...import...
直接导入了功能名到__init__.py
文件的名称空间 - 如
__init__
中from foo.get import get
- 运行文件中
import foo
foo.get()
# 包的导入过程
- 找到导入的文件夹名的下的
__init__.py
文件 - 运行该文件,将运行过程中产生的名字都丢到产生的名称空间去
- 在当前导入模块的名称空间中,拿到导入的模块的名字,该名字指向模块运行产生的名称空间。
# 数据模型
# 数据模型介绍
Python最好的品质之一是一致性,一致性可以保证我们在面对不同的对象时,使用相同的方法实现某些特定的事情,比如说len()可以用来取不同对象的长度等。
Python风格(Pythonic)的设计思想完全体现在Python的数据模型上,数据模型是对Python框架的描述,它规范了这门语言自身构建模块的接口。
数据模型所描述的API,可以让你使用最地道的语言特性来构建你自己的对象提供了工具,比如:
__getitem__
、__len__
等。
# 特殊方法介绍
特殊方法又称魔术方法、双下方法。特殊方法的调用是隐式的,解释器碰到特殊的句法时,会使用特殊方法去调用对象操作。
比如
dict["key"]
会调用字典对象中的dict.__getitem__(key)
方法。
比如
len()
方法执行时,如果对象是一个是自定义类的对象时,则会调用该对象的__len__
方法,如果对象是一个内置类型会直接返回PyVarObject.ob_size属性。
比如
for i in x:
会调用iter(x)
,而这个函数的背后则是x.__iter__()
。
注意:特殊方法的存在是为了被解释器调用,所以无需直接使用特殊方法,除非有大量的元编程存在。
但
__init__
方法是个特例,一般在子类中会使用__init__
方法来调用超类的构造器。
# 常用特殊方法
__init__
在对象创建时,会被执行。
__getitem__
在使用obj[key]
时会被执行。
__len__
在使用len()函数时会被执行。
__repr__
在obj被作为字符串时会被执行,如:str()、print()
。
和
__str__
差不多,但当__str__
不存在但又要用到str()时,解释器会用__repr__
来代替,所以一般用__repr__
即可。
__repr__
所返回的字符串应该准确、无歧义,并且尽可能表达出这个被打印的对象。
__add__
在使用obj做加法运算时会被执行,如:obj1 + obj2
。
__mul__
在使用obj做乘法运算时会被执行,如:obj1 * obj2
。
__bool__
在使用obj做布尔运算时会被执行。如:bool()、if obj:
等。
# 使用案例
# 创建一套卡牌
import random
import collections
Card = collections.namedtuple("Card", ["rank", "suit"])
class PokeCards:
def __init__(self):
ranks = [str(n) for n in range(2, 11)] + list("JQKA")
suits = "红桃 黑桃 梅花 方块".split()
self._cards = [Card(rank, suit) for suit in suits for rank in ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, index):
return self._cards[index]
cards = PokeCards()
print(len(cards))
print(Card("2","红桃") in cards)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22