# 10-Python模块详解 ## 一、何为模块 模块让你能够有逻辑地组织Python代码段。把相关的代码分配到一个模块里能让你的代码更好用,更易懂。模块也是Python对象。简单地说,模块就是一个保存了 Python代码的文件,能定义函数,类和变量。 模块文件,需要以 `.py` 结尾;比如 time.py,模块名为time,可以通 过`import time`使用。 library-->modules-->functions(methods)-->python指令 **模块的分类** 从来源来分,可以分成 1. 内置 2. 第三方 3. 自定义 ```python __import__() 查询模块是内建模块还是属于某一个模块文件 In [5]: __import__("sys") Out[5]: In [3]: __import__("os") Out[3]: In [4]: __import__("os").__file__ Out[4]: '/usr/lib64/python2.7/os.pyc' ``` **查看模块存放位置** 通过查看模块存放路径就知道我们自己制作的模块应该放在哪个位置 查看python默认的模块存放路径。 ```python >>> import sys >>> sys.path ['', '/usr/local/python27/lib/python27.zip', '/usr/local/python27/lib/python2.7', '/usr/local/python27/lib/python2.7/plat-linux2', '/usr/local/python27/lib/python2.7/lib-tk', '/usr/local/python27/lib/python2.7/lib-old', '/usr/local/python27/lib/python2.7/lib-dynload', '/usr/local/python27/lib/python2.7/site-packages'] 列表内的第一个元素''表示当前工作目录,也就是说模块在当前目录下也可使用 默认存放模块的目录是:/usr/local/python27/lib/python2.7/ ``` **查看模块内定义的标识符** ``` __dir__()函数返回一个排好序的字符串列表,内容是一个模块里定义过的名字。容纳了在一个模块里定义的所有模块,变量和函数。 ``` ```python >>> import keyword >>> keyword.__dir__() ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', '__all__', 'kwlist', 'softkwlist', 'iskeyword', 'issoftkeyword'] ``` 在这里,特殊字符串变量`__name__`指向模块的名字,`__file__`指向该模块的导入文件名。 **模块的意义** 1、从文件级别组织程序,功能重复利用,更方便管理 随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做,程序的结构会更清晰,可方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用。 2、偷懒,提升开发效率 同样的原理,我们也可以下载别人写好的模块然后导入到自己的项目中使用,这种偷懒,在 python 中是大力支持的,可以极大地提升我们的开发效率 **模块相关站点** https://pypi.python.org/pypi/ PyPI(Python Package Index,PyPI) python包索引 为Internet上的第三方Python模块提供一个集中的存储库 ## 二、制作模块 **模块结构** 用模块来合理组织Python 代码应该建立一种统一且容易阅读的结构,并将它应用到每一个文件中去。 一个典型模块的内部结构: (1) 起始行 (2) 模块文档 (3) 模块导入 (4) 变量定义 (5) 类定义 (6) 函数定义 (7) 主程序 **一个叫做mod的模块制作和调用的例子** 一个叫做aname的模块里的Python代码一般都能在一个叫aname.py的文件中找到。 ```python [root@wing python]# cat mod.py #!/usr/bin/env python3.8 ''' 这是一个模块实例 ''' import sys,os name="wing" def hello(): print("hello world") if __name__ =="__main__": hello() 使用模块: In [1]: import mod In [2]: mod.hello() hello world In [3]: print(mod.__doc__) 这是一个模块实例 In [4]: print(mod.name) wing ``` **模块中的特殊变量** 1、`__file__` 存储当前 py 文件的文件名或此模块的绝对路径 ```python In [1]: import mod # 导入模块,如果是脚本需要把命令放在脚本的顶端 In [2]: mod.__file__ Out[2]: '/root/Development/python_code/day01/mod.py' ``` 2、`__name__` Python内置了全局变量`__name__`,使用 `__name__` 控制 .py 文件的用途,用来控制.py文件在不同的应用场景下执行不同的逻辑 编写好的一个python文件可以有两种用途: 1)脚本,一个文件就是整个程序,用来被执行,当文件被当做脚本执行时:`__name__` 等于'`__main__`' 2)模块,文件中存放着一堆功能(函数或类),这些功能可以用来被导入使用,当文件被当做模块导入时:`__name__`等于模块名 下例中可以使用两种方法调用main函数,但是每次在python解释器里第一次import模块时都会执行main函数如果又想可以把模块当脚本一样在命令行整体执行,又想在其他脚本调用模块内单独的某一个函数,可以使用第二种方式 ```python # vim pysysinfo_func.py #!/usr/bin/env python3.8 import subprocess def uname_func(): uname = "uname" uname_arg = "-a" print("Gathering system information with %s command:\n" % uname) subprocess.call([uname,uname_arg]) def disk_func(): diskspace = "df" diskspace_arg = "-Th" print("Gathering diskspace information with %s command:\n" % diskspace) subprocess.call([diskspace,diskspace_arg]) def main(): uname_func() disk_func() #第一种方式: main() #第二种方式: if __name__ == "__main__": main() # 第一种方式的结果如下:会在导入模块的时候执行main函数 In [1]: import pysysinfo_func Gathering system information with uname command: Linux vm2.up.com 2.6.32-358.el6.x86_64 #1 SMP Tue Jan 29 11:47:41 EST 2013 x86_64 x86_64 x86_64 GNU/Linux Gathering diskspace information with df command: Filesystem Type Size Used Avail Use% Mounted on /dev/mapper/VolGroup-lv_root ext4 18G 4.1G 13G 25% / tmpfs tmpfs 565M 224K 565M 1% /dev/shm /dev/sda1 ext4 485M 34M 427M 8% /boot /dev/sr0 iso9660 3.5G 3.5G 0 100% /mnt # 第二种方式的结果如下: In [1]: import pysysinfo_func # 不会在导入模块的时候执行main函数 ``` **理解`__name__`** ```python Py1.py #!/usr/bin/env python3.8 def test(): print('__name__ = ',__name__) if __name__ == '__main__': test() Py2.py #!/usr/bin/env python import Py1.py def test(): print('__name__ = ',__name__) if __name__ == '__main__': test() print('Py1.py __name__ = ',Py1.__name__) 执行结果: __name__=__main__ Py1.py __name__=Py1 ``` 通过结果可以知道,Py2.py直接执行,那么内建变量`__name__`的值为`__main__`,否则为模块的名字,通过这个特性可以 在if语句里面添加测试代码 ```python if __name__ == '__main__': test() ``` ## 三、导入模块 以echo.py为例来介绍模块的使用:文件名 echo.py,模块名 echo ```python # vim echo.py #!/usr/bin/env python3.8 print('from the echo.py') s = 'wing' x = 100 def get_info1(): print('来自 echo 模块的 {}:'.format(s)) def get_info2(): print('来自 echo 模块的 get_info2 函数') get_info1() ``` **import 语句** 想使用Python模块,可以在另一个源文件里执行import语句 一个模块只会被导入一次,不管你执行了多少次import。这样可以防止导入模块被一遍又一遍地执行。 导入模块其实就是告诉Python解释器去解释那个py文件,模块可以包含可执行的语句,这些语句的目的是初始化模块,它们只在模块第一次被导入import 语句时才执行 **第一次导入模块时会做三件事** 1、为源文件(echo模块)创建新的命名空间,对于 echo.py 文件中的函数来说,它们的全局变量就在这个命名空间了。 2、在新创建的命名空间中,再去执行模块中包含的代码。 3、把 echo 这个名字分配给该命名空间,这个名字本质上就是变量名 **语法** import module1[, module2[,... moduleN] 当解释器遇到import语句,如果模块在当前的`搜索路径`就会被导入。 **搜索路径** 是一个解释器会进行搜索的所有目录的列表 当你导入一个模块,Python解析器对模块位置的搜索顺序是:内存中已经加载的模块—>sys.path包含的路径中 1、在第一次导入某个模块时(比如echo),会先检查该模块是否已经被加载到内存中,如果有则直接引用,Python解释器在启动时会自动加载一些模块到内存中,可以用sys.modules查看 2、如果还没有找到,就从 sys.path 给出的目录列表中依次寻找`模块名.py` 文件。搜索时按照sys.path中从左到右的顺序查找,位于前面的优先被搜索。 python程序可以修改sys.path,路径放到前面的优先被使用。 ```python import sys sys.path.append('/a/b/c') sys.path.insert(0,'/x/y/z') ``` **使用模块中的变量或者函数等对象** 用模块名加上点,后面跟上要使用的变量名或者函数名即可 ```python import echo print(echo.x) func = echo.get_info1 func() ``` **每个模块都是一个独立的命名空间** 定义在这个模块中的函数,会把这个模块的命名空间当做全局命名空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中的全局变量会在被导入时,与使用者的全局变量冲突了。 ```python # test.py 文件的内容 import echo x=10 print(echo.x) 执行 test.py 文件后的结果如下: from the echo.py 100 ``` **模块别名** 为已经导入的模块起别名的方式对编写可扩展的代码很有用 ```python import echo as sm print(sm.x) ``` 使用场景: 假如有两种sql模块mysql和oracle,根据用户的输入,可以选择不同的sql功能 ```python # vim mysql.py #!/usr/bin/env python3.8 def select(): print('from mysql') # vim oracle.py #!/usr/bin/env python3.8 def select(): print('from oracle') # vim test.py #!/usr/bin/env python3.8 db_type=input('>>: ') if db_type == 'mysql': import mysql as db elif db_type == 'oracle': import oracle as db else: sys.exit('请输入 mysql 或 oracle') db.select() ``` **From…import 语句** Python的from语句让你从模块中导入一个指定的部分到当前命名空间中。 在当前命名空间中,直接使用名字就可以了、无需加前缀:echo. 好处:使用起来方便了 坏处:容易与当前执行文件中的名字冲突 语法如下: ```python from modname import name1[, name2[, ... nameN]] ``` ```python from echo import get_info1 as read # 同样支持 as from echo import get_info1,get_info2,x # 在一行导入多个名字 ``` 例如,要导入模块fib的fibonacci函数,使用如下语句: ```python from fib import fibonacci ``` 这个声明不会把整个fib模块导入到当前的命名空间中,它只会将fib里的fibonacci单个引入到执行这个声明的模块的全局符号表。 **From…import * 语句** 把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明: ```python from modname import * ``` 这提供了一个简单的方法来导入一个模块中的所有项目,但是不包括以单个下划线 _ 开头的名字。然而这种声明不该被过多地使用。 因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字,而且可读性极其的差。 也可以使用模块的内置属性 `__all__` 来控制 *,只需要在echo.py中新增一行 ```python __all__ = ['x','get_info1'] # 这样在另外一个文件中用from echo import * 就能导入列表中规定的两个名字 ``` ​ **Python解释器开启时自动调用模块** 正常情况下使用python解释器,使用模块的方法时需要导入模块,一些自己比较常用的模块比如os,process每次开始python解释器都得重新调用,下面的方法可以为你解除痛苦 只需设置一个变量,变量的值为某个.py文件的路径,在.py文件内设置预先想要导入的模块 ```python # export PYTHONSTARTUP=/a.py # cat /a.py import os import subprocess # python3.8 >>> os.system("ls") # 可以看到测试的OS模块的时候可以直接使用,无需事先导入模块OS anaconda-ks.cfg Documents install.log.syslog Public a.py Downloads Music ``` **重载模块** 当一个模块被导入到一个脚本,模块顶层部分的代码只会被执行一次。因此,如果你想重新执行模块里顶层部分的代码,可以用reload()函数。该函数会重新导入之前导入过的模块。 语法 ```python from imp import reload reload(module_name) # 这里,module_name要直接放模块的名字,而不是一个字符串形式。 ``` 比如想重载hello模块,如下: ```python from imp import reload reload(hello) ``` 1.同一个模块导入,第一次代码全部运行,第二次很多的代码都不运行的,其实中间只是重复执行 2.尝试在第一次导入后,修改源文件,然后第二次导入,结果跟第一次一样。 原因 导入操作的开销非常大,它把文件先编译成字节码,然后再导pvm(python virtual machine)上去执行,在编译的过程中,消耗资源非常多,所以,导入操作只编译执行一次,第二次只是重复执行,不再编译 如果想再次执行完整的代码,就需要 `reload()` 这个函数,他会把源代码重新载入,然后执行一遍,但是在执行reload前,必须保证已经import那个模块 例: ```python 1.编写模块a.py #!/usr/bin/env python3.8 print ("hello world") 2.进入Ipython导入模块 In [1]: import a hello world # 第一次导入结果有输出 In [2]: import a # 第二次导入结果就没有输出了 In [3]: import a # 修改源文件后再次导入仍然没有输出结果,必须的用reload()重载模块才行 ``` ## 四、发布自己的模块 以一个处理嵌套列表的代码为例,把他做成可以发给别人使用的模块 1.创建父目录 ```python # mkdir /nester ``` 2.准备源代码文件 ```python #vim /nester/nester.py #!/usr/bin/env python3.8 "这是模块文档字符串" def print_list(name): "这是函数文档字符串" for each_item in name: if isinstance(each_item,list): print_list(each_item) else: print(each_item) ``` 3.准备setup.py文件 ```python # vim /nester/setup.py #!/usr/bin/env python3.8 from distutils.core import setup setup( name = 'nester', version = '1.0.0', py_modules = ['nester'], author = 'wing', author_email = '276267003@qq.com', url = 'www.fklinux.com', description = 'A simple printer of nested lists', ) ``` 4.构建发布文件 ```python # cd /nester # python setup.py sdist ``` 5.构建成功之后/nester目录下会出现dist目录,dist目录下会出现构建好的模块打包文件,这个文件就可以发给别人使用了 ```python [root@host nester]# ls dist/ nester-1.0.0.tar.gz ``` 6.测试安装模块 ```python # tar xvzf nester-1.0.0.tar.gz # cd nester-1.0.0 # python setup.py install # python3.8 >>> import nester # 其实到这里,如果你能导入模块成功的话,恭喜你,^_^说明你的模块儿没问题了,下面是这个模块的具体使用 >>> a=[1,2,3,[4,5,[6,7]]] >>> nester.print_list(a) 1 2 3 4 5 6 7 ``` ## 五、包 在创建许许多多模块后,我们可能希望将某些功能相近的文件组织在同一文件夹下,这里就需要运用包的概念了。包对应于文件夹,使用包的方式跟模块也类似,唯一需要注意的是,当文件夹当作包使用时,文件夹需要包含`__init__.py`文件,主要是为了避免将文件夹名当作普通的字符串。`__init__.py`的内容可以为空,一般用来进行包的某些初始化工作,创建包的目的不是为了运行,而是被导入使用,其实,包只是模块的一种形式而已,包的本质就是一种模块 **软件开发规范** ```python . ├── bin # 存放命令执行文件 ├── conf # 存放配置文件 ├── src # 存放主程序代码 ├── data # 存放程序的数据 └── lib # 存放程序的库文件,比如公共的函数库 ``` **包的使用** 示范文件 [![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\1.png](assets/1.png)]() 文件初始内容 run.py ```python from core import main main.main_func() ``` settings.py ```python HOST = '1.1.1.1' ``` main.py ```python from conf import settings host = settings.HOST def main_func(): print('from main') print(host) ``` 1、包的使用之from ... import ... 比如在 run.py 中导入 core 下的 main ```python from core import main ``` 但是这样还是不能正常导入 这是因为,环境变量的 sys.path 的目录是以执行文件为基准的。 也就是,当我们现在执行 run.py 文件时,会把 run.py 文件所在的当前目录添加到 sys.path 中,也就是 bin 目录,而我们要导入的 main 是从 core 导入,但是 bin 目录下并没有 core 。 解决办法是,将他们共同的上一级目录添加到 sys.path 中。 在 run.py 文件中添加以下内容即可 ```python import os, sys BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) print(BASEDIR) sys.path.insert(0, BASEDIR) from core import main main.main_func() ``` ```python # 运行run.py文件 [root@workstation bin]# python run.py /root/qf from main 1.1.1.1 ``` 2、包的使用之import 单独导入包时不会导入包中所有包含的所有子模块,如 在与qf同级的test.py中写下如下内容后,执行 ```python import qf # 导入包名qf qf.bin # 执行qf下的bin包 执行结果会报错: AttributeError: module 'qf' has no attribute 'bin' ``` 解决方法:在包 qf 下面的 `__init__.py` 文件下添加如下内容 ```python from . import bin ``` ```python # 如果想执行bin包下的run文件也需要在bin目录下添加__init__.py文件 [root@workstation ~]# vim qf/bin/__init__.py from . import run # 执行test.py文件 [root@workstation ~]# python3.8 test.py /root/qf from main 1.1.1.1 ``` ## 六、技巧-python和shell互传变量 **python -> shell** **1.环境变量** ```python import os var=123或var='123' os.environ['var']=str(var) # environ的键值必须是字符串 os.system('echo $var') ``` **2.字符串连接** 代码如下: ```python import os path='/root/a.txt' var=[1] var='bash' os.system('echo ' + path) # 注意echo后有空格 os.system('echo ' + str(var[0])) os.system('echo ' + var + ' /root/c.sh') # 注意echo后和/root前有空格 ``` **3.通过管道** ```python import os var='123' os.popen('wc -c', 'w').write(var) ``` **4.通过文件** ```python output = open('/tmp/mytxt', 'w') output.write(S) #把字符串S写入文件 output.writelines(L) #将列表L中所有的行字符串写到文件中 output.close() ``` **5.通过重定向标准备输出** ```python buf = open('/root/a.txt', 'w') print >> buf, '123\n', 'abc' ``` 或 ```python print >> open('/root/a.txt', 'w'), '123\n', 'abc' #写入或生成文件 print >> open('/root/a.txt', 'a'), '123\n', 'abc' #追加 ``` **shell -> python** **1.管道** ```python import os var=os.popen('echo -n 123').read( ) print var ``` 2.(python3已经移除) ```python import commands var=commands.getoutput('echo abc') # 输出结果 var=commands.getstatusoutput('echo abc') # 退出状态和输出结果 ``` **3.文件** ```python input = open('/tmp/mytxt', 'r') S = input.read( ) # 把整个文件读到一个字符串中 S = input.readline( ) # 读下一行(越过行结束标志) L = input.readlines( ) # 读取整个文件到一个行字符串的列表中 ```