python基础之模块与包

一、模块介绍

1.什么是模块?

Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句。
模块让你能够有逻辑地组织并且复用 Python 代码段。把相关的代码分配到一个模块里能让你的代码更好用,更易懂。
模块能定义函数,类和变量,模块里也能包含可执行的代码。

总结:模块就是一组能实现功能的代码段的集合体,程序可以导入模块来复用模块里的功能。

2.为什么要使用模块?

1. 将程序按功能分成单个文件,方便组织管理。

随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。
这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能有逻辑地组织并且复用。

2. 拿来主义,直接调用人家写好的功能

调用别人写好的模块(内置的,或第三方):典型的拿来主义,极大的提高开发效率。

3.模块来源

  1. 内置模块(python解释器自带)
  2. 第三方模块(三方开发者共享的)
  3. 自定义模块(自己开发的)

4.python模块通用类别

  1. 使用python编写的py文件(py文件也可以称之为模块)
  2. 已被编译为共享库或DLL的C或C++扩展(了解)
  3. 把一系列模块组织到一起的文件夹(文件夹下有一个__init__.py文件,该文件夹称之为包)包:一系列py文件的结合体.
  4. 使用C编写并连接到python解释器的内置模块

在我们使用模块的时候一定要区分哪个是执行文件,哪个是被导入文件。

'''
module1.py  被导入文件
version:01
author:jasn
Date:2020-02-08
'''
# 我们先自定义一个模块 名为module1.py;
print('I am a module1')
name = "Imported module"
def func1():
    print("我是被导入文件,",name)
    
def func2():
    print("module1")
    func1()

def change():
    global name
    name = 'executive module'

func1()

#然后我们使用同目录下module2.py文件进行导入这个模块;
import module1

#module2.py 文件输出结果
#>> I am a module1
#>> 我是被导入文件, Imported module

二、使用模块

一、使用模块之 import的使用

'''
module2.py  执行文件
version:01
author:jasn
Date:2020-02-08
'''
import module1 #调用module1.py文件
import module1
import module1

#module2.py 文件输出结果
#>> I am a module1
#>> 我是被导入文件, Imported module

1、代码中多次导入 module1模块,发现打印结果只有一次

是因为每次导入模块时,解释器都回去检查一下这个模块有没有之前被导过,多次导入不会再执行模块文件。

2、模块在第一次被导入时三件事,重复导入会直接引用内存中已经加载好的结果。

  1. 为被导入文件(module1.py)在内存中创建新的命名空间;模块中定义的函数和方法若是使用到了global时访问的就是这个名称空间。
  2. 在新创建的命名空间中执行模块中包含的代码,见初始导入import module1
'''提示:导入模块时到底执行了什么?
    In fact function definitions are also ‘statements’ that are 
    ‘executed’; the execution of a module-level function definition 
    enters the function name in the module’s global symbol table.
    事实上函数定义也是“被执行”的语句,模块级别函数定义的执行将函数名放
    入模块全局名称空间表,用globals()可以查看
    '''
  1. 创建模块名来引用该命名空间:使用模块的名称来引用这个模块的内存地址,如果使用别名,则用别名来引用这个模块的内存地址。

3、被导入模块有独立的名称空间

#module2.py  执行文件
import module1
name='wuhan'
print(module1.name)
'''
执行结果:
Imported module
'''
# 说明 :name  和module1.name 不冲突

4、为模块起别名

#module2.py  执行文件
import module1 as mod1
name='wuhan'
print(mod1.name)
'''
执行结果:
Imported module
'''
# 说明 :如果用户使用as来指定变量接受这个内存地址的话,那么就将内存地址赋值给这个变量;
# 且下文在调用时只能使用这个变量进行调用不能再使用模块名进行调用了,然后执行这个模块中的代码。

5、在一行中导入多个模块

import os,sys,re
二、使用模块之 from … import…

1、from … import…的使用

from module1 import func1 ,func2
print(func1)  # <function func1 at 0x00000193A3CBA670>
print(func2())
'''
结果为:
<function func1 at 0x000001CAEC6BA670>
module1
我是被导入文件, Imported module
None
'''

2、from … import…和import的区别

唯一的区别就是:
使用from…import…则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:(模块名).
from…import…的优缺点:
好处:使用起来方便了
坏处:容易与当前执行文件中的名字冲突

2、from … import…也支持 as

from module1 import func1 as f1

2、from…import *

