Python 模块
概念:
对于一个复杂的功能,为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。
在Python中,一个.py文件就称之为一个模块(Module)。即一个文件被看作一个独立的模块,一个模块也可以被看做是一个文件
使用模块可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。
模块分为三种:
- 内置模块:如sys, os, subprocess, time, json 等等
- 自定义模块:自定义模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了
sys
模块,自己的模块就不可命名为sys.py
,否则将无法导入系统自带的sys
模块。 - 开源模块:公开的第三方模块, 如 https://pypi.python.org/pypi 。可以使用pip install 安装,类似于yum 安装软件
>>> help('modules')
查看python所有的modules
一个Python程序通常包括一个顶层程序文件和其它的模块文件
顶层文件:包含了程序的主要控制流程
模块文件:为顶层文件或其他模块提供各种功能性组件
模块首次导入import(或重载reload)时,Python会立即执行模块文件的顶层程序代码(不在函数内的代码),而位于函数主体内的代码直到函数被调用后才会执行
导入模块
Python允许“导入”其它模块以实现代码重用,从而也实现了将独立的代码文件组织成更大的程序系统。模块是被导入的,但模块也可以导入和使用其他模块,这些模块可以用Python或其他编程语言写成
在导入模块时只能使用模块名,而不是使用带.py后缀的模块文件名
1. import 语句, 用于导入整个模块
import module1, module2.... # 建议一个import语句只导入一个模块
import module as module_alias # 别名(也就是自定义的模块名称空间)
>>> import sys,os
>>> import random as rd
2. from-import 语句 , 常用于只导入指定模块的部分属性或模糊导入
from module import name1,name2....
from
pack1.pack2 import *
>>> from random import choice,random,seed
import和from import是赋值语句
import和from可执行语句,类似于def,因此,它们可以嵌套在if测试中,出现于def中等等
Python执行到这些语句时才会对其进行解析,这意味着,所有来自模块的属性仅在import语句执行后才能使用
import的工作机制
import语句导入指定的模块时会执行3个步骤
1. 找到模块文件:在模块搜索路径下搜索模块文件
程序的主目录
PYTHONPATH目录
标准链接库目录
2.编译成字节码:文件导入时会编译,因此,顶层文件的.pyc字节码文件在内部使用后会被丢弃,只有被导入的文件才会留下.pyc文件
3.执行模块的代码来创建其所定义的对象:模块文件中的所有语句从头至尾依次执行,而此步骤中任何对变量名的赋值运算,都会产生所得到的模块文件的属性
注意:模块只在第一次导入时才会执行如上步骤,后续的导入操作只不过是提取内存中已加载的模块对象,reload()可用于重新加载模块
包: 用于将一组模块归并到一个目录中,此目录即为包,目录名即为包名
包是一个有层次的文件目录结构,它定义了一个由模块和子包组成的Python应用执行环境
基于包,Python在执行模块导入时可以指定模块的导入路径 import pack1.pack2.mod1
每个包内都必须有__init__.py文件, __init__.py 可包含python代码,但通常为空,仅用于扮演包初始化、替目录产生模块命名空间以及使用目录导入时实现from*行为的角色
假设有程序结构及依赖关系如下:
web/
├── __init__.py
├── backend
│ ├── __init__.py
│ ├── config
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── authentic.py
│ │ └── sqlapi.py # 需要导入settings和authentic模块
│ └── logic
│ │ ├── __init__.py
│ │ └── handle.py # 需要导入 sqlapi 模块中的select 函数
├── frontend
└── main.py #需要导入handle模块
#!/usr/bin/env python
''' main module '''
import sys
from backend.logic import handle
print(sys.path) # 交互模式下可以直接sys.path,在交互模式下打印有所不同
#!/usr/bin/env python
''' handle module '''
from backend.db.sqlapi import select
#!/usr/bin/env python
''' sqlapi module '''
from backend.config import settings
from backend.db import authentic
main.py 执行结果:
['/root/scripts/python/web', '/root/.pyenv/versions/3.6.1/lib/python36.zip', '/root/.pyenv/versions/3.6.1/lib/python3.6', '/root/.pyenv/versions/3.6.1/lib/python3.6/lib-dynload', '/root/.pyenv/versions/3.6.1/lib/python3.6/site-packages']
#/root/scripts/python/web 程序主目录
#/root/.pyenv/versions/3.6.1/* PYTHONPATH目录 和 标准链接库目录,site-packages 存放第三方开源模块
注意:如果直接执行sqlapi.py 会抛出异常 ModuleNotFoundError: No module named 'backend' 。因为sqlapi模块所在路径下并没有backend目录,所以找不到路径,而我们执行main.py之所以可以正常导入,因为backend目录处于main所在的路径下。
但我们在编写一个模块时往往需要单独调试,此时我们可以把需要导入模块的正确路径添加到sys.path
修改sqlapi.py 如下:
#!/usr/bin/env python
''' sqlapi module '''
import os,sys basedir=os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(basedir) print(sys.path)
from backend.config import settings
from backend.db import authentic
执行结果: ['/root/scripts/python/web/backend/db', '/root/.pyenv/versions/3.6.1/lib/python36.zip', '/root/.pyenv/versions/3.6.1/lib/python3.6', '/root/.pyenv/versions/3.6.1/lib/python3.6/lib-dynload', '/root/.pyenv/versions/3.6.1/lib/python3.6/site-packages', '/root/scripts/python/web']
也可以使用相对路径:
#!/usr/bin/env python
''' sqlapi module '''
import os,sys
sys.path.append('..') # .. 表示上一级目录
print(sys.path)
from config import settings
from db import authentic
执行结果:
['/root/scripts/python/web/backend/db', '/root/.pyenv/versions/3.6.1/lib/python36.zip', '/root/.pyenv/versions/3.6.1/lib/python3.6', '/root/.pyenv/versions/3.6.1/lib/python3.6/lib-dynload', '/root/.pyenv/versions/3.6.1/lib/python3.6/site-packages', '..']
内置变量 __file__:表示当前的文件名
os.path.abspath(path) :返回path的绝对路径
os.path.dirname(path) :返回path的目录(去掉最后一级目录或文件)
一个模块文件可以直接执行或被导入
每个模块都有一个名为__name__的内置变量,此变量值会根据调用此模块的方式发生变化:
1. 如果此文件被作为模块导入,则__name__的值为模块名称
2. 如果此文件被直接执行,则__name__的值为“__main__”
我们在创建模块时,在尾部添加如下代码进行模块的自我测试
if __name__ == '__main__':
...
如果模块被导入,则 if 后的语句就不会执行
第三方模块
官方文档:https://docs.python.org/3/installing/#
安装第三方模块有两种方式:
1. 通过包管理工具pip完成的
如果你正在使用Mac或Linux,安装pip本身这个步骤就可以跳过
如果使用Windows,确保安装时勾选了pip
和Add python.exe to Path
。
注意:Mac或Linux上有可能并存Python 3.x和Python 2.x,因此对应的pip命令是pip3
2. 下载源码=> 解压源码=> 进入目录=> 编译源码:python setup.py build => 安装源码: python setup.py install
在使用源码安装时,需要使用到gcc编译 #yum install gcc
以 paramiko模块为例
paramiko是一个用于做远程控制的模块,使用该模块可以对远程服务器进行命令或文件操作,fabric和ansible内部的远程管理就是使用的paramiko来现实。
# python -m pip install paramiko
No package 'libffi' found
# yum install -y libffi-devel
导入和使用模块
#!/usr/bin/env python
''' 通过用户名和密码连接服务器 ''' import paramiko ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect('10.104.229.168', 22, 'root', 'password') stdin,stdout,stderr = ssh.exec_command('ifconfig') print(stdout.read()) ssh.close();
执行结果:
b'eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n inet 10.104.229.168 netmask 255.255.192.0 broadcast 10.104.255.255\n ether 52:54:00:e3:90:ac txqueuelen 1000 (Ethernet)\n RX packets 12475918 bytes 2316344006 (2.1 GiB)\n RX errors 0 dropped 0 overruns 0 frame 0\n TX packets 12179205 bytes 1357889151 (1.2 GiB)\n TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n\nlo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536\n inet 127.0.0.1 netmask 255.0.0.0\n loop txqueuelen 0 (Local Loopback)\n RX packets 68 bytes 5552 (5.4 KiB)\n RX errors 0 dropped 0 overruns 0 frame 0\n TX packets 68 bytes 5552 (5.4 KiB)\n TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n\n'