模块

1.模块介绍

1.1 模块的概念

模块是什么 ?

模块就是一系列功能(函数)的集合体

模块的分类

  • 内置模块 c语言写的模块
  • 第三方模块 别人写好的模块
  • 自定义模块 自己自定义的

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

ps:模块分为4中形式
    1.使用python编写的.py文件
    2.已被编译为共享库或DLL的C或C++扩展
    3.把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包
    4.使用C编写并链接到python解释器的内置模块

1.2 为什么要用模块

  1. 内置与第三方的模块拿来就用,无需定义,这种拿来主义,可以极大的提升自己的开发效率
  2. 可以将程序的各部分功能提取出来放到一模块中为大家共享使用
  3. 减少了代码冗余,程序组织结构更加清晰

2. 模块的两种导入方式

2.1 import

[创建模块]

# m.py
print('======m=====')
x = 100
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x

[导入模块]

# demo.py
import m     # 导入m模块

x = 1
y = 2

# ======m=====  
# 打印l ======m===== ,说明导入模块,会运行模块里面的代码

[思考] 首次模块会发生什么事情

1.产生m.py的名称空间
2.执行m.py代码,将m.py运行过程中产生的名字都丢到m的名称空间
3.在当前文件中产生一个名字m,指向1中产生的名称空间
image-20201211193721713

[之后导入]

之后的导入,都是直接引用第一次导入产生的m.py名称空间,不会重复执行代码

[深入理解]

# demo.py
import m

print(m.x)            # 100
print(m.get)          # <function get at 0x000001FD80567620>
print(m.change)       # <function change at 0x000001FD81616510>

[强调]

  1. 模块名.名字,是指名道姓的问某一个模块要名字对应的值,不会与当前名称空间的名字发生冲突
  2. 无论是查看还是修改操作的都是以定义阶段为准(原模块本身),与调用位置无关
# demo.py
import m

x = 333
m.get()     # get ======== 100

m.change()  # 改变的是m名称空间里面的x值
print(x)    # 333

print(m.x)  # 101

[补充]

# 1.可以以逗号为分隔符在一行导入多个模块
# 建议如下所示导入多个模块,层次清晰
import time
import random
import m

# 不建议在一行同时导入多个模块
import time, random, m

# 2.导入模块的规范

# 1.python内置模块
# 2.第三方模块
# 3.程序员自定义模块

import time
import sys
import 第三方模块1
import 第三方模块2
import 自定义模块1
import 自定义模块1

# 3.import ... as ...
# 为导入的模块起一个别名,以后可以通过别名.名字,取到模块里面的名字
# 适用于比较长的第三方模块

import jhcsdfveubvf as f

# 4.模块是第一类对象,和变量名,函数名一样,可以被赋值,可以当参数,返回值,容器的元素

# 5.自定义模块的命名应该采用纯小写+下划线风格,py2中还是驼峰体

# 6.可以在函数中导入模块

2.2 from ... import ....

# foo.py
print('======foo=====')
x = 0
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x
# run.py

from foo import X          # x=模块foo中值0的内存地址
from foo import get
from foo import change

print(x)                   # 0
print(get)                 # <function get at 0x0000029FBD8E7620>
print(change)              # <function change at 0x0000029FBE996510>

# 注意:x,get,change这三个名字是在run的名称空间中,但是指向的都是foo里面值的内存地址,并不是名字
# 只是他们的名字相同又指向相同的内存地址,但是不在一个名称空间,所以不会相互影响
image-20201212124645778

[导入模块发生的事]

# 1、产一个模块的名称空间
# 2、运行foo.py将运行过程中产生的名字都丢到模块的名称空间去
# 3、在当前名称空间拿到一个名字,该名字指向模块名称空间中的某一个内存地址

[强调]

# run.py

from foo import X          # x=模块foo中值0的内存地址
from foo import get
from foo import change

print(x)                   # 0
print(get)                 # <function get at 0x0000029FBD8E7620>
print(change)              # <function change at 0x0000029FBE996510>

x = 3333       # 改变全局名称空间中x的指向,现在x指向值3333的内存地址
print(x)       # 3333

get()          # 仍然打印x=0,因为get中的x是引用foo名称空间中x指向的值,foo中x的指向并没有变
change()       # 改变foo中x的内存地址的指向
get()          # 打印1

print(x)       # 虽然foo中x的指向发送改变了,但是run中的这个x并不会发生变化,还是原来的内存地址0

from foo import x   # 这个时候x就指向,foo里面x指向的新地址了
print(x)            # 打印 1

[补充]

#一行导入多个名字(不推荐)
from foo import X, get,change

# *:导入模块中的所有名字,当用到一个模块中超级多的名字时,可以用*,但是最好也要规避掉重名的问题
from foo import *|
print(x)

# 起别名
from foo import get as g    # 针对get起了一个别名g

[了解]

# 了解 : __all__ = ["名字1","名字2",.....],列表的元素全是字符串格式的名字
# 导入*,实际上就是把列表中的名字全导入进入了,可以通过__all__控制*代表的名字有哪些

2.3 两种方式的比较

impot导入模块在使用时必须加前缀"模块."
优点:肯定不会与当前名称空间中的名字冲突
缺点:加前缀显得麻烦

