序列化模块
序列
- 列表
- 元组
- 字符串
- 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
dictcollections模块
根据基础数据类型又做了一些扩展
有序字典 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()