13.模块介绍
本章主题
1、什么是模块
2、模块和文件
3、命名空间
4、导入模块
5、导入模块属性
6、模块内置包模块的其他属性
一、模块基本概念
1、模块是从逻辑上组织python代码的形式
2、当代码量变得相当大的时候,最好把代码分成一些有组织的代码段,前提是保证它们的彼此交互
3、这些代码片段相互间有一定的联系,可能是一个包含数据成员和方法的类,也可能是一组相关但彼此独立的操作函数
4、这些代码段是共享的,所以python允许“调入”一个模块,允许使用其他模块的属性来利用之前的工作成果,实现代码重用
二、模块文件
1、模块是按照逻辑来组织python代码的方法,文件是物理层上组织模块的方法
2、 一个文件被看作是一个独立模块,一个模块也可以被看作是一个文件
3、模块的文件名就是模块的名字加上扩展名.py
模块名称空间
1、名称空间就是一个从名称到对象的关系映射集合
2、给定一个模块名之后,只可能有一个模块被导入到python解释器中,所以在不同模块间不会出现名称交叉现象
3、所以每个模块都定义了它自己的唯一的名称空间
4、即使属性之间有名称冲突,但它们的完整授权名称防止了名称冲突的发生
5、完整授权名称,通过点号属性标识指定了各自的名称空间
先编辑foo.py和bar.py
foo.py
#!/usr/bin/env python #coding:utf8 hi= "hello"
bar.py
#!/usr/bin/env python #coding:utf8 hi = "greet"
执行如下:
>>> import foo
>>> import bar
>>> print foo.hi #调用foo模中的hi变量
hello
>>> print bar.hi #调用bar模块中的hi变量
greet
搜索路径和路径搜索
1、导入模块需要一个"路径搜索"的过程,即在文件系统"预定义区域"中查找某个以.py结尾的文件
2、这些预定义区域只不过是你的Python搜索路径的集合
3、路径搜索是指查找某个文件的操作
4、搜索路径是去查找一组目录
5、默认搜索路径是编译时或安装时指定的,它可以被修改
6、解释器启动后,搜索路径会被保存在sys模块中的sys.path变量里,这个变量包含每个独立路径的列表
7、如果需要导入模块,而该模块不在搜索路径中,那么只需要调用列表中的append()方法即可;修改完之后,即可加载自己的模块
例如:
sys.path.append('/home/py/lib')
python解释器在启动时会自动加载一些模块,可以使用sys.modules查看
在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。
所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
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归档文件当成一个目录去处理。
至于.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
搜索路径:
当一个命名为my_module的模块被导入时
解释器首先会从内建模块中寻找该名字
找不到,则去sys.path中找该名字
sys.path从以下位置初始化
执行文件所在的当前目录
PTYHONPATH(包含一系列目录名,与shell变量PATH语法一样)
依赖安装时默认指定的
注意:在支持软连接的文件系统中,执行脚本所在的目录是在软连接之后被计算的,换句话说,包含软连接的目录不会被添加到模块的搜索路径中
在初始化后,我们也可以在python程序中修改sys.path,执行文件所在的路径默认是sys.path的第一个目录,在所有标准库路径的前面。
这意味着,当前目录是优先于标准库目录的,需要强调的是:我们自定义的模块名不要跟python标准库的模块名重复
三、名称空间
1、名称空间是名称(标识符)到对象的映射
2、向名称空间添加名称的操作过程涉及绑定标识符到指定对象的操作,以及给该对象的引用计数加1
3、改变一个名字的绑定叫做重新绑定,删除一个名称叫做解除绑定
4、在执行期间有2个或3个活动的名称空间:局部名称空间、全局名称空间和内置名称空间。但局部名称空间在执行期间是不断变化的,所以说"2个或3个"
5、从名称空间中访问这些名字依赖于它们的加载顺序,或是系统加载这些名称空间的顺序
6、Python解释器首先加载内置名称空间。它由__buitins__模块中的名字构成。随后加载执行模块的全局名称空间,它会在模块开始执行后变为活动名称空间。这样我们就有了2个活动的名称空间
7、如果在执行期间调用了一个函数,那么则会创建出第三个名称空间,即局部名称空间。我们可以通过globals()和locals()内置函数判断出某一个名称属于哪个名称空间。
核心笔记:__buitins__和__buitin__
1、__buitins__模块和__buitin__模块不能混淆
2、__buitins__模块包含内建名称空间中内建名字的集合
3、其中大多数(如果不是全部的话)来自__builtin__模块,该模块包含内建函数,异常以及其他属性
4、在标准Python执行环境下,__builtins__包含__builtin__的所有名字
名称空间和变量作用域的比较
1、名称空间是纯粹意义上的名称和对象间的映射关系,而作用域还指出了从用户代码的哪些物理位置可以访问到这些名字
2、每个名称空间是一个自我包含的单元。但从作用域的观点来看,事情是不同的。所有局部名称空间的名称都在局部作用范围内。局部作用范围以外的所有空间都在全局作用范围内。
3、在程序执行过程中,局部名称空间和作用域会随函数调用而不断变化,而全局名称空间是不变的。
名称查找、确定作用域、覆盖
作用域联系名称空间的方式:名称查询
1、访问一个属性时,解释器必须在3个名称空间中的一个找到它。
2、首先从局部名称空间开始,如果没有找到就查找全局名称空间。如果还没找到的话,则在内建的名称空间里查找。
3、如果还是查不到的话,则报NameError错误
四、创建模块
1、模块物理层面上组织模块的方法是文件,每一个以.py作为结尾的python文件都是一个模块
2、模块名称切记不要与系统中已存在的模块重名
3、模块文件名字去掉后面的扩展名(.py)即为模块名
五、导入模块
使用import导入模块
1、语法1:
import module1[, module2[,... moduleN]]
但是这样的代码可读性不如多行的导入语句。
2、语法2:
import module1
import module2
...
import moduleN
当我们使用import语句的时候,Python解释器是怎样找到对应的文件的呢?答案就是解释器有自己的搜索路径,存在sys.path里。
因此在当前目录下存在与要引入模块同名的文件,就会把要引入的模块屏蔽掉
1、模块被导入后,程序会自动生成pyc的字节码文件以提升性能
2、模块属性通过"模块名.属性"的方法调用
3、可以在一行导入多个模块,但是可读性会下降
例子1:
>>> import sys
>>> import os, string
>>> string.digits
'0123456789'
>>> from random import randint
>>> randint(1, 10)
3
例子2:
>>> import time, os, sys
>>> from random import choice
>>> import cPickle as p
示例文件:自定义模块my_module.py,文件名my_module.py,模块名my_module
my_module.py文件内容如下:
print('from the my_module.py')
demo.py文件内容如下:
import my_module
为什么这段代码,Pycharm会飘红呢?
因为我的Pycharm的工作目录为E:\python_script
而代码所在的目录为E:\python_script\day26
在工作目录下面,找不到my_module文件,所以报错
为了解决这个,需要点击file->open 选择文件夹为E:\python_script\day26
点击Open in current windows->ok
在新窗口中,点击deme.py文件,就不会飘红了。
执行demo.py文件
程序输出:
from the my_module.py
what ?为什么执行了?
导入一个模块,相当于这个模块从上到下依次被执行了
import 语句的模块推荐顺序
1、Python标准库模块
2、Python第三方模块
3、自定义模块
另外,使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突
解释器执行到import语句,如果在搜索路径中找到了找到的模块,就会加载它
1、如果在一个模块的顶层导入,那么它的作用域就是全局的;
2、如果在函数中导入,那么它的作用域就是局部的
3、如果模块是被第一次导入,它将被加载并执行
from-import语句
1、可以只导入模块的某些属性 ,也就是把指定名称导入到当前作用域
语法:from module import name1 [,name2[,...nameN] ]
这个声明不会把整个modulename模块导入到当前的命名空间中,只会将它里面的name1或name2单个引入到执行这个声明的模块的全局符号表。
扩展的import语法(as)
1、导入模块时,可以为模块取别名
语法:
import moduleName as alias
运行本质
#1 import test
#2 from test import add
无论1还是2,首先通过sys.path找到test.py,然后执行test脚本(全部执行),区别是1会将test这个变量名加载到名字空间,而2只会将add这个变量名加载进来。
编译python文件
为了提高加载模块的速度,强调强调强调:提高的是加载速度而绝非运行速度。python解释器会在__pycache__目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。通常会包含python的版本号。例如,在CPython3.3版本下,my_module.py模块会被缓存成__pycache__/my_module.cpython-33.pyc。这种命名规范保证了编译后的结果多版本共存。
Python检查源文件的修改时间与编译的版本进行对比,如果过期就需要重新编译。这是完全自动的过程。并且编译的模块是平台独立的,所以相同的库可以在不同的架构的系统之间共享,即pyc使一种跨平台的字节码,类似于JAVA或.NET,是由python虚拟机来执行的,但是pyc的内容跟python的版本相关,不同的版本编译后的pyc文件不同,2.5编译的pyc文件不能到3.5上执行,并且pyc文件是可以反编译的,因而它的出现仅仅是用来提升模块的加载速度的。
python解释器在以下两种情况下不检测缓存
1 如果是在命令行中被直接导入模块,则按照这种方式,每次导入都会重新编译,并且不会存储编译后的结果(python3.3以前的版本应该是这样)
python -m my_module.py
2 如果源文件不存在,那么缓存的结果也不会被使用,如果想在没有源文件的情况下来使用编译后的结果,则编译后的结果必须在源目录下
提示:
1.模块名区分大小写,foo.py与FOO.py代表的是两个模块
2.你可以使用-O或者-OO转换python命令来减少编译模块的大小
-O转换会帮你去掉assert语句
-OO转换会帮你去掉assert语句和__doc__文档字符串
由于一些程序可能依赖于assert语句或文档字符串,你应该在在确认需要的情况下使用这些选项
3.在速度上从.pyc文件中读指令来执行不会比从.py文件中读指令执行更快,只有在模块被加载时,.pyc文件才是更快的
4.只有使用import语句是才将文件自动编译为.pyc文件,在命令行或标准输入中指定运行脚本则不会生成这类文件,因而我们可以使用compieall模块为一个目录中的所有模块创建.pyc文件
模块可以作为一个脚本(使用python -m compileall)编译Python源
python -m compileall /module_directory 递归着编译
如果使用python -O -m compileall /module_directory -l则只一层
命令行里使用compile()函数时,自动使用python -O -m compileall
详见:https://docs.python.org/3/library/compileall.html#module-compileall
六、模块导入的特性
载入时执行模块
1、模块具有一个__name__特殊属性
2、当模块文件直接执行时,__name__的值为'__main__'
3、当模块被另一个文件导入时,__name__的值就是该模块的名字
[root@py01 bin]# vim foo.py
#!/usr/bin/env python
print __name__
[root@py01 bin]# ./foo.py
__main__
[root@py01 bin]# python
>>> import foo
foo
如果我们是直接执行某个.py文件的时候,该文件中那么__name__ == '__main__'是True,但是我们如果从另外一个.py文件通过import导入该文件的时候,这时__name__的值就是我们这个py文件的名字而不是__main__。 这个功能还有一个用处:调试代码的时候,在if __name__ == '__main__'中加入一些我们的调试代码,我们可以让外部模块调用的时候不执行我们的调试代码,但是如果我们想排查问题的时候,直接执行该模块文件,调试代码能够正常运行
Python中的__name__和__main__的含义
注意:测试代码和调试代码略有不同:
1.测试代码指的是if __name__ == '__main__'上面的python代码
2.调试代码指的是if __name__ == '__main__'下面的python代码
导入(import)和加载(load)
1、一个模块只被加载一次,无论它被导入多少次
2、只加载一次可以阻止多重导入时代码被多次执行
3、如果两个文件相互导入,防止了无限的相互加载
4、模块加载时,顶层代码会自动执行,所以只将函数放入模块的顶层是良好的编程习惯
[root@py01 ~]# cat foo.py
hi = 'hello'
print hi
[root@py01 ~]# python
>>> import foo
Hello #第一次导入,执行print语句
>>> import foo #再次导入,print语句不再执行
>>>
同一个模块不会被多次导入
总结:
模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句)
我们可以从sys.modules中找到当前已经加载的模块,sys.modules是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。
修改my_module.py,内容如下:
print('from the my_module.py')
def func():
print('in func')
那demo.py如何调用呢?修改demo.py文件
import my_module
my_module.func() # 执行函数
执行输出:
from the my_module.py
in func
import 的 过程
import一个模块的时候,首先创建一个属于my_module的内存空间
加载my_module模块中所有的代码
将my_module模块中的名字方法放在my_module的命名空间里
my_module.py增加一个变量,修改文件,内容如下:
print('from the my_module.py')
money = 100
def func1():
print('in func')
def func2():
print('in func2')
修改demo.py,调用func2和money
import my_module
my_module.func2()
print(my_module.money)
执行输出:
from the my_module.py
in func
100
demo.py里面也创建一个func1和money
那么调用时,它会调用谁呢?
import my_module
money = 200
def func1(): # func1函数名不冲突
print('in my func1')
my_module.func1() # 执行my_module模块中的func1函数
func1() # 执行当前模块中的func1函数
print(money)
执行输出:
from the my_module.py
in func
in my func1
200
从结果中,可以看出,函数func1调用的是当前模块中的。
每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突
总结:首次导入模块my_module时会做三件事:
1.为源文件(my_module模块)创建新的名称空间,在my_module中定义的函数和方法若是使用到了global时访问的就是这个名称空间。
2.在新创建的命名空间中执行模块中包含的代码,见初始导入import my_module
提示:导入模块时到底执行了什么?
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()可以查看
3.创建名字my_module来引用该命名空间
这个名字和变量名没什么区别,都是‘第一类的’,且使用my_module.名字的方式可以访问my_module.py文件中定义的名字,my_module.名字与test.py中的名字来自两个完全不同的地方。
导入到当前名称空间的名称
1、调用from-import可以把名字导入到当前的名称空间里去,这意味着不需要使用属性/句点属性标识来访问模块的标识符
from module import *
核心风格:限制使用from module import *
1、from module import *不是良好的编程风格,因为它"污染"当前名称空间,而且很可能覆盖当前名称空间中现有的名称
2、建议只在2种场合使用:目标模块中的属性非常多,反复键入模块名不方便;在交互环境下,这样可以减少输入次数
被导入到导入者作用域的名字
1、只从模块导入名字的另一个副作用是那些名字会成为局部名称空间的一部分。
2、这可能覆盖一个已经存在的具有相同名字的对象,而且对这边变量的改变只影响它的局部拷贝而不是所导入模块的原始名称空间
3、也就是说,绑定只是局部的而不是整个名称空间
关于__future__
1、使用fom-import语句"导入"新特性,用户可以尝试一下新特性或特性变化,以便在特性固定下来的时候修改程序
2、语法:from __future__ import new_feature
3、只import __future__不会有什么变化,必须显式地导入指定特性
从zip文件中导入模块
1、在2.3版中,python加入了从ZIP归档文件导入模块的功能
2、如果搜索路径中存在一个包含python模块(.py、.pyc、或.pyo文件)的.zip文件,导入时会把ZIP文件当作目录处理
3、如果要导入的一个ZIP文件只包含.py文件,那么Python不会为其添加对应的.pyc文件,这意味着如果一个ZIP归档没有匹配的.pyc文件时,导入速度会相对慢一些
#导入sys模块,在搜索路径中加入相应的zip文件
>>> import sys
>>> sys.path.append('/root/pymodule.zip')
>>> import foo #导入pymodule.zip压缩文件中的foo模块
七、模块内置函数
__import__()
1、Python 1.5导入了__import__()函数,它作为实际上导入模块的函数,这意味着import 语句调用__import__()函数完成它的工作
2、提供这个函数是为了让有特殊需要的用户覆盖它,实现自定义的导入算法
3、__import__()的语法是: __import__(module_name [,globals [, locals[,fromlist] ] ] )
3.1 module_name变量是要导入模块的名字,globals是包含当前全局符号表的名字的字典,locals是包含局部符号表的名字的字典,fromlist是一个使用from_import语句所导入符号的列表
3.2 globals、locals和fromlist参数都是可选的,,,默认分别是globals()、locals()和[]
调用import sys可以使用下边的语句完成:
sys = __import__('sys')
用名称字符串导入模块
1、使用内置的__import__函数来从一个名称字符串载入的话,代码可能会运行得更快
>>> modname = "string"
>>> string = __import__(modname)
>>> string
<module 'string' from 'D:\Python27\lib\string.pyc'>
>>>
globals()和locals()
1、globals()和locals()内置函数分别返回调用者全局和局部名称空间的字典
2、在一个函数内部,局部名称空间代表在函数执行时候定义的所有名字
3、locals()函数返回的就是包含这些名字的字典
4、globals()会返回函数可访问的全局名字
5、在全局名称空间下,globals()和locals()函数返回相同的字典,因为这时的局部名称空间就是全局空间
#!/usr/bin/env python
#coding:utf8
def foo():
print "\ncalling foo()..."
aString = 'bar'
anInt = 42
print "foo()'s globals:",globals().keys()
print "foo()'s locals:",locals().keys()
print "__main__'s globals:",globals().keys()
print "__main__'s locals:",locals().keys()
foo()
执行结果:
__main__'s globals: ['__builtins__', '__file__', '__package__', '__name__', 'foo', '__doc__']
__main__'s locals: ['__builtins__', '__file__', '__package__', '__name__', 'foo', '__doc__']
calling foo()...
foo()'s globals: ['__builtins__', '__file__', '__package__', '__name__', 'foo', '__doc__']
foo()'s locals: ['anInt', 'aString']
reload()
1、reload()内建函数可以重新导入一个已经导入的模块
2、语法:reload(module)
3、module是你想要重新导入的模块
4、使用reload的注意事项:
4.1 首先模块必须是全部导入,不是使用from-import ,而且它必须被成功导入
4.2 reload()函数的参数必须是模块自身的而不是包含模块名的字符串,也就是说必须类似relaod(sys)而不是reload('sys')
八、包
包是一种通过使用'.模块名'来组织python模块名称空间的方式。
1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:
凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。
但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
对比import item 和from item import name的应用场景:如果我们想直接使用name那必须使用后者。
2. 包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
3. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
强调:
1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
4.包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
使用Pycharm创建一个包,会自动创建__init__.py文件
所谓的包,就是一个包含__init__.py文件的文件夹
包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
目录结构
1、包是一个有层次的文件目录结构,为平坦的名称空间加入有层次的组织结构
2、允许程序员把有联系的模块组合到一起
3、在python2中,包导入语句的路径中的每个目录下必须有一个__init__.py文件
,否则导入失败;这个文件的作用是当导入初次遍历一个包目录的时候所运行的的代码的文件
4、除了模块名之外,导入也可以指定目录路径
5、包导入是把计算机上的目录变成另一个Python命名空间,则属性则对应于目录中所包含的的子目录和模块文件
6、在python2中,import和from语句中所列出的每个目录都必须含有__init__文件。其他目录则不需要包含这个文件,包括含有包路径最左侧组件的目录
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。
请注意,每一个包目录下面都会有一个__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录(文件夹),而不是一个包。__init__.py
可以是空文件,也可以有Python代码,因为__init__.py
本身就是一个模块,而它的模块名就是对应包的名字。
调用包就是执行包下的__init__.py文件
自动创建目录结构
import os os.makedirs('glance/api') os.makedirs('glance/cmd') os.makedirs('glance/db') l = [] l.append(open('glance/__init__.py','w')) l.append(open('glance/api/__init__.py','w')) l.append(open('glance/api/policy.py','w')) l.append(open('glance/api/versions.py','w')) l.append(open('glance/cmd/__init__.py','w')) l.append(open('glance/cmd/manage.py','w')) l.append(open('glance/db/models.py','w')) map(lambda f:f.close() ,l)
执行代码,查看目录结构
文件内容如下:
#policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py: ',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py: ',engine)
手动将内容复制到对应的py文件中去
这里面的每一个文件夹,都是包
包里面的每一个py文件,都是模块
新建一个文件new.py
目录结构如下:
./ ├── glance │ ├── api │ │ ├── __init__.py │ │ ├── policy.py │ │ └── versions.py │ ├── cmd │ │ ├── __init__.py │ │ └── manage.py │ ├── db │ │ └── models.py │ └── __init__.py └── new.py
new.py想用glance目录下的api文件夹下的policy模块
修改new.py
from glance.api import policy policy.get()
执行输出:
from policy.py
注意事项
1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:
凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。
2.对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
3.对比import item 和from item import name的应用场景:如果我们想直接使用name那必须使用后者。
也可以用这种写法:
import glance.api.policy glance.api.policy.get()
执行输出:
from policy.py
对于点没有约束,对比2种方式,使用from方式,就比较简单了。
使用from-import 导入包
语法:from package.module import *
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
这样就报错了
from glance import api.policy policy.get()
下面的方式,也是不对的
import glance glance.api.policy.get()
症结就是导入一个包,并没有把包里面的所有内容,导入进来
导入一个包,相当于执行了这个包下面的__init__.py文件
修改glance下面的__init__.py文件
import api
手动执行__init__.py文件,没有报错
修改new.py
import glance glance.api.policy.get()
执行new.py,提示报错
ImportError: No module named 'api'
为什么?
因为sys.path,路径中找不到api。__init__.py 执行时,在它的工作目录中可以找到api
但是new.py所在的工作目录,无法直接找到api目录,所以就报错了
跨模块导入包
目录结构如下:
├── __init__.py
├── crm
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py
├── manage.py
└── proj
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
根据上面的结构,如何实现在crm/views.py里导入proj/settings.py
模块?
from proj import settings
结果:
Traceback (most recent call last):
File "D:/crm/views.py", line 1, in <module>
from proj import views
ModuleNotFoundError: No module named 'proj'
为什么直接导入会报错呢?
是因为路径找不到,proj/settings.py 相当于是crm/views.py的父亲(crm)的兄弟(proj)的儿子(settings.py),settings.py算是views.py的表弟啦,在views.py里只能导入同级别兄弟模块代码,或者子级别包里的模块,根本不知道表弟表哥的存在。这可怎么办呢?
答案是添加环境变量,把父亲级的路径添加到sys.path中,就可以了,这样导入 就相当于从父亲级开始找模块了。
crm/views.py中添加环境变量
#!/usr/bin/env python #coding:utf-8 import sys ,os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #__file__的是打印当前被执行的模块.py文件相对路径,注意是相对路径 print(BASE_DIR) sys.path.append(BASE_DIR) from proj import settings
注意:此时在proj/settings.py写上import urls会有问题么?
答案是肯定会有问题的,结果报错了,因为现在的程序入口是views.py,所以执行views.py会报错
注意:python2不会报错
因为现在的程序入口是views.py,而且之前在crm/views.py执行了添加变量的操作:
#!/usr/bin/env python #coding:utf-8 import sys ,os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #__file__的是打印当前被执行的模块.py文件相对路径,注意是相对路径 print(BASE_DIR) sys.path.append(BASE_DIR) from proj import settings
所以你在settings.py导入import urls,其实相当于在crm目录找urls.py,而不是proj目录,若想正常导入,要改成如下:
from proj import urls #proj这一层目录已经添加到sys.path里,可以直接找到
print('in proj/settings.py')
这里就涉及了绝对导入和相对导入了,下面直接说一下
绝对导入
1.包的使用越来越广泛,很多情况下导入子包会导致和真正的标准库模块发生冲突
2.因此,所有的导入现在都被认为是绝对的,也就是说这些名字必须通过python路径(sys.path或PYTHONPATH)来访问
3.这个决定的基本原理是子包也可以通过sys.path访问
4.从Python 2.7开始,绝对导入特性将成为默认功能
5.包模块会把名字相同的标准库隐藏掉,因为它首先在包内执行相对导入,隐藏掉标准库模块
绝对导入的格式为 import A.B 或 from A import B
我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入:以glance作为起始
相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
修改glance目录下的__init__.py文件
from glance import api
修改api目录下的__init__.py
from glance.api import policy
再次执行new.py,输出:
from policy.py
为什么呢?因为首先执行了glance目录下的__init__.py文件,再执行了api目录下的__init__.py
所以就能找到policy模块
所有导入,都是以glance为基础
这就是绝对路径导入
这个时候,新建一个文件new_pac
将glance目录剪切到new_pac
当前目录结构如下:
./ ├── new_pac │ └── glance │ ├── api │ │ ├── __init__.py │ │ ├── policy.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-35.pyc │ │ │ └── policy.cpython-35.pyc │ │ └── versions.py │ ├── cmd │ │ ├── __init__.py │ │ └── manage.py │ ├── db │ │ └── models.py │ ├── __init__.py │ └── __pycache__ │ └── __init__.cpython-35.pyc └── new.py
如果new.py想调用new_pac目录下glance文件夹下面的内容呢?
修改new.py文件
from new_pac import glance glance.api.policy.get()
执行报错:
ImportError: No module named 'glance'
为什么?因为glance下的所有__init__.py路径不对,都得改
修改glance目录下的__init__.py
from new_pac.glance import api
修改api目录下的__init__.py
from new_pac.glance.api import policy
再次执行new.py,输出
from policy.py
但是这样太麻烦了,有缺点
一旦上级目录发生变化,那么所有的init_py,都得修改
总结:
绝对路径
被直接执行的文件与包的关系必须是固定的,
一旦发生改变,包内的所有关系都要重新指定
跨包引用的问题,无法解决
如果policy想要执行manage呢?
修改policy.py,内容如下:
from new_pac.glance.cmd import manage def get(): print('from policy.py') manage.main()
执行报错:
ImportError: No module named 'new_pac'
不能单独执行policy.py
它取决于sys.path
不改变new.py内容,执行new.py,输出:
from manage.py
from policy.py
结果正常。
特别需要注意的是:可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式。
相对导入
1.绝对导入特性使得程序员失去了import的自由,为此出现了相对导入
2.因为import语句总是绝对导入的,所以相对导入只应用于from-import语句
3.相对导入特性稍微改变了import语法,让程序员告诉导入者在子包的哪里查找某个模块
相对导入格式为 from . import B 或 from ..A import B,.代表当前模块,..代表上层模块,...代表上上层模块,依次类推。
Python 3.0 引入了2个变化:
1、它修改了模块导入的搜索路径语义,以默认地跳过包自己的目录。导入只是检查搜索路径的其他组件。这叫做绝对导入
2、它扩展了from语句的语法,以允许显示地要求导入只搜索包的目录。这叫做相对导入语法
在linux里可以通过cd ..回到上一层目录 ,cd ../.. 往上回2层,这个..就是指相对路径,在python里,导入也可以通过..来实现
相对导入可以避免硬编码带来的维护问题,例如我们改了某一顶层包的名,那么其子包所有的导入就都不能用了。但是 存在相对导入语句的模块,不能直接运行,否则会有异常:
ValueError: Attempted relative importin non-package
这是什么原因呢?我们需要先来了解下导入模块时的一些规则:
在没有明确指定包结构的情况下,Python 是根据 __name__ 来决定一个模块在包中的结构的,如果是 __main__ 则它本身是顶层模块,没有包结构,如果是A.B.C 结构,那么顶层模块是 A。基本上遵循这样的原则:
1.如果是绝对导入,一个模块只能导入自身的子模块或和它的顶层模块同级别的模块及其子模块 2.如果是相对导入,一个模块必须有包结构且只能导入它的顶层模块内部的模块
如果一个模块被直接运行,则它自己为顶层模块,不存在层次结构,所以找不到其他的相对路径。
Python2.x 缺省为相对路径导入,Python3.x 缺省为绝对路径导入。
python2.0默认是相对导入,即先执行相对导入包,如果没有找到相关包,再执行绝对导入
python3.0默认是绝对导入,即通过搜索sys.path路径查找模块或者包,如果在各个path的路径上没有查找到对应的包或者模块名字,则会直接报错,提示无法:ModuleNotFoundError: No module named 'XXXX'。
绝对导入可以避免导入子包覆盖掉标准库模块(由于名字相同,发生冲突)。如果在 Python2.x 中要默认使用绝对导入,可以在文件开头加入如下语句:
from __future__ import absolute_import
from __future__ import absolute_import
这句 import 并不是指将所有的导入视为绝对导入,而是指禁用 implicit relative import(隐式相对导入), 但并不会禁掉 explicit relative import(显示相对导入)。
那么到底什么是隐式相对导入,什么又是显示的相对导入呢?我们来看一个例子,假设有如下包结构:
thing
├── books
│ ├── adventure.py
│ ├── history.py
│ ├── horror.py
│ ├── __init__.py
│ └── lovestory.py
├── furniture
│ ├── armchair.py
│ ├── bench.py
│ ├── __init__.py
│ ├── screen.py
│ └── stool.py
└── __init__.py
那么如果在 stool 中引用 bench,则有如下几种方式:
import bench # 此为 implicit relative import from . importbench # 此为 explicit relative import from furniture import bench # 此为 absolute import
隐式相对就是没有告诉解释器相对于谁,但默认相对与当前模块;而显示相对则明确告诉解释器相对于谁来导入。以上导入方式的第三种,才是官方推荐的,第一种是官方强烈不推荐的,Python3 中已经被废弃,这种方式只能用于导入 path 中的模块。
相对与绝对仅针对包内导入而言
最后再次强调,相对导入与绝对导入仅针对于包内导入而言,要不然本文所讨论的内容就没有意义。所谓的包,就是包含 __init__.py 文件的目录,该文件在包导入时会被首先执行,该文件可以为空,也可以在其中加入任意合法的 Python 代码。
相对导入可以避免硬编码,对于包的维护是友好的。绝对导入可以避免与标准库命名的冲突,实际上也不推荐自定义模块与标准库命令相同。
前面提到含有相对导入的模块不能被直接运行,实际上含有绝对导入的模块也不能被直接运行,会出现 ImportError:
ImportError: No module named XXX
这与绝对导入时是一样的原因。要运行包中包含绝对导入和相对导入的模块,可以用 python -m A.B.C 告诉解释器模块的层次结构。
有人可能会问:假如有两个模块 a.py 和 b.py 放在同一个目录下,为什么能在 b.py 中 呢?
这是因为这两个文件所在的目录不是一个包,那么每一个 python 文件都是一个独立的、可以直接被其他模块导入的模块,就像你导入标准库一样,它们不存在相对导入和绝对导入的问题。相对导入与绝对导入仅用于包内部。
例如:
├── __init__.py
├── crm
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py #from ..proj import settings
├── manage.py
└── proj
├── __init__.py
├── settings.py #from . import urls
├── urls.py
└── wsgi.py
views.py里代码
from ..proj import settings
def sayhi():
print('hello world!')
执行结果报错了(linux)
Traceback (most recent call last):
File "my_proj/crm/views.py", line 4, in <module>
from ..proj import settings
SystemError: Parent module '' not loaded, cannot perform relative import
或者报错如下(windows):
Traceback (most recent call last):
D:/模块学习
File "D:/crm/views.py", line 9, in <module>
from .. import settings
ValueError: attempted relative import beyond top-level package
其实这两个错误的原因归根结底是一样的:在涉及到相对导入时,package所对应的文件夹必须正确的被python解释器视作package,而不是普通文件夹。否则由于不被视作package,无法利用package之间的嵌套关系实现python中包的相对导入。
文件夹被python解释器视作package需要满足两个条件:
文件夹中必须有__init__.py文件,该文件可以为空,但必须存在该文件。
不能作为顶层模块来执行该文件夹中的py文件(即不能作为主函数的入口)。
所以,当用.. 或 ../..返回上级去导入的时候,如果到了程序的入口就会报错:
ValueError: attempted relative import beyond top-level package
这是因为第2条的原因,也就是相对导入的时候不能返回到顶层目录去导入,否则会报错。所以,用绝对导入的人比较多,相对导入中一个点(同级导入)用的比较多。
所以这个问题的解决办法就是,既然你在views.py里执行了相对导入,那就不要把views.py当作入口程序,可以通过上一级的manage.py调用views.py
├── __init__.py
├── crm
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py #from ..proj import settings
├── manage.py #from crm import views
└── proj
├── __init__.py
├── settings.py #from .import urls
├── urls.py
└── wsgi.py
事实证明还是不行,报错
ValueError: attempted relative import beyond top-level package
但把from ..proj import settings
改成from . import models
后却执行成功了,为什么呢?
from .. import models
会报错的原因是,这句代码会把manage.py所在的这一层视作package,但实际上它不是,因为package不能是顶层入口代码,若想不出错,只能把manage.py往上再移一层。
正确的代码目录结构如下
packages/
├── __init__.py
├── manage.py #from my_proj.crm import views
└── my_proj
├── crm
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py #from . import models; from ..proj import settings
└── proj
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
再执行manage.py就不会报错了。
注意:虽然python支持相对导入,但对模块间的路径关系要求比较严格,处理不当就容易出错,so并不建议在项目里经常使用。
相对导入:
用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
好处就是,比方是你电脑开发的。换了另外一台电脑,也依然可以正常运行,不需要修改文件路径。
再次执行创建文件的脚本
import os os.makedirs('glance/api') os.makedirs('glance/cmd') os.makedirs('glance/db') l = [] l.append(open('glance/__init__.py','w')) l.append(open('glance/api/__init__.py','w')) l.append(open('glance/api/policy.py','w')) l.append(open('glance/api/versions.py','w')) l.append(open('glance/cmd/__init__.py','w')) l.append(open('glance/cmd/manage.py','w')) l.append(open('glance/db/models.py','w')) map(lambda f:f.close() ,l)
再次补充文件内容
#policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py: ',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py: ',engine)
新建文件new2.py
目录结构如下:
./
├── glance
│ ├── api
│ │ ├── __init__.py
│ │ ├── policy.py
│ │ └── versions.py
│ ├── cmd
│ │ ├── __init__.py
│ │ └── manage.py
│ ├── db
│ │ └── models.py
│ └── __init__.py
└── new2.py
修改glance下的__init__.py,内容如下:
from . import api
点表示当前路径
api目录的__init__.py,内容如下:
from . import policy
修改new2.py,内容如下:
import glance glance.api.policy.get()
执行new2.py,输出:
from policy.py
新建目录new_pac2,将目录glance剪切到new_pac2下面
目录结构如下:
./
├── new2.py
└── new_pac2
└── glance
├── api
│ ├── __init__.py
│ ├── policy.py
│ └── versions.py
├── cmd
│ ├── __init__.py
│ └── manage.py
├── db
│ └── models.py
└── __init__.py
修改new2.py,内容如下:
from new_pac2 import glance glance.api.policy.get()
再次执行new2.py,输出:
from policy.py
居然没有报错,666啊!
修改policy.py,导入versions模块
from . import versions def get(): print('from policy.py') versions.create_resource('userinfo')
执行new2.py,输出:
from version.py: userinfo
from policy.py
手动执行policy.py,输出:
ImportError: attempted relative import with no known parent package
它不能当成脚本执行
结论:
在一个py文件中使用了相对路径引入一个模块
那么这个文件就不能被当成脚本运行了
修改policy.py,导入manage模块
from . import versions from .. cmd import manage def get(): print('from policy.py') versions.create_resource('userinfo') manage.main()
点点表示上一级目录
执行new2.py,输出:
from version.py: userinfo from manage.py from policy.py
new2.py 只能关联到glance包
from glance.api import *
在讲模块时,我们已经讨论过了从一个模块内导入所有*,此处我们研究从一个包导入所有*。
此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:
修改policy.py
from . import versions from ..cmd import manage __all__ = ['get'] def get(): print('from policy.py') versions.create_resource('userinfo') manage.main()
修改api.py,内容如下:
from . import policy __all__ = ['policy']
修改new2.py
from new_pac2.glance.api import *
执行输出:
from version.py: userinfo from manage.py
总结:
包就是py模块的集合
自带__init__.py文件
py2 包中必须有一个__init__.py文件
py3 不存在也可以
能不能导入一个包:要看sys.path中的路径下有没有这个包
从包中导入模块: 把包与包之间的关系写清楚,精确到模块,就一定能导入
直接导入一个包,并不会导入包下的模块,而是执行这个包下的__init__.py文件
如果对导入还有更高的要求
可以对包中的__init__.py文件做定义
绝对路径导入的方式
相对路径导入的方式 使用相对路径导入的模块不能作为脚本执行
模块查找规则总结
使用包导入和相对导入,Python 3.0中的模块查找可以概括为:
1、简单模块名通过sys.path 路径列表上的每个目录来查找,从左到右
2、包是带有一个__init__文件的Python模块的直接目录,这使得一个导入中可以使用A.B.C目录路径语法。在A.B.C的一条导入中,名为A的目录位于相对于sys.path的常规模块导入搜索,B是A中的另一个包子目录,C是一个模块或B中的其他可导入项
总结:
包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py 文件,那么这个文件夹就称之为包
有效避免模块名称冲突问题,让应用组织结构更加清晰
__init__.py文件有什么用
__init__.py 控制着包的导入行为
2.1 __init__.py为空
仅仅是把这个包导入,不会导入包中的模块
2.2 __all__
在__init__.py文件中,定义一个__all__变量,它控制着 from 包名 import *时导入的模块
2.3 (了解)可以在__init__.py文件中编写内容
可以在这个文件中编写语句,当导入时,这些语句就会被执行__init__.py文件
九、模块的其他特性
自动载入的模块
1、当 Python 解释器在标准模式下启动时, 一些模块会被解释器自动导入, 用于系统相关操作。唯一一个影响你的是 __builtin__ 模块, 它会正常地被载入, 这和 __builtins__ 模块相同
2、sys.modules 变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典, 模块名作为键, 它们的位置作为值
>>> import sys
>>> sys.modules.keys() #通过调用字典的keys()方法查找出模块名
阻止属性导入
1、如果不想让某个模块属性被"from module import *"导入,就可以给不想导入的属性名称加上一个下划线_
2、但如果你导入了整个模块或者显式地导入某个属性,这个隐藏数据的方法就不起作用了
import foo.bar
模块中的__all__
1. 没有__all_
注意:test.py和main.py是在同一个目录
test.py文件内容:
#!/usr/bin/env python
#coding:utf-8
class Test(object):
def test(self):
print "---Test类中的test函数---"
def test1():
print "---test1类中的test函数---"
def test2():
print "---test2类中的test函数---"
main.py文件内容:
#!/usr/bin/env python
#coding:utf-8
from test import *
a = Test()
a.test()
test1()
test2()
执行main.py文件:
---Test类中的test函数--- ---test1类中的test函数--- ---test2类中的test函数---
2. 模块中有__all__
test.py文件内容:
#!/usr/bin/env python
#coding:utf-8
__all__ = ["Test","test1"]
class Test(object):
def test(self):
print "---Test类中的test函数---"
def test1():
print "---test1类中的test函数---"
def test2():
print "---test2类中的test函数---"
执行main.py文件的结果:
Traceback (most recent call last):
---Test类中的test函数---
File "G:/PycharmProject/fullstack2/week1/main.py", line 11, in <module>
---test1类中的test函数---
test2()
NameError: name 'test2' is not defined
总结
如果一个文件中有__all__变量,那么也就意味着这个变量中的元素,不会被from xxx import *时导入
软件开发规范
比如校园管理系统,目录结构如下:
开始程序为start.py
为什么要设计好目录结构?
"设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。
设计一个层次清晰的目录结构,就是为了达到以下两点:
1.可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。
2.可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。
所以,我认为,保持一个层次清晰的目录结构是有必要的。更何况组织一个良好的工程目录,其实是一件很简单的事儿。
关于README的内容
这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。
它需要说明以下几个事项:
1.软件定位,软件的基本功能。
2.运行代码的方法: 安装环境、启动命令等。
3.简要的使用说明。
4.代码目录结构说明,更详细点可以说明软件的基本原理。
5.常见问题说明。
我觉得有以上几点是比较好的一个README。在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。
python 相对导入和绝对导入参考:https://blog.csdn.net/u013571243/article/details/77734346
https://www.cnblogs.com/xiao987334176/p/8954715.html