python 模块和包
1 模块
1. 什么是模块?
常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。
但其实import加载的模块分为四个通用类别:
1 使用python编写的代码(.py文件)
2 已被编译为共享库或DLL的C或C++扩展
3 包好一组模块的包
4 使用C编写并链接到python解释器的内置模块
2. 常见的模块形式?
别人写好的函数,变量,方法放在一个文件里(这个文件被我们直接使用),这个文件就是模块.
形式 : py文件 dll文件 zip文件 等
3. 为什么要使用模块?
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用.
模块的导入
import
示例文件 : 自定义一个模块 my_module.py, 文件名为 my_module.py, 模块名则为 my_module
# my_module.py 内容
print(12345)
name = 'alex'
def read1():
print('hello world')
def read2():
print('in read2 func',name)
模块导入
导入模块 import my_module # pycharm有时候认为模块导不进来,报个假错
模块导入的过程发生了什么? 导入一个模块就是执行一个模块, 因此, print(12345) 会被打印.
如何使用my_module模块中的变量 :
print(my_module.name)
print(my_module.read1)
my_module.read1()
# 12345 # 模块导入时就执行了
# alex
# <function read1 at 0x000001FD35798F28>
# hello world
import 的命名空间
模块的命名空间和当前文件在不同的命名空间中,当模块导入的时候,会在内存中为模块开辟一个空间.
模块中的变量使用时为 : 如 module.read1() module.name
# import的命名空间,模块和当前文件在不同的命名空间中
import my_module
name = 'egon'
def read1():
print('main read1')
print(name) # 此处name变量指的是本文件中的变量
print(my_module.name) # 此处的name变量才是模块中的name变量
read1() #指的是本文件中的read1方法
# 12345 # 导入模块的时候,模块中的打印代码
# egon # 模块中虽然有name变量,此处打印的name变量是本文件中的变量
# alex
# main read1
模块导入的过程
# 模块是否可以被重复导入
# import my_module
# import my_module
# 怎么判断这个模块已经被导入过了???
# import sys
# print(sys.modules)
模块导入的过程发生了什么?
1 找到这个模块
2 判断这个模块是否被导入
3 如果没有被导入过
1) 创建一个属于这个模块的命名空间
2) 让模块的名字 指向 这个空间
3) 执行这个模块中的代码
给模块起别名
import my_module as m
给模块起了别名以后,使用这个模块就要使用别名来引用变量.
import my_module as m
m.read1()
起别名的用处 :
1. 简化别名的长度
2. 例如 序列化模块中,jason和pickle模块中的方法一样, 想实现一样的功能,可以使用别名引入,减少代码的重复.
# json pickle
# dumps loads
def func(dic,t = 'json'):
if t == 'json':
import json
return json.dumps(dic)
elif t == 'pickle':
import pickle
return pickle.dumps(dic)
import json as aaa
def func1(dic, t='json'):
if t == 'json':
import json as aaa
elif t == 'pickle':
import pickle as aaa
return aaa.dumps(dic)
模块导入的规范建议
# 导入多个模块的非规范形式
import os,time
import os as o,time as t
导入多个模块时,应该一个一个的导入,且导入的顺序为:
内置模块, 第三方(扩展)模块, 自定义模块 (且三大类之间用空行隔开)
#模块导入的正确演示
import os
import django
import my_module
from ... import ...
from ... import ...
如何使用?
需要从一个文件中使用哪个名字,就把这个名字给导入进来.
from my_module import name #导入的过程中仍然执行了这个被导入的文件.
from my_module import read1 # import 谁 就只能用谁
命名空间
from my_module import read1
# 当前文件命名空间和模块的命名空间的问题
from my_module import read1 #文件中会开辟一个read1指向 模块中的read1
def read1(): # 此时read1会打断指向模块的路, 会重新指向声明的函数read1
print('in my read1')
read1() # 执行read1 会执行函数的read1 而非模块中的read1
from my_module import read2
read2()
# 12345
# in my read1
# in read2 func alex
解析:

from ... import ... 模块导入的时候会发生什么?
# 1.找到要被导入的模块
# 2.判断这个模块是否被导入过
# 3.如果这个模块没被导入过
# 创建一个属于这个模块的命名空间
# 执行这个文件
# 找到你要导入的变量
# 给你要导入的变量创建一个引用,指向要导入的变量
导入多个函数或变量名字
# 导入多个名字?
from my_module import read1,read2
read1()
read2()
为函数或变量名字起别名
# 给导入的名字起别名
from my_module import read1 as r1,read2 as r2
def read1():
print('in my read1')
r1()
r2()
read1()
* 和 __all__ 的关系
from my_module import * # import * 指的是导入模块中所有的变量
__all__ 只能约束 * ,
my_module 中代码 : 如下:

