高阶函数
- 数学概念:y=g(f(x))
- 高阶函数应当满足以下条件之一
- 接受一个或多个函数作为参数
- 输出一个函数
- Python函数
- 函数在Pyhon中是一等公民( First Class Object)
- 函数也是对象 可调用的对象
- 函数可做为普通变量 参数 返回值等等
def counter(base):
def inc(step=1):
nonlocal base
base += step
return base
return inc
调用该函数
f1=counter(3)
f1(1) --> 4
f1(1) --> 5
f1(1) --> 6
以排序函数为例 演示高阶函数
def sort(iterable,key):
lst=[]
for x in iterable:
for i,y in enumerate(iterable):
if y < x :
lst.insert(i,y)
else:
lst.append(y)
return lst
内建高阶函数
- sorted(iterable[, key][, reverse])
- 排序 输入可迭代序列 输出升序序列 key为自定义函数 将可迭代序列代入自定义函数 输出自定义函数结果的排序
- filter(function, iterable) –> filter object
- 过滤数据 输入fn和可迭代序列 输出fn结果为True的元素组成的序列
- map(func, *iterables) –> map object
- 映射 输入可迭代序列和key key为自定义函数 将可迭代序列代入自定义函数 输出自定义函数结果组成的序列
sorted([-1,67,566,0.5],key=lambda x:-x)
list(filter(lambda x: x%3==0, [1,9,55,150,-3,78,28,123]))
list(map(lambda x:2*x+1, range(5)))
柯里化 Currying
- 将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数
- z = f(x, y) 转换成 z = f(x)(y)的形式
- 加法函数柯里化
def add(x, y):
return x + y
#调用:
add(1,2)
def addK(x):
def _a(y):
return x + y
return _a
#调用:
addK(1)(2)
装饰器
- 假设有如下需求:
- 增强某个函数的功能但不修改原有函数
- 实现方法:新建函数 加入增强功能 同时调用原有函数
- 增进建议:将原有函数放入增强函数的参数中 实现更灵活的调用
def add(x,y): # 原有函数
return x + y
def logger(fn): # 增强函数
print('begin') # 增强的输出
x = fn(4,5)
print('end') # 增强的输出
return x
print(logger(add))
- 如何实现add函数自由传参
def logger(fn): # 增强函数
def wrapper(*args,**kwargs):
print('begin') # 增强的输出
x = fn(*args,**kwargs)
print('end') # 增强的输出
return x
return wrapper
def add(x,y): # 原有函数
return x + y
add_improved=logger(add)
'''
原有函数对象add传入logger函数被fn收集 并命名为add_improved
调用add_improved函数 相当于从logger内部wrapper开始
add_improved的实参被args和kwargs收集
先执行第一条打印语句
fn指向原有函数add 实参被args和kwargs收集后在此处被解构并传给fn 此行相当于执行原有函数
最后执行第二条打印语句
最后返回fn函数的执行结果
'''
print(add_improved(4,5))
- add_improved=logger(add)可否直接改为add=logger(add) ?
- 下次直接调用add 就像调用了增强函数一样
- 原有add函数被覆盖了不是就不能用了吗?
- 分析 : 执行add=logger(add)语句.原有函数对象add传入logger函数,被fn收集.logger函数内部通过fn还可调用原有函数.并不影响.
- 既然add=logger(add)可以使用 那可否更简练?
def logger(fn): # 增强函数
def wrapper(*args,**kwargs):
print('begin') # 增强的输出
x = fn(*args,**kwargs)
print('end') # 增强的输出
return x
return wrapper
@logger #等价于add=logger(add)
def add(x,y): # 原有函数
return x + y
print(add(4,5)) --> 9
- @logger 就是装饰器语法
- 装饰器是高阶函数,是对传入函数的功能的装饰或增强
文档字符串 Documentation Strings
- Python函数内部的说明文档
- 位于函数语句块内部 使用三引号
- 可使用特殊属性__doc__访问这个文档
def logger(fn): # 增强函数
def wrapper(*args,**kwargs):
""" wrapperdoc """
print('begin') # 增强的输出
x = fn(*args,**kwargs)
print('end') # 增强的输出
return x
return wrapper
@logger #等价于add=logger(add)
def add(x,y): # 原有函数
""" adddoc """
return x + y
print(add.__name__,add.__doc__) --> wrapper wrapperdoc
**logger将原有函数指向了内部的wrapper 因此执行被logger装饰后的add函数实际是执行wrapper函数 因此原始函数的一些属性就看不到了
一般情况下我们更想查看原有函数的属性 这时装饰器就有了副作用 怎么解决?**
1. 写补丁 把原有函数的属性复制回来
def copy_properties(src, dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
def logger(fn): # 增强函数
def wrapper(*args,**kwargs):
""" wrapperdoc """
print('begin') # 增强的输出
x = fn(*args,**kwargs)
print('end') # 增强的输出
return x
copy_properties(fn,wrapper)#注意此处的参数 功能是将第一个位置函数的属性复制给第二个位置的函数
return wrapper
@logger #等价于add=logger(add)
def add(x,y): # 原有函数
""" adddoc """
return x + y
print(add.__name__,add.__doc__) --> add adddoc
- 这个补丁可不可以写成装饰器呢?
先想一下之前的装饰器是如何实现的
add_improved=logger(add)
add_improved(2,3)
其实就是logger(add)(2,3)
我们的目标是 copy_properties(fn,wrapper) 改成 copy_properties(fn)(wrapper)
这和之前提到的高阶函数和柯里化有关系
def copy_properties(src): # 柯里化
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #难点
return _copy
def logger(fn): # 增强函数
@copy_properties(fn) #等价于wrapper=copy_properties(fn)(wrapper) 因此@后面的函数定义应该写在此句前面
def wrapper(*args,**kwargs):
""" wrapperdoc """
print('begin') # 增强的输出
x = fn(*args,**kwargs)
print('end') # 增强的输出
return x
return wrapper
@logger #等价于add=logger(add)
def add(x,y): # 原有函数
""" adddoc """
return x + y
print(add.__name__,add.__doc__) --> add adddoc
带参装饰器
- 新的需求 : 获取函数的执行时长,对时长超过阈值的函数记录一下
业务分析 : 先对原始函数装饰 装饰器函数内记录原始函数执行时间 并与给定阈值作比较
与上边的add_improved相比 需要多传入阈值参数
def logger(threshold, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
def _logger(fn): # 增强函数
def copy_properties(src): # 柯里化
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #难点
return _copy
@copy_properties(fn) #等价于wrapper=copy_properties(fn)(wrapper)
def wrapper(*args,**kwargs):
""" wrapperdoc """
import datetime
start = datetime.datetime.now()
x=fn(*args,**kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
if delta > threshold
func(fn.__name__,threshold)
return x
return wrapper
return _logger
@logger(0.5) #等价于add=logger(0.5)(add) 注意阈值实参在此处传入
def add(x,y): # 原有函数
""" adddoc """
return x + y
functools模块
import functools
functools.update_wrapper?
Signature: functools.update_wrapper(wrapper, wrapped, assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'), updated=('__dict__',))
Docstring:
Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
从以上文档可以看出
wrapper是指要被更新或增强的函数 即装饰器函数
wrapped就是指被包装的函数 即原始函数
WRAPPER_ASSIGNMENTS中是要被更新的属性
包括 ‘module‘, ‘name‘, ‘qualname‘, ‘doc‘, ‘annotations’ 模块名、名称、限定名、文档、参数注解
用法如下
import datetime,functools
def logger(threshold):
def _logger(fn): # 增强函数
def wrapper(*args,**kwargs):
""" wrapperdoc """
start = datetime.datetime.now()
x=fn(*args,**kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print('{}>{}'.format(threshold , delta) if threshold > delta else '!>')
return x
functools.update_wrapper(wrapper, fn)
return wrapper
return _logger
@logger(0.5) #等价于add=logger(threshold)(add)
def add(x,y): # 原有函数
""" adddoc """
return x + y
'''
导入functools模块以后 在wrapper函数末尾加上更新属性的函数语句
'''
- 装饰器版本
- @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS,
updated=WRAPPER_UPDATES)
import datetime,functools
def logger(threshold):
def _logger(fn): # 增强函数
@functools.wraps(fn) #等价于wrapper=functools.wraps(fn)(wrapper)
def wrapper(*args,**kwargs):
""" wrapperdoc """
start = datetime.datetime.now()
x=fn(*args,**kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print('{}>{}'.format(threshold , delta) if threshold > delta else '!>')
return x
return wrapper
return _logger
@logger(0.5) #等价于add=logger(0.5)(add)
def add(x,y): # 原有函数
""" adddoc """
return x + y
'''
@functools.wraps(fn) 等价于wrapper=functools.wraps(fn)(wrapper)
因此写在紧挨着wrapper函数的上面 且在fn关键字下面
函数注解Function Annotations
- 函数注解
- def add(x:int , y:int) -> int :
- Python3.5引入
- 对参数以及返回值进行类型注解
- 只是对参数的辅助说明,不对参数进行类型检查
- 函数注解信息保存于annotation属性中
- 主要用于代码分析 发现隐藏的bug
- 变量注解
- i:int = 3
- Python3.6引入
def add(x:int , y:int) -> int :
return x + y
add.__annotations__
{'return': int, 'x': int, 'y': str}
- _annotations_属性是一个字典,其中包括返回值以及形参的类型声明。假设要做位置参数的判断,无法和字典中的声明对应。需使用inspect模块
inspect模块
- 获取对象信息的函数,可检查函数和类 类型检查
- inspect.signature(callable)
import inspect,functools
p=functools.partial(print,sep=' +++ ') #偏函数 用于固定函数的某些参数
def add(x:int, y:int, *args,**kwargs) -> int:
return x + y
sig = inspect.signature(add)
p(sig, type(sig))
'''(x:int, y:int, *args, **kwargs) -> int +++ <class 'inspect.Signature'>
函数签名 (x:int, y:int, *args, **kwargs) -> int 类型为 <class 'inspect.Signature'>'''
p(sig.parameters, sig.return_annotation)
'''OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">),
('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)]) +++ <class 'int'>
函数签名的parameters属性返回的是一个有序字典 内部是由Key,Value组成的二元组 以key值排序
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">),
('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
返回值注解为整型类 <class 'int'> '''
p(sig.parameters['y'], type(sig.parameters['y']), sig.parameters['y'].annotation)
'''y:int +++ <class 'inspect.Parameter'> +++ <class 'int'>
有序字典也可利用key查询对应值 参数y对应类型为<class 'inspect.Parameter'> 对应注解为<class 'int'>'''
p(sig.parameters['args'],sig.parameters['args'].annotation)
'''*args +++ <class 'inspect._empty'>
可变参数也在此处查询 对应注解为空 <class 'inspect._empty'>'''
p(sig.parameters['kwargs'],sig.parameters['kwargs'].annotation)
'''**kwargs +++ <class 'inspect._empty'>
可变关键字参数也在此处查询 对应注解为空 <class 'inspect._empty'>'''
- 函数签名的parameters属性返回的是一个有序字典 内部是由Key,Value组成的二元组 以key值排序. 二元组包括形参以及可变参数
- inspect.is**(obj)
inspect.isfunction(add),是否是函数
inspect.ismethod(add)),是否是类的方法
inspect.isgenerator(add)),是否是生成器对象
inspect.isgeneratorfunction(add)),是否是生成器函数
inspect.isclass(add)),是否是类
inspect.ismodule(inspect)),是否是模块
inspect.isbuiltin(print)),是否是内建对象
import inspect
def add(x, y:int=7, *args, z, t=10,**kwargs) -> int:
return x + y
sig = inspect.signature(add)
print(sig)
print('params : ', sig.parameters) # 有序字典
print('return : ', sig.return_annotation)
函数签名的parameters属性返回的是一个有序字典
params : OrderedDict([(‘x’,
for i, item in enumerate(sig.parameters.items()):
name, param = item
print(i+1, name, param.annotation, param.kind, param.default,param.default is param.empty,sep=' | ')
变量介绍
name,参数的名字
annotation,参数的注解,可能没有定义
default,参数的缺省值,可能没有定义
empty,特殊的类,用来标记default属性或者注释annotation属性的空值
kind,实参如何绑定到形参,就是形参的类型
POSITIONAL_ONLY,值必须是位置参数提供
POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供
VAR_POSITIONAL,可变位置参数,对应*args
KEYWORD_ONLY,keyword-only参数,对应*或者*args之后的出现的非可变关键字参数
VAR_KEYWORD,可变关键字参数,对应**kwargs
i+1 | 参数 | 参数注解 | 参数类型 | 默认值 | 默认值是否为空 |
---|---|---|---|---|---|
1 | x | POSITIONAL_OR_KEYWORD | True | ||
2 | y | POSITIONAL_OR_KEYWORD | 7 | False | |
3 | args | VAR_POSITIONAL | True | ||
4 | z | KEYWORD_ONLY | True | ||
5 | t | KEYWORD_ONLY | 10 | False | |
6 | kwargs | VAR_KEYWORD | True |
- 应用实例
有函数如下
def add(x, y:int=7) -> int:
return x + y
请检查用户输入是否符合参数注解的要求?
- 思路
1. 对用户输入的数据和声明的类型进行对比,如果不符合,提示用户
2. 此功能可尝试用装饰器实现
普通版本:
import inspect
def add(x, y:int=7) -> int:
return x + y
def check(fn):
def wrapper(*args, **kwargs):
params = inspect.signature(fn).parameters
values = list(params.values())
for i,p in enumerate(args):
if isinstance(p, values[i].annotation): # 实参和形参声明一致
print('==')
for k,v in kwargs.items():
if isinstance(v, params[k].annotation): # 实参和形参声明一致
print('===')
return fn(*args, **kwargs)
return wrapper
装饰器版本:
import inspect
def check(fn):
def wrapper(*args, **kwargs):
params = inspect.signature(fn).parameters #parameters属性 有序字典
values = list(params.values()) #params的值列表化 与下面的enumerate配合使用
for i,p in enumerate(args):
param = values[i]
if param.annotation is not param.empty and not isinstance(p, param.annotation):
#若函数注解非空且不是注解指定类型 提示输入有误
print(p,'!==',values[i].annotation)
for k,v in kwargs.items():
if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
#若函数注解非空且不是注解指定类型 提示输入有误
print(v,'!===',params[k].annotation)
return fn(*args, **kwargs) #返回原函数的调用,原函数继续执行
return wrapper
@check
def add(x, y:int=7) -> int:
return x + y
functools模块
偏函数 partial
- 固定函数的部分参数 相当于为部分参数添加默认值
- partial生成的新函数是对原函数的封装
import functools
def add(x, y) -> int:
return x + y
newadd = functools.partial(add, y=5) 偏函数将y值默认为5
print(newadd(7)) --> 12
print(newadd(7, y=6)) --> 13
print(newadd(y=10, x=6)) --> 16
import inspect
print(inspect.signature(newadd) --> (x, *, y=5) -> int
def add(x, y, *args) -> int:
print(args)
return x + y
newadd = functools.partial(add, 1,3,6,5) # x和y分别设默认值1和3, 6和5存于args中
print(newadd(7)) --> (6, 5, 7) 4
print(newadd(7, 10))--> (6, 5, 7,10) 4
print(newadd(y=20, x=26,9, 10)) #语法错误
print(newadd())--> (6, 5) 4
import inspect
print(inspect.signature(newadd)) --> (*args) -> int
偏函数本质
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords): # 包装函数
newkeywords = keywords.copy()
newkeywords.update(fkeywords) #newkeywords由keywords与fkeywords合并而来
return func(*(args + fargs), **newkeywords)
newfunc.func = func # 保留原函数
newfunc.args = args # 保留原函数的位置参数
newfunc.keywords = keywords # 保留原函数的关键字参数参数
return newfunc
def add(x,y):
return x+y
foo = partial(add,y=4)
print(foo(5,y=8))
- 偏函数代码分析
- 收集原函数func与固定参数 分别放于func args和keywords中 生成新函数newfunc
- 调用新函数,参数放于fargs和fkeywords中
- 两次收集的参数合并 args+fargs newkeywords = keywords.copy() newkeywords.update(fkeywords)
- 新函数newfunc依然调用原函数func 只是将两次的收集的参数合并以后调入
偏函数使用技巧:用关键字传参方式固定靠后的参数 尽量不要固定靠前的参数,对默认值的修改要用关键字传参方式 因此应靠后
LRU缓存
- @functools.lru_cache(maxsize=128, typed=False)
- lru:Least-recently-used最近最少使用 cache:缓存
- maxsize设置为None,则禁用LRU功能,且缓存可无限增长。当maxsize是二的幂时,LRU功能执行得最好
- typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用
LRU缓存通过一个字典缓存被装饰函数的调用和返回值
LRU缓存使用前提
- 同样的函数参数一定得到同样的结果
- 函数执行时间很长,且要多次执行
- 本质是函数调用的参数=>返回值
- 缺点
- 不支持缓存过期,key无法过期、失效
- 不支持清除操作
- 不支持分布式,是一个单机的缓存
- 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询
典型使用场景:递归方法求斐波那契数列 效果看下图
从图中可以看出 开启LRU缓存前后 耗费时间差了7个数量级
Base64
- Base64是一种基于64个可见字符来表示二进制数据的表示方法。
- 简单而言 Base64是将ASCII表体系原有的8个比特位断开改成6个比特位断开
- 因为6和8的最小公倍数是24 因此作转化时 往往是将3个ASCII字节转化为4个Base64字节
若ASCII字节不能整除3 则用0补齐二进制位 同时输出的Base64字符最后加入1或2个=
Base64 编码表:
一图胜千言的编码过程:
自己实现Base64编解码
def base64encode(s:bytes) -> bytes:
n=len(s)%3
s=s.encode() if isinstance(s,str) else s
bit8=''.join(('{:0>8}'.format(bin(i)[2:]) for i in s))
bit8+='0'*((3-n)<<3) if n else ''
bit6=[bit8[i:i+6] for i in range(0,len(bit8),6)]
tab='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
dic={'{:0>6}'.format(bin(i)[2:]):j for i,j in enumerate(tab)}
t=''.join(map(lambda x:dic[x],bit6)).encode()
return t[:len(t)-3+n]+b'='*(3-n) if n else t
def base64decode(s:bytes) -> bytes :
s=s.decode() if isinstance(s,bytes) else s
n=s.count('=',-2)
s=s[:len(s)-n]
tab='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
dic={j:'{:0>6}'.format(bin(i)[2:]) for i,j in enumerate(tab)}
bit6=''.join(dic[i] for i in s)
bit6=bit6[:len(bit6)-n*2]
bit8=[bit6[i:i+8] for i in range(0,len(bit6),8)]
return ''.join(map(lambda x:chr(int(x,2)),bit8)).encode()
- python自带base64模块 注意输入输出数据类型为bytes
- 详细用法为
import base64
print(base64.b64encode(b'Python2018')) --> b'UHl0aG9uMjAxOA=='
print(base64.b64decode(b'UHl0aG9uMjAxOA==')) --> b'Python2018'
Bytes与Bytearray
- bytes 字节组成的有序不可变序列
- bytearray 字节组成的有序可变序列
- bytes与str的转化
- 默认编解码为utf-8
- str.encode() -> bytes
- bytes.decode() -> str bytearray.decode() -> str
- bytes定义
- bytes() 空bytes
- bytes(str) -> bytes 等价于str.encode() -> bytes
- bytes(int) 指定int个用0填充的空字节
- bytes(iterable_of_ints) -> bytes [0,255]的int组成的可迭代对象
- b前缀
- 只能用基本ASCII字符
- 16进制 b’\x61\x62’
- bytearray因为可变,可使用以下方法:
- bytearray(b’abcdef’).replace(b’f’,b’k’)
- bytes操作
- 与str类似
- 类方法 bytes.fromhex(str) str必须是2个字符的16进制形式
- bytes.fromhex(‘6162’)
- bytes.hex() -> str b’ab’.hex() -> ‘6162’
- bytes[index] -> int b’ab’[2] -> 98
- 迭代 list(i for i in b’ab’) -> [97,98]
- bytearray特有操作
- bytearray(b’abcdef’).replace(b’f’,b’k’)
- append(int)
- insert(index,int)
- extend(iterable_of_ints) #an iterable yielding integers in range(256)
- pop(index=-1)
- remove(value)
- clear()
- reverse()