Week 6
本周学习主要内容包括柯里化,装饰器(带参+不带参),文件操作第一部分
柯里化
- 指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数
- EX: z = f(x,y)转换成z=f(y)的过程
无参装饰器
@标识符
-
标识符指向一个函数,用一个函数来装饰他下面的函数,logger函数称为装饰器函数,add成为被装饰或被包装函数
-
logger习惯上称为wrapper;add习惯上称为wrapped
-
本质上来看,无参装饰器 logger实际上是等效为一个参数的函数
-
无参装饰器 logger
@logger 会把它下面紧挨着的函数的标识符(或者类)提上来作为他的实参 xyz = logger(xyz) -
等价式非常重要!!如果不能理解装饰器,开始的时候一定要把等价式写在后面!!
@logger #等价于 add = wrapper <=> add = logger(add)
def add(x,y):
return x+y
print(add(100,200)
总结
- 上面的装饰器语法称为无参装饰器
- @符号后是一个函数
- 虽然是无参装饰器,但@后的函数本质上是一个单参函数
- 上例中logger函数是一个高阶函数
第一个装饰器务必背过:
import datetime
import time
def logger(fn): #此装饰器为所学的第一个装饰器,请务必背过!!
def wrapper(*args,**kwargs):
print('调用前增强功能')
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
print('调用后增强功能')
delta = (datetime.datetime.now()-start).total_seconds()
print('Function {} took {}s.'.format(fn.__name__,delta))
return ret
return wrapper
@logger #add = logger(add) #@logger称为无参装饰器,add = wrapper
def add(x,y):
time.sleep(2)
return x+y
add(4,5)
装饰器本质:
- 习惯上add函数被称为被包装函数(wrapped),增强他的函数成为包装器或包装函数(wrapper)
- 包装的目的是增强,而不是破坏原函数,采用非侵入式代码
- 可以在原函数前或后加入增强代码
- 原函数外可以更换装饰器,而原函数不变;同个装饰器可以用在不同函数外;同一个函数可以嵌套多个装饰器
带参装饰器
文档字符串
- Documentation Strings
- 在函数(类、模块)语句块的第一行,且习惯是多行的文本,所以用三引号
- 文档字符串也是合法的一条语句
- 惯例首字母大写,第一行写概述,空一行,第三行写详细描述
- 可以使用特殊属性__doc__访问这个文档
问题:使用装饰器后,发现原函数的函数名和文档都变了。
函数也是对象,特殊属性也是属性,可以被覆盖,现在访问add函数实际上是访问装饰器wrapper函数。
解决方案:使用原来定义的add函数的名称和文档属性覆盖wrapper函数的对应属性
带参装饰器
import datetime
import time
def copy_properties(dst):
def _copy(src): #柯里化
dst.__name__ = src.__name__ #闭包
dst.__doc__ = src.__doc__
#其他属性覆盖略去
return dst #这一句返回值特别重要
return _copy
def logger(duration): #fn 被包装函数wrapped
def _logger(fn):
@copy_properties(fn) #带参装饰器 #wrapper = copu_properties(fn)(wrapper)
#copy_properties(fn)(wrapper) -> _copy(wrapper)
def wrapper(*args,**kwargs): #包装函数wrapper
"""Wrapper function +++"""
start = datetime.datetime.now()
ret = fn(*aargs,**kwargs) #参数解构
delta = (datetime.datetime.now()-start).total_seconds()
duration = 5
if delta>duration: #s
print("Function {} took {}s.Slow".format(fn.__name__,delta))
else:
print("Function {} took {}s.Fast".format(fn.__name__,delta))
return ret
#wrapper = copy_properties(fn)(wrapper) #_copy(fn)
return wrapper
return _logger
像@copy_properties(fn)这种在装饰器后面跟着参数的装饰器称为带参装饰器
属性更新
copy_properties是通用功能,标准库中functools已经提供了
fuctools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, update=WRAPPER_UPDATES)
- 类似copy_properties功能
- wrapper是包装函数、被更新者,wrapped是被包装函数、数据源
- 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性,有模块名__module__,名称__name__,限定名__qualname__,文档__doc__,参数注解__annoatations__
- 元组WAPPER_UPDATES中是要被更新的属性,__dict__属性字典
- 增加一个__wrapped__属性,保留着wrapped函数
总结
- @之后不是一个单独的标识符,是一个函数调用
- 函数调用的返回值又是一个函数,此函数是一个无参装饰器
- 带参装饰器,可以有任意个参数:@func();@func(1);@func(1,2) ……
进阶*
import time
from functools import wraps
#wraps(fn)(wrapper) => update_wrapper(wrapper,fn) => wrapper
def logger(fn): #logger全局标识符,一个函数对象
@wraps(fn) # wrapper.__name__ = fn.__name__ wrapper = wraps(fn)(wrapper) => wrapper = wrapper
def wrapper(*args,**kwargs): #logger执行时,临时创建内部函数对象,如同局部变量一样
"""Wrapper function +++"""
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now()-start).total_seconds
print(delta)
return ret
return wrapper
@logger #add = logger(add) => add=wrapper1(局部变量) 此wrapper非彼wrapper
def add(x,y): #内存中产生一个全局对象add 函数对象
"""Add function ~~~"""
time.sleep(2)
return x+y
@logger #sub = logger(sub) => sub = wrapper2(局部变量) 函数每次调用之间没什么关系
def sub(x,y):
return x-y
print(add.__name__,add.__doc__)
print(sub.__name__,sub.__doc__)
#add函数,sub函数执行过吗? -- 没有 除非有add() / sub()
#logger什么时候执行? -- 只要走过@logger即执行
#logger执行过几次? -- 2次 两次调用无关,两次生成的wrapper都无关(wrapper1 wrapper2),各自有闭包
#wraps装饰器执行过几次? -- 两次
#wrapper的__name__等属性被覆盖过几次? -- 两次(各是各的wrapper 各是各的fn 各覆盖各的)
#add.__name___打印什么名称? -- add
#sub.__name___打印什么名称? -- sub
文件操作
冯诺依曼体系架构
- CPU:由运算器和控制器两部分组成;
- 运算器:完成各种算术运算、逻辑运算、数据传输等数据加工处理;
- 控制器:控制计算机各部件协调运行;
- 存储器:用于记忆程序和数据,例如内存;
- 输入设备(I):将数据或程序输入到计算机中,例如鼠标、键盘;
- 输出设备(O):将数据或程序的处理结果展示给用户,例如显示器、打印机……
一般说IO操作指的是文件IO,如果说网络IO都直接说网络IO
磁盘目前依然是文件持久化最重要的设备
磁盘基础原理
现代磁盘使用温彻斯特磁盘驱动器
- 运行时磁头摆动,从飞速旋转的盘片上读取或写入数据
- 常见转速为5400转/min、7200转/min
- 磁头摆动定位数据过程为寻道;转速、寻道都影响磁盘读写性能
- 磁头和盘片不接触,磁盘内部不真空,磁头悬浮在磁盘上放,一组磁头从0编号
- 盘片(Platter):涂满磁性材料的盘片,早期金属现在多为玻璃
- 磁道(Track):盘片划分同心圆,同心圆间距越小一般来说存储容量越大;磁道编号由外向内从0开始
- 扇区(Sector):一个磁道上能存储的0或1信号太多,所以需要进一步划分成若干弧段,每一弧段就是一个扇区。通常一个扇区512字节,每个扇区都有自己的编号
- 柱面(Clinder):从垂直方向看,磁道的同心圆就构成了圆柱,编号由外向内从0开始
- 盘片内圈扇区面积小,外圈面积大,但扇区都是512字节;外圈磁密度低,内圈磁密度高
- 簇(Cluster):操作系统为了优化IO,使用磁盘空间时最小占用的多个扇区。一般簇大小为4 ~ 64个扇区(2KB ~ 32KB)。所以一个文件就有了物理占用空间大小即实际分得的磁盘空间,和逻辑占用大小即文件本身的实际字节数。往往实际占用空间都比文件本身大。越是小文件越浪费空间。
注解 annotation
- 动态语言中变量可以随时被赋值并改变类型,Python的变量是运行时决定的。
- 动态语言缺点:
- 难发现:由于不能做任何类型检查,往往到了运行时问题才显现,或线上运行时暴露
- 难使用:函数使用者看到函数时并不知道其设计者意图,如果没有详尽文档使用者只能猜测数据类型,因而使用会极其困难
- 解决方式:
- 文档字符串,对函数、类、模块使用详尽的使用描述、距离,让使用者使用帮助就能知道使用方式,但大多数项目管理不严或项目变动,导致文档不全或没有更新
- 类型注解:函数注解、变量注解
函数注解
- 对函数的形参和返回值类型的说明
- 只是对函数形参类型,返回值类型做的辅助说明,非强制类型约束
- 第三方工具,如Pycharm就可以根据类型注解进行代码分析发现隐藏bug
- 函数注解存放在函数属性__annotations__中,字典类型
类型注解
- 对变量类型的说明,非强制约束
- 第三方工具可以进行类型分析和推断
类型检查
- 可以在函数内部写isinstance来判断参数类型(写入就是侵入式代码)
- 使用函数属性__annotations__但其类型为字典,无序
- 为了更灵活地检查:
- 非侵入式代码
- 动态获取待检查的函数的参数类型注解
- 当函数调用传入实参时,和类型注解比对
inspect 模块
import inspect
inspect.isfunction(add) #是否是函数
inspect.ismethod(pathlib.Path().absolute) #是否是类的方法,要绑定
inspect.isgenerator(add) #是否是生成器对象
inspect.isgeneratorfunction(add) #是否是生成器函数
inspect.isclass(add) #是否是类
inspect.ismodule(inspect) #是否是模块
inspect.isbuiltin(print) #是否是内建对象
-
inspect.signature(callable,*,follow_wrapped=True)
获取可调用对象的签名
增加follow_wrapped,如果使用fuctools的wraps或update_wrapper,follow_wrapped为True跟进被包装函数的__wrapped__,即获取真正的被包装函数的签名 -
inspect.Parameter:
- 4个属性:
- name,参数名,字符串
- default,缺省值
- annotation,类型注解
- kind,类型
- 4个属性:
- POSITIONAL_ONLY,只接受位置传参
- POSITIONAL_OR_KEYWORD,可以接受关键字或位置传参
- VAR_POSITIONAL,可变位置参数,对应*args
- KEYWORD_ONLY,对应或args之后出现的非可变关键字形参,只接受关键字传参
- VAR_KEYWORD,可变关键字传参,对应**kwargs
- empty,特殊类,用来标记default属性或者annotation属性为空