脚本文件如下:
# * 和 __all__ __all__能够约束*导入的变量的内容
from my_module import *
print(name) # 只能输出name
read1() #函数不在 __all__中 不能够执行 会报错
read2() #同样会报错
# 12345
# alex
# 报错
from my_module import * 相当于在文件中开辟了 三个新的变量指向模块中对应的变量

模块引用中的情况
模块的循环引用
♥♥♥
模块之间不允许循环引用 ,
循环引用指的是 : 环形引用,如下图
a中导入b, b中导入c, c中导入a 等

模块a中引用了模块b :
import b
def funca():
print('funca')
b.funcb()
# 一个纯函数组成的 多个模块之间 不能形成循环引用
模块b中引用了模块a :
import a
def funcb():
print('in funcb')
a.funca()
解析: 这是不对的.

模块的加载与修改
♥
已经被导入的模块发生了修改是不会被程序感知的,
要想修改的模块被正在运行的程序感知到,需要重启(重运行)这个程序.
把模块当成脚本来执行
♥♥♥♥♥
if __name__ == '__main__'
代码块
写在这里的代码块只有这个文件被当做脚本的时候才会执行.
1. 执行一个文件的方式 :
在cmd执行, 在pycharm中执行 : 直接执行这个文件 -- (以脚本形式执行)
导入这个文件 : 文件被导入的时候也会被执行
2. 都是py文件
直接运行这个文件, 这个文件就是一个脚本
导入这个文件, 这个文件就是一个模块
3. 当一个 py 文件
当做一个脚本的时候 : 能够独立的提供一个功能,能自主完成交互
当做一个模块的时候 : 能够被导入调用这个功能,不能自主交互.
4. 在一个 py 文件中的 __name__ 变量
当这个文件被当做脚本执行的时候 : __name__ == '__main__'
当这个文件被当做模块导入的时候 : __name__ == '模块的名字'
例如 '计算器' 只需要改为如下即可 :
# 原来的
s = '1 - 2 * (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998))'
ret = main(s)
print(ret,type(ret))
# 新的,这样只有做脚本使用的时候才会执行.
if __name__ == '__main__':
s = input('>>>')
print(main(s))
模块的搜索路径
♥♥♥♥♥
和被当做脚本执行的文件, 同目录下的模块, 可以被直接导入
除此之外其他路径下的模块, 在被导入的时候需要自己修改 sys.path 列表
1. 查看 当前工作目录的模块路径
sys.path 可以查看当前文件工作目录, 以打印的第一个列表为准,
import sys
print(sys.path)
# ['E:\\123\\code\\day21', 'E:\\123', 'D:\\Program Files (x86)\\Python36\\python36.zip',
# 'D:\\Program Files (x86)\\Python36\\DLLs', 'D:\\Program Files (x86)\\Python36\\lib',
# 'D:\\Program Files (x86)\\Python36', 'D:\\Program Files (x86)\\Python36\\lib\\site-packages',
# 'D:\\Program Files (x86)\\PyCharm 2018.1.3\\helpers\\pycharm_matplotlib_backend']
2. 将模块的绝对路径添加到 sys.path 列表中
path = r'E:\123\code\day21\my_module.py'
sys.path.append(path) # 将模块的路径添加到列表中即可
import my_module
print(my_module.name)
my_module.read2()
2. 包
包是什么?
包是几个模块的集合, 包是一种通过使用'模块名'来组织python模块名称空间的方式.
包和文件夹的区别?
在包中会含有一个 __init__.py 文件. 导入包的时候会先执行 __init__.py 文件.
♥♥♥♥♥
1. 无论是 import 形式还是 from...import 形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
2. 包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
3. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件,运行该文件.
4. 强调:
1) 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
2) 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
5. 注意事项:
1) 关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。
2) 对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
3) 对比import item 和from item import name的应用场景:
如果我们想直接使用name那必须使用后者。
包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
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)
创建目录代码
glance/ #Top-level package
├── __init__.py #Initialize the glance package
├── api #Subpackage for api
│ ├── __init__.py
│ ├── policy.py
│ └── versions.py
├── cmd #Subpackage for cmd
│ ├── __init__.py
│ └── manage.py
└── db #Subpackage for db
├── __init__.py
└── models.py
目录结构
#文件内容
#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)
文件内容
import
从包中直接导入模块,
我们在与包glance同级别的脚本文件中测试, 同级别的包, 在脚本文件的sys.path列表中,是可以直接导入的.
# import
import glance.api.policy
glance.api.policy.get()
import glance.api.policy as policy
policy.get()
from ... import ...
语法 : from 包.包.包 import 模块名 from 包.包.包.模块 import 函数名
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
我们在与包glance同级别的脚本文件中测试, 同级别的包, 在脚本文件的sys.path列表中,是可以直接导入的.
# from import
from glance.api import policy
policy.get()
from glance.api.policy import get
get()
__init__.py 文件
不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码。该文件中也可以导入同级包中的模块等, 方便以后直接使用.
绝对导入和相对导入
直接导入包,需要通过设计__init__文件,来完成导入包之后的操作.
导入一个包 :
# 不意味着这个包下面的所有内容都是可以被使用的,
# 导入一个包到底发生了什么?
# 相当于执行这个包下面的__init__.py文件
我们的最顶级包glance是写给别人用的,然后在glance包的内部也会有彼此之间相互导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入 : 以glance为起始, (脚本文件与glance包在同级目录内)
相对导入 : 用 . 或者 .. 的方式最为起始 (只能用于同一个包内,不能用于不同的目录内)
在glance/api/version.py (在version.py中导入cmd中的manage.py)
#绝对导入方式
from glance.cmd import manage
manage.main()
#相对导入方式
from ..cmd import manage
manage.main()
测试结果 : 注意一定要在与glance同级的文件中进行测试.
from glance.api import versions # 其中version中导入了manage就可以使用manage中的代码了
注意:在使用pycharm时,有的情况会为你多做一些事情,这是软件相关的东西,会影响你对模块导入的理解,因而在测试时,一定要回到命令行去执行,模拟我们生产环境,你总不能拿着pycharm去上线代码吧!!!
特别需要注意的是 :
可以使用 import 直接导入内置模块(import os)或者第三方模块,(因为两者已经在sys.path中了)
不能使用 import 直接导入自定义模块(不在sys.path中), 应该使用 from ... import ... 的绝对导入或者相对导入,且包的相对导入只能使用 from 的形式.
比如,我们想在 glance\api\version.py 中导入 glance\api\policy.py , 在同一级目录中直接导入policy, 于是直接做了.
# 在version.py import policy policy.get()
上述情况,单独运行version没有问题, 运行version的路径是从当前路径开始的,两者(version和policy)在同一个路径下,所以在导入的时候可以找到
但是,子包中的模块version.py极有可能是被一个glance包同一级别的其他文件导入,比如我们在于glance同级下的一个test.py文件中导入version.py,如下:
from glance.api import versions ''' 执行结果: ImportError: No module named 'policy' ''' ''' 分析: 此时我们导入versions在versions.py中执行 import policy需要找从sys.path也就是从当前目录找policy.py, 这必然是找不到的 '''
以 glance 包, 同级文件做测试脚本为例 :
绝对导入如下 :
glance/ ├── __init__.py from glance import api from glance import cmd from glance import db ├── api │ ├── __init__.py from glance.api import policy from glance.api import versions │ ├── policy.py │ └── versions.py ├── cmd from glance.cmd import manage │ ├── __init__.py │ └── manage.py └── db from glance.db import models ├── __init__.py └── models.py 绝对导入
相对导入如下:
glance/ ├── __init__.py from . import api #.表示当前目录 from . import cmd from . import db ├── api │ ├── __init__.py from . import policy from . import versions │ ├── policy.py │ └── versions.py ├── cmd from . import manage │ ├── __init__.py │ └── manage.py from ..api import policy #..表示上一级目录,想再manage中使用policy中的方法就需要回到上一级glance目录往下找api包,从api导入policy └── db from . import models ├── __init__.py └── models.py 相对导入
单独导入包
单独导入包 是不会把 包内所有的子模块 都导入进来
比如 :
#在与glance同级的test.py中 import glance glance.cmd.manage.main() ''' 执行结果:报错 找不到 AttributeError: module 'glance' has no attribute 'cmd' '''
解决办法 :
#glance/__init__.py from . import cmd #glance/cmd/__init__.py from . import manage #执行: #在于glance同级的test.py中 import glance glance.cmd.manage.main()
千万别问:__all__不能解决吗,__all__是用于控制from...import *
import glance 之后能够直接调用模块中的方法 :
glance/ ├── __init__.py from .api import * from .cmd import * from .db import * ├── api │ ├── __init__.py __all__ = ['policy','versions'] │ ├── policy.py │ └── versions.py ├── cmd __all__ = ['manage'] │ ├── __init__.py │ └── manage.py └── db __all__ = ['models'] ├── __init__.py └── models.py import glance policy.get() import glance
软件开发的规范

