第二模块第20章 模块

1. 什么是模块?

模块就是一系列功能的集合体, 分为三大类:

  1. 内置模块 (python解释器提供的, 用C语言编写的模块)

  2. 自定义模块 (可以是python, C 或 C++写的)

    一个python文件本身就是一个模块, 文件名m.py, 模块名m

  3. 第三方模块 

模块其实分为四个通用类别,分别是:(关注1和3)

  1、使用纯Python代码编写的py文件

  2、包含一系列模块的包 (把一系列模块组织到一起的文件夹, 文件夹下有一个__init__.py文件, 该文件夹称为包)

  3、使用C编写并链接到Python解释器中的内置模块

  4、使用C或C++编译的扩展模块

2. 为何要有模块?

内置与第三方模块拿来就可以用, 无需定义, 这可以极大地提高开发效率.

自定义模块:

  可以将程序的各部分功能提取出来放到一个模块中为大家共享使用, 好处是减少代码冗余, 程序组织结构更加清晰.

3. 如何用模块?

1. import 模块名

foo.py代码如下:

print(‘模块foo>>>‘)
x = 1
def get():
    print(x)
def change():
    global x
    x = 0
    print(x)

run.py代码如下:

import foox = 111y = 222
foo.get()
foo.change()
‘‘‘
结果:
模块foo>>>
1
0
‘‘‘

原理图如下:

第二模块第20章 模块

x = 000
y = 111
z = 222
import foo
‘‘‘
1. 首次导入模块会发生的三件事:
    1. 执行foo.py
    2. 产生foo.py的名称空间, 将foo.py运行过程中产生的名字丢到foo的名称空间中
    3. 在当前文件中产生产生一个名字foo, 该名字指向2中产生的名称空间.
之后的导入, 都是引用首次导入产生的名称空间, 不会重复执行代码.
‘‘‘

# 2. 引用
print(foo.x)  # 结果: 1
print(foo.login)  # 结果: <function login at 0x0000018A359C1488>
‘‘‘
强调1: 模块名.名字, 是指明道姓地问某一个模块要名字对应的值, 不会与当前名称空间冲突.
强调2: 无论查看还是修改, 操作的都是模块本身, 以定义模块为准, 与调用位置无关.
‘‘‘

# 3. 补充
# 可以一行一行地写, 也可以用逗号隔开, 写在同一行, 但是不推荐写在同一行, 示例:
# import time
# import foo
# import m

# import time, foo, m

# 4. 导入模块的规范
    # 1. python内置模块
    # 2. 第三方模块
    # 3. 程序员自定义模块

# 5. import...as...
# 给导入的模块起个别名, 示例:
# import foo as f
# 当模块名比较长, 用起来比较麻烦时可以采用这种方法

# 6. 模块是第一类对象
# 可以当作返回值, 可以当作容器类数据的元素...

# 7. 自定义模块的命名应该采用纯小写加下划线的风格(python3), python2中有些采用驼峰体的格式

# 8. 可以在函数内导入模块, 如果是在全局导入, 则导入的模块可以在全局使用, 如果在函数内导入, 则导入的模块只能在局部使用

一个py文件的两种用途:

1. 当作py文件运行

2. 当作模块被导入

每个py文件都内置有__name__, 当该文件被运行时, __name__的值为__main__, 当该文件被当作模块导入时, __name__的值为__模块名__.

注意: 输 if __name__ == ‘__main__‘: 的快捷方式为: 输入main, 然后按回车键即可.

import 导入模块在使用时必须加前缀:

优点: 肯定不会与当前名称空间中的名字冲突.

缺点: 加前缀显得麻烦.

2. from...import...

from...import...导入发生的三件事:
  1. 产生一个模块的名称空间
  2. 运行foo.py将运行过程中产生的名字都丢到模块的名称空间中去
  3. 在当前名称空间拿到一个名字, 该名字对应模块名称空间中的某一个内存地址
优点: 代码更精简
缺点: 容易与当前名称空间混淆


补充:
1. 可以在同一行导入多个名字, 示例:
from foo import x, get, change
不推荐使用以上方法
2. *导入模块中的所有名字, 大多数情况下都不要使用这种情况
from foo import *
3. 被导入的模块中默认内置有__all__, __all__中存放的是字符串格式的名字的列表,
__all__ = [‘x‘, ‘get‘, ‘change‘]
可以用__all__控制*代表的名字有哪些
4. from foo import get as g
以上方法是针对get起别名

foo.py的代码如下:

print(‘模块foo>>>‘)
x = 1
def get():
    print(x)
def change():
    global x
    x = 0
    print(x)
__all__ = [‘x‘, ‘get‘]  # 可以用它控制*代表的名字有哪些

run.py的代码:

from foo import x
from foo import get as g  # 将get重命名为g
from foo import change
print(x)  # 结果: 1
g()  # 结果: 1
change()  # 结果: 0
print(x)  # 结果: 1

# from foo import *
# print(change)  # 结果: NameError: name ‘change‘ is not defined

原理图如下:

第二模块第20章 模块

4. 循环导入

循环导入, 如从m1导入m2, 从m2导入m1, 会导致报错.

解决方案一: 将名字前提

解决方案二: 如果导入的模块是给某个功能用的, 那么就不要在全局导入.

不过, 不推荐使用循环导入的方式.

5. 模块查找优先级

无论是import还是from...import...在导入模块时都涉及到查找问题

查找优先级:

1. 内存(内置模块)

2. 硬盘: 按照sys.path中存放的文件的顺序依次查找要导入的模块

可以通过sys.modules查看已经加载到内存中的模块.

导入的模块, 通过del不能从内存中删除; 在函数中导入的模块, 函数运行完毕后, 也不会从内存中回收.

如果导入模块不在sys.path中, 即导入模块与执行文件不在同一路径下, 则会报错, 为了避免这一问题, 可以采用以下方法将导入模块的路径追加到sys.path中.

import sys
sys.path.append(r‘F:\python全栈开发\python\day21\aa‘)  # 注意: 这里追加的是aa文件夹的路径, 这种方法是临时加的.
import mm
mm.login()

6. 编写规范的模块

编写一个模块时最好按照统一的规范去编写,如下:

#!/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() #在被当做脚本执行时,执行此处的代码

7. 函数的类型提示

# 函数类型提示
def register(name:str, hobbies:‘必须传入元祖‘, age:int = 18, )->‘int‘:
    print(name)
    print(age)
    print(hobbies)
    return 111
register(‘egon‘, (‘篮球‘, ‘足球‘))
# 函数定义阶段, 对参数进行注释, 并对返回结果进行注释, 添加注释并不影响函数的运行.
print(register.__annotations__)  
#  结果: {‘name‘: <class ‘str‘>, ‘hobbies‘: ‘必须传入元祖‘, ‘age‘: <class ‘int‘>, ‘return‘: ‘int‘}