from.. . impat.. .导入模块在使用时不用加前缀
优点:代码更精简
缺点:容易与当前名称空间混淆

3. 循环导入

极力不推荐在程序中设计有模块的循环导入,但是如果真的遇到,记住两种解决方式

m1.py

print("正在导入m1")

from m2 import y

x = 1

m2.py

print("正在导入m2")

from m1 import x

y = 2

run.py

import m1


# 运行代码,首先会加载m1的名称空间,然后运行里面的代码,当运行到第二句的时候,此时m1的名称空间中还没有x这个名字,由于是首次导入会加载m2的名称空间,运行m2的代码,所以控制台会打印正在导入m1,正在导入m2,当运行当m2的第二句代码时,由于m1不是首次导入,所以不会再加载名称空间,也不会执行代码,但是m2问m1要x这个名字,由于m1中的x这个名字还没有加载到名称空间,所以程序报错

[解决方法]

# 方式1:
# 将m1,和m2中的from m2 import y,from m1 import x,放在文件的最后一行,确保导入的时候,名字已经完全加载到名称空间了

# 方式2:
# 将模块导入的代码,放在函数体中,这样保证在运行代码的时候,函数未调用函数体里面的代码不执行

4. 搜索模块路径的优先级

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

[优先级]

  1. 内存 (内置模块,默认会自动加载到内存中)
  2. 硬盘 : 按照sys.path(环境变量)中存放的文件的顺序依次查找要导入的模块
import sys
print(sys.path)

['E:\\project\\python\\s29code\\day21', 'E:\\project\\python\\s29code', 'E:\\developTools\\pycharm\\PyCharm 2020.1\\plugins\\python\\helpers\\pycharm_display', 'E:\\developTools\\Anaconda3\\python36.zip', 'E:\\developTools\\Anaconda3\\DLLs', 'E:\\developTools\\Anaconda3\\lib', 'E:\\developTools\\Anaconda3', 'E:\\developTools\\Anaconda3\\lib\\site-packages', 'E:\\developTools\\Anaconda3\\lib\\site-packages\\win32', 'E:\\developTools\\Anaconda3\\lib\\site-packages\\win32\\lib', 'E:\\developTools\\Anaconda3\\lib\\site-packages\\Pythonwin', 'E:\\developTools\\pycharm\\PyCharm 2020.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend']

# 其中第一个文件夹是当前执行文件所在的文件夹  E:\\project\\python\\s29code\\day21
# 其中第二个文件夹是当前项目的文件夹  E:\\project\\python\\s29code
# 这个值是pycharm自动添加的,所以当他不存在,值里面的压缩包就当文件夹看待

[补充]

通过sys.modules查看已将加载到内存的模块,内容格式 {'模块名':'内存地址'}

print('foo' in sys.modules)   # 判断foo模块是否在当前内存中
print('m1' in sys.modules)    # 判断foo模块是否在当前内存中

{'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module 'importlib._bootstrap' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread': <module '_thread' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_frozen_importlib_external': <module 'importlib._bootstrap_external' (frozen)>, '_io': <module 'io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'nt': <module 'nt' (built-in)>,...}

[python的优化机制]

导入模块会发生模块名和对应的内存地址的捆绑关系,但是你通过del 模块名
是无法从内存中把那块空间回收的,因为python导入一个模块是较耗费内存的
如果你中途从内存中删除了,再导入就会再开辟内存空间,极大的消耗内存
同样你在函数内部中导入模块,函数调用结束后,导入的模块在内存中也不会回收
只有当当前运行文件结束,无其他文件对模块中的名字有引用,模块才会从内存中消失

5. 模块规范写法

我们在编写py文件时,需要时刻提醒自己,该文件既是给自己用的,也有可能会被其他人使用,

因而代码的可读性与易维护性显得十分重要,为此我们在编写一个模块时最好按照统一的规范去编写,如下

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

6. py文件的用途

6.1 当做模块导入

# foo.py
print('======m=====')
x = 0
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x
# run.py
import foo      # 首次导入模块,执行代码
#print(foo)

print(foo.x)    # 0
foo.change()    # 把x指向的内存地址改变了,指向了1
print(foo.x)    # 1

x = 1
y = 2

print('11111')
print('22222')

image-20201211202054880
# 名称空间的回收顺序
首次导入foo模块,foo代码会运行,但是代码运行结束后它的名称空间没有被回收,因为里面的名字被run.py引用了,紧接着run.py中的代码运行完毕后,它的名称空间里面的名字没有被其他引用,运行完后,run.py的名称空间就被回收了,foo的名称空间也被回收了

# 总结: 模块没有被其他文件引用就被回收了

5.2 当做程序运行

如果一个模块在开发测试的时候,肯定会要运行,但是如果被别人导入的时候,不想执行里面的函数怎么办呢?

# foo.py
print('======m=====')
x = 100
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x

if __name__ == '__main__':
    # 当作程序运行的时候做的事情
    get()
    change()
else:
    # 被当做模块导入的时候做的事情
    pass
# 每一个py文件内部都有一个__name__变量,
# 当文件当做程序运行的时候,__name__ == __main__
# 当文件当做模块导入时,__name__ == 模块名,即去掉后缀的文件名
posted @ 2020-12-12 16:56  Mn猿  阅读(71)  评论(0编辑  收藏  举报