程序开发的规范 : 包之间的关系
bin : 脚本代码
core : 核心代码, 注册 购物 登录 等等
db : database 数据库, userinfo文件 存放数据的
conf : 配置文件, 用到了某一个值,这个值在程序执行的过程中会被修改,且一旦修改就要修改程序的多处,这样的值应该单独写在conf文件夹下面的一个文件中,以后所又的程序使用这个值,都需要从这个文件中读取,在需要修改这个值得时候只需要修改配置文件中的一处就可以了. 另外可以方便运维人员使用和维护.
lib : 库, 一个模块既不是内置模块也不是第三方模块, 可能是自己写的比较完善的功能, 和当前项目的相关性不大, 通用模块, 也可以被其他的项目使用
log : 日志, 给用户查看追责, 给开发人员看分析数据
#=============>bin目录:存放执行脚本 #start.py import sys,os BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from core import core from conf import my_log_settings if __name__ == '__main__': my_log_settings.load_my_logging_cfg() core.run() #=============>conf目录:存放配置文件 #config.ini [DEFAULT] user_timeout = 1000 [egon] password = 123 money = 10000000 [alex] password = alex3714 money=10000000000 [yuanhao] password = ysb123 money=10 #settings.py import os config_path=r'%s\%s' %(os.path.dirname(os.path.abspath(__file__)),'config.ini') user_timeout=10 user_db_path=r'%s\%s' %(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),\ 'db') #my_log_settings.py """ logging配置 """ import os import logging.config # 定义三种日志输出格式 开始 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s' # 定义日志输出格式 结束 logfile_dir = r'%s\log' %os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # log文件的目录 logfile_name = 'all2.log' # log文件名 # 如果不存在定义的日志目录就创建一个 if not os.path.isdir(logfile_dir): os.mkdir(logfile_dir) # log文件的全路径 logfile_path = os.path.join(logfile_dir, logfile_name) # log配置字典 LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, } def load_my_logging_cfg(): logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置 logger = logging.getLogger(__name__) # 生成一个log实例 logger.info('It works!') # 记录该文件的运行状态 if __name__ == '__main__': load_my_logging_cfg() #=============>core目录:存放核心逻辑 #core.py import logging import time from conf import settings from lib import read_ini config=read_ini.read(settings.config_path) logger=logging.getLogger(__name__) current_user={'user':None,'login_time':None,'timeout':int(settings.user_timeout)} def auth(func): def wrapper(*args,**kwargs): if current_user['user']: interval=time.time()-current_user['login_time'] if interval < current_user['timeout']: return func(*args,**kwargs) name = input('name>>: ') password = input('password>>: ') if config.has_section(name): if password == config.get(name,'password'): logger.info('登录成功') current_user['user']=name current_user['login_time']=time.time() return func(*args,**kwargs) else: logger.error('用户名不存在') return wrapper @auth def buy(): print('buy...') @auth def run(): print(''' 购物 查看余额 转账 ''') while True: choice = input('>>: ').strip() if not choice:continue if choice == '1': buy() if __name__ == '__main__': run() #=============>db目录:存放数据库文件 #alex_json #egon_json #=============>lib目录:存放自定义的模块与包 #read_ini.py import configparser def read(config_file): config=configparser.ConfigParser() config.read(config_file) return config #=============>log目录:存放日志 #all2.log [2017-07-29 00:31:40,272][MainThread:11692][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!] [2017-07-29 00:31:41,789][MainThread:11692][task_id:core.core][core.py:25][ERROR][用户名不存在] [2017-07-29 00:31:46,394][MainThread:12348][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!] [2017-07-29 00:31:47,629][MainThread:12348][task_id:core.core][core.py:25][ERROR][用户名不存在] [2017-07-29 00:31:57,912][MainThread:10528][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!] [2017-07-29 00:32:03,340][MainThread:12744][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!] [2017-07-29 00:32:05,065][MainThread:12916][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!] [2017-07-29 00:32:08,181][MainThread:12916][task_id:core.core][core.py:25][ERROR][用户名不存在] [2017-07-29 00:32:13,638][MainThread:7220][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!] [2017-07-29 00:32:23,005][MainThread:7220][task_id:core.core][core.py:20][INFO][登录成功] [2017-07-29 00:32:40,941][MainThread:7220][task_id:core.core][core.py:20][INFO][登录成功] [2017-07-29 00:32:47,222][MainThread:7220][task_id:core.core][core.py:20][INFO][登录成功] [2017-07-29 00:32:51,949][MainThread:7220][task_id:core.core][core.py:25][ERROR][用户名不存在] [2017-07-29 00:33:00,213][MainThread:7220][task_id:core.core][core.py:20][INFO][登录成功] [2017-07-29 00:33:50,118][MainThread:8500][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!] [2017-07-29 00:33:55,845][MainThread:8500][task_id:core.core][core.py:20][INFO][登录成功] [2017-07-29 00:34:06,837][MainThread:8500][task_id:core.core][core.py:25][ERROR][用户名不存在] [2017-07-29 00:34:09,405][MainThread:8500][task_id:core.core][core.py:25][ERROR][用户名不存在] [2017-07-29 00:34:10,645][MainThread:8500][task_id:core.core][core.py:25][ERROR][用户名不存在]


浙公网安备 33010602011771号