常用模块>包
序列化模块
序列
- 列表
- 元组
- 字符串
- bytes
什么叫序列化
- 把一个数据类型转换成 字符串、byets类型的过程就是序列化
为什么要把一个数据类型序列化?
- 当你需要把一个数据类型存储在文件中
- 当你需要把一个数据类型通过网络传输的时候
- json模块
- Json模块提供了四个功能:dumps、dump、loads、load
import json
stu = {'name':'何青松','sex':'male'}
ret = json.dumps(stu) # 序列化的过程,将一个字典转换成一个字符串
print(stu,type(stu))
print(ret,type(ret))
d = json.loads(ret) # 反序列化的过程,将一个字符串格式的字典转换成一个字典
print('d-->',d,type(d))
- dump、load 和文件打交道
- 可以多次向一个文件dump,但load的时候会报错
- 定义一个函数调用函数输入多个类型
def func(dic):
with open('json_file','a',encoding='utf-8') as f:
res = json.dumps(dic,ensure_ascii=False)
f.write('%s\n'%res)
dic = {'k1':'v1','k2':'v2','k3':'何青松'}
func(dic)
import json
f = open('json_file','w')
dic = {'k1':'v1','k2':'v2','k3':'v3'}
json.dump(dic,f) #dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
f.close()
f = open('json_file')
dic2 = json.load(f) #load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回
f.close()
print(type(dic2),dic2)
import json
data = {'username':['李华','二愣子'],'sex':'male','age':16}
json_dic2 = json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False)#sort_keys,字符编码排序 #indent=2默认换行空格# separators格式化
print(json_dic2)
json的优点
- 所有的语言都通用
缺点
- 只支持非常少的数据类型
- 对数据类型的约束很苛刻
- 字典的key必须是字符串
- 只支持 : 数字 字符串 列表 字典(不支持元祖)
-
dumps(dic/list) dic/list --> 序列化方法
-
loads(str) str -->dic/list 反序列化方法
-
dump(dic/list,f) dic/list -->文件写入,序列化方法
-
load(f) 文件 -->dic/list 反序列方法
-
参数:ensure_ascii = False 希望序列化的中文能以中文形式显示
-
pickle模块
-
pickle模块提供了四个功能:dumps、dump(序列化,存)、loads(反序列化,读)、load (不仅可以序列化字典,列表...可以把python中任意的数据类型序列化
import pickle
stu = {'name':'何青松','sex':'male',1:('a','b')}
ret = pickle.dumps(stu)
print(ret)
d = pickle.loads(ret)
print(d,type(d))
class Course():
def __init__(self,name,price):
self.name = name
self.price = price
python = Course('python',29800)
ret = pickle.dumps(python)
print(ret)
p = pickle.loads(ret)
print(p.name,p.price)
import pickle
class Course():
def __init__(self,name,price):
self.name = name
self.price = price
python = Course('python',29800)
linux = Course('linux',25800)
mysql = Course('mysql',18000)
ret = pickle.dumps(python)
print(ret)
p = pickle.loads(ret)
print(p.name,p.price)
with open('pickle_file','wb') as f:
pickle.dump(python,f)
with open('pickle_file','rb') as f:
course = pickle.load(f)
print(course.name)
pickle
dump
load
操作文件文件必须以+b打开
在load的时候 如果这个要被load的内容所在的类不在内存中,会报错
pickle支持多次dump和多次load(需要异常处理)
pickle 能不能多次 dump
总结:
内置方法
__new__ :
构造方法,在对象实例化的时候帮助对象开辟一块儿空间
__new__ 比init先执行
单例模式
__del__: 析构方法
删除一个对象之前调用
__eq__ : obj1 == obj2 obj1.__eq__(obj2)
__len__:len(obj)
__hash__:# 一种算法,把一个对象转换成一个数字
字典的一次寻址
set去重
模块
三种:
内置模块
第三方模块
自定义模块
序列化模块 把数据类型 --> str、bytes
数据的持久化存储 :文件存储
数据的网络传输
json
所有语言都支持
支持的数据类型有限
pickle
只有python语言支持
支持几乎所有的数据类型
时间模块
import time
- 时间模块
- 三种格式
- 时间戳时间 浮点数 秒为单位
1970.1.1 0:0:0 英国伦敦时间 - 结构化时间 元组
- 格式化时间 str数据类型的
- 时间戳时间 浮点数 秒为单位
import time
print(time.localtime()) #显示结构化时间 得到的是北京时间 开始于1970-1-1 8:0:0
print(time.gmtime()) #得到的是伦敦时间 开始于1970-1-1 0:0:0
print(time.time()) #显示时间戳
print(time.strftime('%Y-%m-%d %X')) #显示格式化时间
print(time.strptime('2018-8-8','%Y-%m-%d')) # 转化格式化时间到结构化时间
计算本月1号的时间戳时间
import time
def get_timestamp():
time1 = time.strftime('%Y-%m-1')
time2 = time.strptime(time1,'%Y-%m-%d')
time3 = time.mktime(time2)
return time3
print(get_timestamp())
随机数模块
import random
- 取随机小数
print(random.random()) #(0,1) 默认0到1之间
print(random.uniform(2,3)) #(n,m) n 到 m 之间
-
取随机整数
print(random.randint(1,2)) # [1,2]
print(random.randrange(1,2)) # [1,2)
print(random.randrange(1,100,2)) -
从一个列表中随机抽取
print(random.choice(list))
print(random.choice(range(100)))
print(random.sample(lst,3)) #在列表中随机去除3个数
-
乱序
lst = [1,2,3,4,5,('a','b'),'cc','dd']
random.shuffle(lst)
print(lst)
随机生成n位数的数字验证码
def get_code(n):
code = ''
for i in range(n):
num = random.randint(0,9)
code += str(num)
return code
print(get_code(6))
65-90 A-Z 字符编码数 chr() 内置函数
97-122 a-z
生成6位验证码
def get_code(n=6):
code = ''
for i in range(n):
num = str(random.randint(0,9))
alpha_upper = chr(random.randint(65, 90))
alpha_lower = chr(random.randint(97, 122))
c = random.choice([num,alpha_upper,alpha_lower])
code += c
return code
进阶
def get_code(n = 6 ,alph_flag = True):
code = ''
for i in range(n):
c = str(random.randint(0,9))
if alph_flag:
alph_A = chr(random.randint(65,90))
alph_a = chr(random.randint(97,122))
c = random.choice([c,alph_A,alph_a])
code += c
return code
print(get_code(alph_flag=False))
OS模块
- 创建
os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname
os.makedirs('dirname1/dirname2') 可生成多层递归目录 - 删除
os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.stat('path/filename') 获取文件/目录信息
stat 结构:
st_mode: inode 保护模式
st_ino: inode 节点号。
st_dev: inode 驻留的设备。
st_nlink: inode 的链接数。
st_uid: 所有者的用户ID。
st_gid: 所有者的组ID。
st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。
st_atime: 上次访问的时间。
st_mtime: 最后一次修改的时间。
st_ctime: 由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。
os.path.abspath(path) 返回path规范化的绝对路径 path可以是相对如今也可以是绝对路径,相对路径要在当前目录下
os.path.split(path) 将path分割成目录和文件名二元组返回
os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path) 返回path最后的文件名。如果path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) 如果path是绝对路径,返回True
os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 可能和os.path.abspath 拼接完再规范化
os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间
os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间
os.path.getsize(path) 返回path的大小(文件大小)
os.system("bash command") # - 以字符串的形式来执行操作系统的命令 运行shell命令,直接显示
exec - 以字符串的形式来执行python代码
os.popen("bash command).read() 运行shell命令,获取执行结果
eval 以字符串的形式来执行python代码 且返回结果
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
print(os.getcwd())
open('file','w').close() # 文件在执行这个文件的目录下创建了
不是当前被执行的文件所在的目录,而是执行这个文件所在的目录
工作目录在哪儿,所有的相对目录文件的创建,都是在哪儿执行这个文件,就在哪儿创建
os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd
改变os.getcwd() 操作系统特性
os.chdir('D:\骑士计划PYTHON1期\day23')
open('file3','w').close()
print('-->cwd : ',os.getcwd())
print(__file__) #当前文件的绝对路径
- import sys
- sys模块是与python解释器交互的一个接口
- sys.argv
- sys.path
- sys.modules
sys.modules 查看当前内存空间中所有的模块,和这个模块的内存空间
print(sys.path)
一个模块能否被导入,就看这个模块所在的目录在不在sys.path路径中
内置模块和第三方扩展模块都不需要我们处理sys.path就可以直接使用
自定义的模块的导入工作需要自己手动的修改sys.path
- print(sys.argv) # python D:/骑士计划PYTHON1期/day23/6.sys模块.py
总结:
time模块
时间戳格式 浮点型
结构化时间 元组
格式化时间 字符串
'%Y %m % d %H %M %S'
时间戳时间 -localtime/gmtime-> 结构化时间 -strftime-> 格式化时间
时间戳时间 <-mktime- 结构化时间 <-strptime- 格式化时间
random模块
random.random 随机小数(0,1)
random.uniform 随机小数(n,m)
random.randint 随机整数 [n,m]
random.randrange(start,end,step) 随机整数 [n:m:s)
random.choice(list/range/tuple/str) 随机抽取一个值
random.sample(list/range/tuple/str,n) 随机抽取n个值
random.shuffle(list) 乱序,在原有基础上乱序
os 和操作系统交互
文件和文件夹的操作
os.remove
os.rename
os.mkdir
os.makedirs
os.rmdir
os.removedirs
os.listdir
os.stat 获取文件的信息
路径的操作
os.path.join 目录的拼接
os.path.split(path) # 将路径分割成两个部分,目录、文件/文件夹的名字
os.path.dirname(path) # 返回这个path的上一级目录
os.path.basename(path) # 文件/文件夹的名字
os.path.exits 这个路径是否存在
os.path.isfile 是否文件目录
os.path.isdir 是否文件夹目录
os.path.abspath 规范文件目录、返回一个绝对路径
os.path.getsize
和python程序的工作目录相关的
getcwd # 获取当前的工作目录 get current working dir
chdir # 改变当前的工作目录 change dir
执行操作系统命令
os.system(命令)
os.popen(命令).read()
file文件中的一个内置变量,描述的是这个文件的绝对路径
sys 和python解释器
sys.argv 执行py文件的时候传入的参数
sys.path 查看模块搜索路径 import 模块的时候从这个路径下来寻找
sys.modules 查看当前导入的模块和它的命名空间
collections
-
内置的数据类型
int float complex
str list tuple
dict set -
基础数据类型
int float complex
str list tuple
dict -
collections模块
根据基础数据类型又做了一些扩展
有序字典 py3.6以后自动有序
Counter 计数器(计算字符串单字符个数返回一个字典)
默认字典
可命名元组
双端队列
from collections import Iterable
from collections import Iterator
- d = dict([('a',1),('k1','v1')]) #创建字典的方法
from collections import OrderedDict
dd = OrderedDict([('a',1),('k1','v1')]) #创建有序字典 插入的时候默认在末尾,,默认有序
from collections import defaultdict #默认字典 { defaultdict(lambda : 默认值) }
d = defaultdict(可调用的) #d是一个默认字典 如果是list,可以直接append ,也可以和匿名函数结合使用
from collections import defaultdict
values = [11, 22, 33,44,55,66,77,88,99,90]
my_dict = defaultdict(list)
for value in values:
if value>66:
my_dict['k1'].append(value)
else:
my_dict['k2'].append(value)
- namedtuple
from collections import namedtuple #可命名元祖 不能用索引方式访问
birth = namedtuple('Struct_time',['year','month','day']) #'Struct_time是一个类名
b1 = birth(2018,9,5) #可看作实例化对象 ,结构化时间就是一个可命名元祖
可命名元组非常类似一个只有属性没有方法的类的对象
这个类最大的特点就是一旦实例化 不能修改属性的值,安全性能好
定义一个自由属性没有方法的类就可以用 namedtuple
-
双端队列
- list的缺点双端队列可以弥补
from collections import deque
dq = deque() #dq就是一个双端队列
dq.append(1) #左加
dq.appendleft(3) #右加
print(dq) #deque([3, 1])
print(dq.pop()) #1
print(dq.popleft()) #3 内部机制比列表快
- list的缺点双端队列可以弥补
-
队列
import queue
q = queue.Queue() # 队列 先进先出
q.put(1) #进
print(q.get()) #出
hashlib模块
hashlib
登录验证 md5、sha - 动态加盐
文件的一致性校验 md5 - 不需要加盐
- 算法不可逆
- 不同的字符串通过这个算法的计算得到的密文总是不同的
- 相同的算法 相同的字符串 获得的结果总是相同的
- 不同的语言 不同的环境(操作系统、版本、时间)
hashlib 摘要算法
多种算法
md5算法 :32位16进制的数字字符组成的字符串
应用最广大的摘要算法
效率高,相对不复杂,如果只是传统摘要不安全 #撞库
sha算法 :40位的16进制的数字字符组成的字符串
sha算法要比md5算法更复杂
且shan n的数字越大算法越复杂,耗时越久,结果越长,越安全
转化密文函数
def get_md5(a):
import hashlib
md5_obj = hashlib.md5()
md5_obj.update(a.encode('utf-8'))
return md5_obj.hexdigest()
- 加盐和动态加盐,安全性能提高
- 动态加盐
每一个用户创建一个盐 - 用户名
- 动态加盐
import hashlib
def get_md5(a,b):
md5_obj = hashlib.md5(a.encode('utf-8'))
md5_obj.update(b.encode('utf-8'))
ret = md5_obj.hexdigest()
return ret
def login(file_name):
while 1:
inp_name = input('请输入你的用户名:').strip()
if inp_name == '':continue
while 1:
inp_pwd = input('请输入你的密码:').strip()
if inp_pwd == '':continue
else:break
with open(file_name,encoding='utf-8') as f:
for line in f:
name,pwd = line.strip().split('|')
if name == inp_name and pwd == get_md5(inp_name,inp_pwd):
print('登录成功')
return True
else:print('登录失败!')
login('hqs.txt')
- 登录验证 - hashlib
- 两种算法 md5,sha
- 常规验证 - 撞库
- 加盐 - 固定的盐 会导致恶意注册的用户密码泄露
- 动态加盐 - 每个用户都有一个固定的并且互不相同的盐
- 文件的一致性校验
- 给一个文件中的所有内容进行摘要算法得到一个md5的结果
import hashlib
md5_obj = hashlib.md5()
md5_obj.update('hello,world'.encode('utf-8'))
ret1 = md5_obj.hexdigest()
print(ret1)
md5_obj = hashlib.md5()
md5_obj.update('hello'.encode('utf-8'))
md5_obj.update(',world'.encode('utf-8'))
ret2 = md5_obj.hexdigest()
print(ret2)
# 结论:ret1 == ret2
文件的校验
# 一次性读取
import hashlib
def get_md5(file_name):
with open(file_name,'rb') as f:
md5_obj1 = hashlib.md5()
md5_obj1.update(f.read())
return md5_obj1.hexdigest()
#分段读取
import os
import hashlib
def get_file_md5(file_name,butter = 1024):
md5_obj = hashlib.md5()
file_size = os.path.getsize(file_name)
with open(file_name,'rb') as f:
while file_size:
count = f.read(1024)
md5_obj.update(count)
file_size -= len(count)
return md5_obj.hexdigest()
import hashlib
def get_file_md5(filename):
md5_obj = hashlib.md5()
with open(filename,'rb') as f:
for line in f:
md5_obj.update(line)
return md5_obj.hexdigest()
configparser
处理配置文件的模块
1 开发环境
2 测试环境
3 生产环境
- 创建文件
import configparser
config = configparser.ConfigParser() #config相当于一个对象/字典
config["DEFAULT11111"] = {'ServerAliveInterval': '45',
'Compression': 'yes',
'CompressionLevel': '9',
'ForwardX11':'yes'
}
config['bitbucket.org'] = {'User':'hg'}
config['topsecret.server.com'] = {'Host Port':'50022','ForwardX11':'no'}
with open('example.ini', 'w') as configfile:
config.write(configfile)
- 查找文件
- 增删改操作
logging 模块
- 日志:
无处不在的日志(显示和写入文件都属于日志)
所有的程序都必须记录日志
logging
日志模块
import logging
logging.basicConfig(level=logging.INFO)
logging.debug('debug message') # 计算或者工作的细节
logging.info('info message') # 记录一些用户的增删改查的操作
logging.warning('input a string type') # 警告操作
logging.error('error message') # 错误操作
logging.critical('critical message') # 批判的 直接导致程序出错退出的
- 简单配置,只能写或者显示
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%c',
filename='test.log') #filename='test.log' 配置写入文件或者显示
logging.warning('input a string type') # 警告操作
logging.error('EOF ERROR ') # 警告操作
logging.info('小明买了三斤鱼') # 警告操作
- 对象的配置
解决中文问题
同时向文件和屏幕输出内容
import logging
# 先创建一个log对象 logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) #改变默认的INFO 是输入输出都是DEBUG以下
# 还要创建一个控制文件输入的文件操作符
fh = logging.FileHandler('mylog.log')
# 还要创建一个控制屏幕输出的屏幕操作符
sh = logging.StreamHandler()
# 要创建一个格式
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fmt2 = logging.Formatter('%(asctime)s - %(name)s[line:%(lineno)d] - %(levelname)s - %(message)s')
# 文件操作符 绑定一个 格式
fh.setFormatter(fmt)
# 屏幕操作符 绑定一个 格式
sh.setFormatter(fmt2)
sh.setLevel(logging.WARNING) #屏蔽掉WARNING上的屏幕输出
# logger对象来绑定:文件操作符, 屏幕操作符
logger.addHandler(sh)
logger.addHandler(fh)
logger.debug('debug message') # 计算或者工作的细节
logger.info('info message') # 记录一些用户的增删改查的操作
logger.warning('input a string type') # 警告操作
logger.error('error message') # 错误操作
logger.critical('critical message') # 批判的 直接导致程序出错退出的
logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,可以通过:logger.setLevel(logging.Debug)设置级别,当然,也可以通过----fh.setLevel(logging.Debug)单对文件流设置某个级别
模块和包
模块
import
- 在import的过程中发生了哪些事情
- 被导入的模块和本文件之间命名空间的关系
- import 多个模块
- 给导入的模块起别名 (as) --- > 给模块起别名 原名就不能用
- 模块搜索路径
- 模块和脚本
import
import一个模块相当于执行了这个模块
在import模块的时候发生的事情
1.寻找模块
2.如果找到了,就开辟一块儿空间,执行这个模块
3.把这个模块中用到的名字都收录到新开辟的空间中
4.创建一个变量来引用这个模块的空间
- 1.模块不会被重复导入
- 2.模块和文件之间的内存空间始终是隔离的
- 3.模块的名字必须是符合变量命名规范的
模块搜索路径
所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
正常的sys.path中出了内置、扩展模块所在的路径之外
只有一个路径是永远不会出问题
你直接执行的这个文件所在的目录
一个模块是否能被导入,就看这个模块所在的目录在不在sys.path中
两种运行一个py文件的方式
直接运行它 : cmd python xx.py pycharm 脚本
__name__ == '__main__'
导入它 : 模块
__name__ == '模块名
from import
在from import的过程中发生了哪些事情
被导入的内容和本文件之间命名空间的关系
from import 多个内容
给导入的内容起别名
from ... import *
* 和 __all__
1.pyc文件、pyi文件
pyc只能提高程序的启动效率并不能提高程序的执行效率
2.模块的导入和修改
3.模块的循环引用 不能循环 只能单向
4.dir(模块名) dir(list) dir(str) iter
可以获取这个模块中的所有名字(列表你显示字符串,反射)
包
什么是包 package
含有一个init.py的文件夹就是一个包
包中通常含有一些py文件
从包中导入模块
import 包.包.模块
import 包.包.模块.属性/方法
1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
-
包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含init.py文件的目录)
-
import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的init.py,导入包本质就是在导入该文件
强调:
1. 在python3中,即使包下没有init.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
- 创建目录代码
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)
特别需要注意的是:可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式
直接导入包
绝对导入
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
│ ├── __init__.py from glance.cmd import manage
│ └── manage.py
└── db
├── __init__.py from glance.db import models
└── models.py
glance2.api.policy.get()
导入包的过程中发生了什么事?
相当于执行了这个包的 init.py文件
sys.path中的内容 永远是当前你执行的文件
['D:\骑士计划PYTHON1期\day26']
相对导入
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
│ ├── __init__.py from . import manage
│ └── manage.py from ..api import policy
#..表示上一级目录,想再manage中使用policy中的方法就需要回到上一级glance目录往下找api包,从api导入policy
└── db from . import models
├── __init__.py
└── models.py
运用了相对导入的文件不能被直接执行
'.'表示当前目录
'..'表示上一级目录
import glance3
glance/
├── __init__.py from .api import *
from .cmd import *
from .db import *
├── api
│ ├── __init__.py __all__ = ['policy','versions']
│ ├── policy.py
│ └── versions.py
├── cmd
│ ├── __init__.py __all__ = ['manage']
│ └── manage.py
└── db
├── __init__.py __all__ = ['models']
└── models.py
import glance
policy.get()