'''
from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置
#大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,
很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。
'''
# 可以使用__all__来控制*(用来发布新版本),在(模块).py中新增一行
__all__=['name','func1'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字

三、模块的使用顺序

模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块

#模块的查找顺序
'''
1、在第一次导入某个模块时(比如module1),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
    ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看
2、如果没有,解释器则会查找同名的内建模块
3、如果还没有找到就从sys.path给出的目录列表中依次寻找module1.py文件。
'''
#sys.path的初始化的值来自于:
The directory containing the input script (or the current directory when no file is specified).
PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
The installation-dependent default.

#需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。 

#在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。
>>> import sys
>>> sys.path.append('/a/b/c/d')
>>> sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索
注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理,

#首先制作归档文件:zip module.zip foo.py bar.py 
import sys
sys.path.append('module.zip')
import foo,bar

#也可以使用zip中目录结构的具体位置
sys.path.append('module.zip/lib/python')

#windows下的路径不加r开头,会语法错误
sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a')
 
#至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。

#需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。

***官方解释:***
官网链接:https://docs.python.org/3/tutorial/modules.html#the-module-search-path
搜索路径:
当一个命名为spam的模块被导入时
解释器首先会从内建模块中寻找该名字
找不到,则去sys.path中找该名字

sys.path从以下位置初始化
执行文件所在的当前目录
PTYHONPATH(包含一系列目录名,与shell变量PATH语法一样)
依赖安装时默认指定的

注意:在支持软连接的文件系统中,执行脚本所在的目录是在软连接之后被计算的,换句话说,包含软连接的目录不会被添加到模块的搜索路径中

在初始化后,我们也可以在python程序中修改sys.path,执行文件所在的路径默认是sys.path的第一个目录,在所有标准库路径的前面。这意味着,当前目录是优先于标准库目录的,需要强调的是:我们自定义的模块名不要跟python标准库的模块名重复,除非你是故意的。

二、包介绍

1.什么是包?

1.官网解释:
Packages are a way of structuring Python’s module namespace by using “dotted module names”
包是一种通过使用‘.模块名’来组织python模块名称空间的方式。

2.自己总结:
包就是一个包含有__init__.py文件的文件夹,创建包的目的就是为了用文件夹将文件/模块组织起来,是一种组织管理代码的方式。

2.包的组织结构

  • |- -包名(package)
    • |- -|- - init.py (包的标识文件)
    • |- -|-- 模块.py
    • |- -|–模块1.py
    • |- -|–子包名
      • |- -|- - init.py (子包的标识文件)
      • |- -|- - 子模块1.py
      • |- -|- - 子模块2.py
      • |- -|- - 子模块3.py

3.包的导入

'''
目录:
code01
    |--package01   #包名
        |-- __init__.py
        |--string.py  #模块1 ,输出字符串123456
        |--package02  #子包名
        |--|--__init__.py
        |--|--MD5.py  #子包的模块1,作用将字符串MD5加密
    |--包的使用.py  # 执行文件

'''

# package01下 __init__.py 内容
print('我是package01目录下的__init__.py文件')


# package02下 MD5.py文件
import hashlib
import time

def jm_md5(key):
    '''
    :param key: 加密的值
    :return: key+时间戳生成一个唯一的MD5值并返回
    '''
    value = str(time.time())
    hsobj = hashlib.md5(key.encode("utf-8"))
    hsobj.update(value.encode("utf-8")) # 添加时间戳,生成不重复的MD5值
    return hsobj.hexdigest()

方法一:直接导入包,此时只导入了__init__模块

import package01 #直接导入一个包
import package01 as pack01 # 别名 用别名代替,功能与上句一样这里是引用

# package01下 __init__.py 内容
print('我是package01目录下的__init__.py文件')

# 包的使用.py  # 执行文件
import package01

'''
结果:

>>>: 我是package01目录下的__init__.py文件
'''

方法二:导入包中的某个具体模块,此方法会默认导入包目录下的__init__模块

import package01.string

# 包的使用.py  # 执行文件
import package01.string
'''
结果:
>>>: 我是package01目录下的__init__.py文件  
>>>: 123456
'''

方法三:from…import,从包中导入某个模块,不包含__init__模块中的内容
#在包中导入指定模块,引用时可以不用写包的名字

# package01 目录下string文件
def printer():
    print('123456')

# 包的使用.py  # 执行文件
from package import string   #在包中导入指定模块,引用时可以不不用写包的名字
string.printer()
'''
结果:
>>>: 123456
'''

方法四:from package import *
导入__init__模块所有内容

from package01 import *  #从包中导入__init__模块中所有的函数和类

方法五、调用子包里面的子模块
导入模块时除了使用模块名进行导入,还可以使用目录名进行导入。例如,在sys.path路径下,有一个package01/package02/MD5 .py模块,那么在任意位置处都可以使用下面这种方式导入这个模块。

from package01.package02.MD5 import XXXX #XXXX为模块中的函数体或者变量名

# package02(子包)里面MD5 文件

import hashlib
import time
def jm_md5(key):
    '''
    :param key: 加密的值
    :return: key+时间戳生成一个唯一的MD5值并返回
    '''
    value = str(time.time())
    hsobj = hashlib.md5(key.encode("utf-8"))
    hsobj.update(value.encode("utf-8")) # 添加时间戳进行混淆,生成不重复的MD5值
    return hsobj.hexdigest()


# 包的使用.py  # 执行文件
from package01.package02.MD5 import jm_md5   #导入模块时除了使用模块名进行导入,还可以使用目录名进行导入。

g = jm_md5('123456')  #将字符串进行混淆加密
print(g)

'''
结果:
>>>:a50f085fe7107c99d946623360928d89
'''

4.包的绝对导入和相对导入

我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入: 以glance作为起始
相对导入: 用.或者…的方式最为起始(只能在一个包中使用,不能用于不同目录内)

绝对导入: 以执行文件的sys.path为起始点开始导入,称之为绝对导入

  1. 优点: 执行文件与被导入的模块中都可以使用
  2. 缺点: 所有导入都是以sys.path为起始点,导入麻烦
from package01.string import printer     #执行文件中只能用绝对导入
g=printer()
print(g)

相对导入: 参照当前所在文件的文件夹为起始开始查找,称之为相对导入

  1. 符号: . 代表当前所在文件的文件加,…代表上一级文件夹,…代表上一级的上一级文件夹
  2. 优点: 导入更加简单
  3. 缺点: 只能在导入包中的模块时才能使用,不能在执行文件中用

***注意:***
1. 相对导入只能用于包内部模块之间的相互导入,导入者与被导入者都必须存在于一个包内
2. attempted relative import beyond top-level package # 试图在顶级包之外使用相对导入是错误的,
言外之意,必须在顶级包内使用相对导入,每增加一个.代表跳到上一级文件夹,而上一级不应该超出顶级包

发布了46 篇原创文章 · 获赞 37 · 访问量 4529

猜你喜欢

转载自blog.csdn.net/weixin_42444693/article/details/104228900
今日推荐