07 高阶函数 装饰器 函数注解 偏函数 LRU缓存 Base64

高阶函数

  • 数学概念:y=g(f(x))
  • 高阶函数应当满足以下条件之一
    1. 接受一个或多个函数作为参数
    2. 输出一个函数
  • Python函数
    1. 函数在Pyhon中是一等公民( First Class Object)
    2. 函数也是对象 可调用的对象
    3. 函数可做为普通变量 参数 返回值等等
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 
  1. 这个补丁可不可以写成装饰器呢?
    先想一下之前的装饰器是如何实现的
    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
  • 函数注解
    1. def add(x:int , y:int) -> int :
    2. Python3.5引入
    3. 对参数以及返回值进行类型注解
    4. 只是对参数的辅助说明,不对参数进行类型检查
    5. 函数注解信息保存于annotation属性中
    6. 主要用于代码分析 发现隐藏的bug
  • 变量注解
    1. i:int = 3
    2. 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参数参数注解参数类型默认值默认值是否为空
1xPOSITIONAL_OR_KEYWORDTrue
2yPOSITIONAL_OR_KEYWORD7False
3argsVAR_POSITIONALTrue
4zKEYWORD_ONLYTrue
5tKEYWORD_ONLY10False
6kwargsVAR_KEYWORDTrue

- 应用实例
有函数如下
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分别设默认值13, 65存于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))
  • 偏函数代码分析
    1. 收集原函数func与固定参数 分别放于func args和keywords中 生成新函数newfunc
    2. 调用新函数,参数放于fargs和fkeywords中
    3. 两次收集的参数合并 args+fargs newkeywords = keywords.copy() newkeywords.update(fkeywords)
    4. 新函数newfunc依然调用原函数func 只是将两次的收集的参数合并以后调入

偏函数使用技巧:用关键字传参方式固定靠后的参数 尽量不要固定靠前的参数,对默认值的修改要用关键字传参方式 因此应靠后

LRU缓存
  • @functools.lru_cache(maxsize=128, typed=False)
    1. lru:Least-recently-used最近最少使用 cache:缓存
    2. maxsize设置为None,则禁用LRU功能,且缓存可无限增长。当maxsize是二的幂时,LRU功能执行得最好
    3. typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用
  • LRU缓存通过一个字典缓存被装饰函数的调用和返回值

  • LRU缓存使用前提

    1. 同样的函数参数一定得到同样的结果
    2. 函数执行时间很长,且要多次执行
    3. 本质是函数调用的参数=>返回值
  • 缺点
    1. 不支持缓存过期,key无法过期、失效
    2. 不支持清除操作
    3. 不支持分布式,是一个单机的缓存
  • 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询
  • 典型使用场景:递归方法求斐波那契数列 效果看下图
    这里写图片描述

  • 从图中可以看出 开启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的转化
    1. 默认编解码为utf-8
    2. str.encode() -> bytes
    3. bytes.decode() -> str bytearray.decode() -> str
  • bytes定义
    1. bytes() 空bytes
    2. bytes(str) -> bytes 等价于str.encode() -> bytes
    3. bytes(int) 指定int个用0填充的空字节
    4. bytes(iterable_of_ints) -> bytes [0,255]的int组成的可迭代对象
    5. b前缀
      1. 只能用基本ASCII字符
      2. 16进制 b’\x61\x62’
      3. bytearray因为可变,可使用以下方法:
      4. 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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值