文章目录
模块化
- 一般来说,编程语言中,库,包,模块是同一种概念,是代码组织方式.
- python中只有一种模块对象类型,但是为了模块化组织模块的便利,提供了"包"的概念.
- 模块__module__,指的是python的__源代码文件__.
- 包package,指的是模块组织在一起的和包名同名的目录及其相关文件.
导入语句
语句 | 含义 |
---|---|
import 模块1 [,模块2,…] | 完全导入(后面只能跟模块,不能跟类和函数) |
import… as … | 模块别名 |
- import语句
- 找到指定的模块,加载和初始化它,生成模块对象.找不到,抛出异常(加载)
- 在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联(关联->可调用)
import functools #导入模块
print (dir()) ## 返回模块特殊属性以及新增了变量名"functools"
print(functools) ##<module 'functools' from 'C:\\Programs\\Python\\Python36\\lib\\functools.py'>
print(functools.wraps) #<function wraps at 0x000001708C2AA048>
import os.path
print(dir()) ##多了一个"os"(这里os.path比较特殊)
print(os) #'C:\\Programs\\Python\\Python36\\lib\\os.py'>
print(os.path) # <module 'ntpath' from 'C:Programs\\Python\\Python36\\lib\\ntpath.py'>
import os.path as osp ##导入os.path并复制给osp
print(dir()) ##多了一个"osp"
print(osp) ##和os.path打印一样
def testimport():
import os.path #导入到局部的作用域中
print(dir()) ##显示本地的变量名
testimport() # ['os']
print(globals().keys()) ##没有出现os变量名
- 总结
- 导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象.
- 导入非顶级模块,纸将其顶级模块名称加入到本地名词空间中.导入的模块必须使用完全限定名称来访问
完全限定名称访问(例如:os.path) - 如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
语句 | 含义 |
---|---|
from … import … | 部分导入 |
from … import … | 别名 |
- 与单独import区别在于本地命名空间中添加的名称是import后的名称,而非模块名
- 且from…import.后可以跟类函数等变量名
from pathlib import Path, PosixPath# 在当前名词空间导入该模块指定的成员
print(dir())#['Path', 'PosixPath', '__annotations__', '__builtins__'...]
from pathlib import * #在当前名词空间导入该模块的所有公共成员,即不带下划线的
print(dir())
# out ['Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath', '__annotations__']
from functools import wraps as wr, partial# 别名
print(dir()) # [..., 'wr', 'partial']
from os.path import exists# 加载、初始化os、os.path模块,exists加入本地名词空间并绑定
if exists('o:/t'):
print('Found')
else:
print('Not Found')
print(dir())
print(exists)
import os
# 4种方式获得同一个对象exists
print(os.path.exists) #<function exists at 0x000001CCFA5A9D08>
print(exists) #<function exists at 0x000001CCFA5A9D08>
print(os.path.__dict__["exists"])#<function exists at 0x000001CCFA5A9D08>
print(getattr(os.path,"exists"))#<function exists at 0x000001CCFA5A9D08>
总结
- 找到from字句中指定的模块,加载并初始化它(注意不是导入)
- 对于import字句后的名称
- 先查from字句导入的模块是否具有该名称的属性
- 如果不是,则尝试导入该名称的子模块
- 还没有找到,则抛出ImportError异常
- 成功导入,这个名称保存在本地名词空间中,如果有as字句,则使用as字句后的名称
from pathlib import Path ##导入Path类
print(Path, id(Path))# <class 'pathlib.Path'> 1485470051272
import pathlib as p1 ##导入模块使用别名
print(dir()) ##包含path 和 p1
print(p1)# <module 'pathlib' from 'C:\\Programs\\Python\\Python36\\lib\\pathlib.py'>
print(p1.Path, id(p1.Path)) #<class 'pathlib.Path'> 1485470051272
- 可以看出导入的名词Path 和p1.path是同一个对象,名称不同时其名称都会记录
# t1
print("this is test1 module")
class A:
def showmodule(self):
print(1,self.__module__,self)
print(2,self.__dict__)
print(3,self.__class__.__dict__)
print(4,self.__class__.__name__)
a = A()
a.showmodule()
#t2
import t1
a = t1.A()
a.showmodule()
# t3
from t1 import A as cls
a = cls()
a.showmodule()
- 在t1中运行时,主模块是t1,此时实例的模块返回的是__ main__
- 在t2,t3中运行时,主模块为所在模块,此时会先初始化并加载ti模块,执行t1中的打印操作
- 实例的模块返回的是t1
自定义模块命名规范
- 模块名就是文件名
- 模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线的组合。
test-module.py这样的文件名不能作为模块名。也不要使用中文。 - 不要使用系统模块名来避免冲突,除非你明确知道这个模块名的用途
- 通常模块名为全小写,下划线来分割
模块搜索顺序
- 使用sys.path查看搜索顺序
import sys
print(*sys.path,sep = "\n")
- 显示结果为python模块的路径搜索顺序
- 当加载一个模块的时候,需要从这些搜索路径从前到后依次查找,并不搜索这些目录的子目录
- 搜索到模块就加载,搜索不到就抛异常
- 路径也可以为字典,zip文件,egg文件
- .egg文件,是由sepuptools库创建的包,第三方库常用的格式.添加了元数据(版本号,依赖项等)信息的文件
- 路径顺序为
- 程序主目录,程序运行的主程序脚本所在目录
- PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索模块的路径
- 标准库目录,Python自带的库模块所在的目录
- sys.path可以被修改,增加新的目录
模块的重复导入
-
模块不会产生重复导入的现象
-
因为所有加载模块都记录在sys.modules中,sys.modules是存储已经记载过的所有模块的字典
-
打印sys.mofules可以看到已经加载的模块
-
‘builtins’, ‘sys’, ‘_frozen_importlib’, ‘_imp’, ‘_warnings’, ‘_thread’, ‘_weakref’, ‘_frozen_importlib_external’, ‘_io’, ‘marshal’, ‘nt’, ‘winreg’, ‘zipimport’, ‘encodings’, ‘codecs’, ‘_codecs’, ‘encodings.aliases’, ‘encodings.utf_8’, ‘_signal’, ‘main’, ‘encodings.latin_1’, ‘io’, ‘abc’, ‘_weakrefset’, ‘site’, ‘os’, ‘errno’, ‘stat’, ‘_stat’, ‘ntpath’, ‘genericpath’, ‘os.path’, ‘_collections_abc’, ‘_sitebuiltins’, ‘_bootlocale’, ‘_locale’, ‘encodings.gbk’, ‘_codecs_cn’, ‘_multibytecodec’, ‘types’, ‘functools’, ‘_functools’, ‘collections’, ‘operator’, ‘_operator’, ‘keyword’, ‘heapq’, ‘_heapq’, ‘itertools’, ‘reprlib’, ‘_collections’, ‘weakref’, ‘collections.abc’, ‘importlib’, ‘importlib._bootstrap’, ‘importlib._bootstrap_external’, ‘warnings’, ‘importlib.util’, ‘importlib.abc’, ‘importlib.machinery’, ‘contextlib’, ‘mpl_toolkits’, ‘sysconfig’, ‘encodings.cp437’])
-
sys模块已经在打开文件时就已经在字典中了,是python帮我们自动加载的一些必须的模块
模块运行
-
__ name__ ,每个模块都会定义一个__ name__特殊变脸来存储当前模块的名称,
如果不指定,则默认为源代码文件名,如果是包则有限定名. -
解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数,常量)模块
__ main__模块,sys模块已经初始化模块搜索路劲sys.path -
Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入
-
从标准输入(命令行方式敲代码)、脚本(python test.py)或交互式读取的时候,
会将模块的__ name__设置为__ main__,模块的顶层代码就在__ main__这个作用域中执行。
顶层代码:模块中缩进最外层的代码。如果是import导入的,其__ name__默认就是模块名
if __ name__ == ‘__ main__’:用途
- 利用了模块在被导入时模块名不再是__ main__:
- 本模块的功能测试
对于非主模块,测试本模块内的函数,类 - 避免主模块变更的副作用
顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入模块,
由于原来代码没有封装,一并执行了。
# test1.py文件
import test2
# test2.py文件
# 判断模块是否以程序的方式运行 $python test.py
if__name__ == '__main__':
print('in __main__') # 程序的方式运行的代码
else:
print('in imported module') # 模块导入的方式运行的代码
模块的属性
属性 | 含义 |
---|---|
__ file__ | 字符串,源文件路径 |
__cached __ | 字符串,编译后的字节码文件路径 |
__ spec__ | 显示模块的规范 |
__ name__ | 模块名 |
__ pachage__ | 当模块是包,同__name__;否则,可以设置为顶级模块的空字符串 |
import t1
for k,v in t1.__dict__.items():
print(k,str(v)[:80])
print(dir(t1))
for name in dir(t1):
print(getattr(t1,name))
# 打印结果
# in imported module
# __name__ t1
# __doc__ None
# __package__
# __loader__ <_frozen_importlib_external.SourceFileLoader object at 0x0000028F044390F0>
# __spec__ ModuleSpec(name='t1', loader=<_frozen_importlib_external.SourceFileLoader object
# __file__ C:\Users\17849\PycharmProjects\untitled\练习\模块化\t1.py
# __cached__ C:\Users\17849\PycharmProjects\untitled\练习\模块化\__pycache__\t1.cpython-36.pyc
# __builtins__ {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other o
包
定义: 特殊的模块
Python模块支持目录吗?
import 练习 as l
print(l)
print(type(l))
print(dir(l))
##<module '练习' (namespace)>
#<class 'module'>
#['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
没有__file__,但是多了一个__path__
.
可以导入目录,目录也是文件,但是目录模块怎么写代码呢?
为了解决这个问题,python要求目录下建立一个特殊文件__init__.py
,在其中写入代码
pytcharm中,创建Directory和创建Python package不同,前者是创建普通的目录,后者是创建一个带有__init__.py
文件的目录即包
子模块
- 包目录下的py文件,子目录都是其子模块
- 删除__init__.py 试一试.可以发现删除并不影响导入,但是有时候一些工具发现包里没有__init__文件,就不认这个包
模块和包的总结
包能够更好的组织模块, 尤其是大的模块, 其代码行数很多, 可以拆成很多子模块,便于想要单独使用某个功能对应的子模块
包目录__init__.py是在包第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工作的代码,最好不要删除它
导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块
包目录之间只能使用.号作为间隔符,表示模块及其子模块的层级关系
模块也是封装,如同函数和类一样.
模块就是命名空间,其内部的顶层标识符都是他的属性,可以通过__dict__
或者dir(module)
查看
包也是模块
,包是特殊的模块,是一种组织方式, 它包含__path__
属性
-
问题
from json import encoder之后,json.dump函数用不了,为什么?
import json.encoder之后呢?json.dump函数能用吗? -
原因是
from json import encoder之后,当前名词空间没有json,但是json模块已经加载过了,没有json的引用,无法使用dump函数。
import json.encoder也加载json模块,但是当前名词空间有json,因此可以调用json.dump。
绝对导入,相对导入
绝对导入
在mport语句或者from导入模块,模块的名称最前面不是.点开头的
绝对导入总是去模块搜索路径中找
,当然会查看一下该模块是否已经加载
相对导入
只在包内使用,且只能用在from语句中
使用.点号,表示当前目录
…表示上一级目录
不要在顶层模块中使用相对导入
- 举例a.b.c模块,a、b是目录,c是模块c.py,c的代码中,使用
from . import d # imports a.b.d
from … import e # imports a.e
from .d import x # a.b.d.x
from …e import x # a.e.x … 三点表示上上一级
测试一下有相对导入语句的模块,能够直接运行吗?
不能了,很好理解,使用相对导入的模块就是为了内部互相的引用资源的,不是为了直接运行的,对于包来说,正确的使用方式还是在顶级模块使用这些包。
相对导入,更像是目录操作。
注意:一旦一个模块中使用相对导入,就不可以作为主模块运行了
访问控制
下划线开头的模块名
_ 或者 __ 开头的模块是否能够被导入呢?
创建文件名为 _xyz.py 或者 __xyz.py 测试
都可以成功的导入,因为它们都是合法的标识符,就可以用作模块名
- 普通变量、保护变量、私有变量、特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做特殊处理。
- 依然可以使用from语句,访问所有变量
from … import * 和__all__
使用from … import * 导入
# xyz.py
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
# test.py中
from xyz import*
import sys print(sorted(sys.modules.keys()))
print(dir())
print(locals()['A'])
A = 55
print(locals()['A']) # 思考这个A是谁的A了
结果是只导入了A,下划线开头的都没有导入
使用__all__
__all__是一个列表,元素是字符串,每一个元素都是一个模块内的变量名
# xyz.py
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
# test.py中
from xyz import*
import sys print(sorted(sys.modules.keys()))
print(dir())
# print(locals()['A'])
print(local()['X'])
print(local()['Y'])
print(local()['_B'])
print(local()['__C'])
使用from xyz import *只能导入__all__列表中的名称
包 和子模块
init.py中有什么变量,则使用from m import *加载什么变量,这依然符合模块的访问控制
总结
- 一. 使用from xyz imort *导入
- 如果模块没有__all__.from xyz imort *只导入非下划线开头的该模块的变量,如果是包,子模块也不会导入,除非在__all__中设置,或者__init__.py中导入他们
- 如果模块中有__all__,from xyz imort *只导入__all__列表中指定的名称,哪怕是下划线开头后者子模块
- from xyz imort *方式简单,但是副作用是导入大量不需要的变量,甚至造成名称冲突, __ all__就是为了阻止其导入过多的模块变量,因此,编写模块时,应该尽量加入__all__
- 二. form module import name1, name2 导入
这种方式的导入是明确的,哪怕是导入子模块,或者导入下划线开头的名称
程序员可以有控制的导入名称和其对应的对象
模块变量的修改
- 模块对象是同一个,因此模块的变量也是同一个,对模块变量的修改,会影响所有使用者。
- 除非万不得已,或明确知道自己在做什么,否则不要修改模块的变量。
- 前面学习过的猴子补丁,也可以通过打补丁的方式,修改模块的变量、类、函数等内容。