Python第五周 学习笔记(1)

高阶函数


  • First Class Object
  • 函数也是对象,可调用的对象
  • 函数可以作为普通变量、参数、返回值等等

  • 数学概念 y=g(f(x))
  • 在数学和计算机科学中,高阶函数应当是至少满足下面一个条件的函数
  • 接受一个或多个函数作为参数
  • 输出一个函数

内建高阶函数

  • sorted(iterable[, key][, reverse])
    • 排序
  • filter(function, iterable) --> filter object
    • 过滤数据
  • map(func, *iterables) --> map object
    • 映射

sorted(iterable[, key][, reverse]) 排序

  • 返回一个新的列表,对一个可迭代对象的所有元素排序,排序规则为key定义的函数,reverse表示是否排序翻转

filter(function, iterable)

  • 过滤可迭代对象的元素,返回一个迭代器
  • function一个具有一个参数的函数,返回bool

map(function, *iterables) --> map object

  • 对多个可迭代对象的元素按照指定的函数进行映射,返回一个迭代器

柯里化Currying

  • 指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数
    z = f(x, y) ==> z = f(x)(y)

  • 举例
    将加法函数柯里化
    def add(x, y):
    return x + y

    转换如下

    def add(x):
    def _add(y):
    return x+y
    return _add
    add(5)(6)

    通过嵌套函数就可以把函数转换成柯里化函数

装饰器


在OOP设计模式中,装饰器属于一种装饰模式
装饰器语法是一个语法糖

def logger(func):
    def wrapper(*args, **kwargs):
        print('call ' + func.__name__)
        return func(*args, **kwargs)
    return wrapper

@logger # 等价于add = logger(add)
def add(x,y):
return x + y

装饰器(无参)

  • 它是一个函数
  • 函数作为它的形参
  • 返回值也是一个函数
  • 可以使用@functionname方式,简化调用

装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)

文档字符串

  • 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
  • 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
  • 可以使用特殊属性doc访问这个文档

装饰器副作用

  • 原函数对象的属性都被替换了(执行后返回的是wrapper的属性),而使用装饰器,我们的需求是查看被封装函数的属性

解决方法:

1.提供一个函数,被封装函数属性 ==copy==> 包装函数属性

def copy_properties(src, dst): # 可以改造成装饰器
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__
  • 通过copy_properties函数将被包装函数的属性覆盖掉包装函数
  • 凡是被装饰的函数都需要复制这些属性,这个函数很通用
  • 可以将复制属性的函数构建成装饰器函数,带参装饰器

2.使用functools.update_wrapper(wrapper, wrapped)

import datetime, time, functools

def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
    def _logger(fn):
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > duration:
                func(fn.__name__, duration)
            return ret
        return functools.update_wrapper(wrapper, fn)
    return _logger

@logger(5) # add = logger(5)(add)
def add(x,y):
    time.sleep(1)
    return x + y
print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')

3.使用@functools.wraps(wrapped)

import datetime, time, functools

def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
    def _logger(fn):
        @functools.wraps(fn)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > duration:
                func(fn.__name__, duration)
            return ret
        return wrapper
    return _logger

@logger(5) # add = logger(5)(add)
def add(x,y):
    time.sleep(1)
    return x + y

print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')

带参装饰器

  • 它是一个函数 函数作为它的形参
  • 返回值是一个不带参的装饰器函数
  • 使用@functionname(参数列表)方式调用
  • 可以看做在装饰器外层又加了一层函数

将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出

参数注解


形如:

def add(x:int , y:int) -> int :
'''
:param x: int
:param y: int
:return: int
'''
return x + y
  • Python 3.5引入
  • 对函数的参数进行类型注解
  • 对函数的返回值进行类型注解
  • 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
  • 提供给第三方工具,做代码分析,发现隐藏的bug
  • 函数注解的信息,保存在annotations属性中
  • 键是参数名,值是class类型

变量注解使用较少

inspect模块


signature(callable),返回一个Signature类对象。获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)

sig = signature(fn)
params = sig.parameters

Signature类对象的parameters方法返回的是一个orderedDict,其key值为fn的形参名,value值为Parameter类对象

Parameter对象

保存在元组中,是只读的

  • name,参数的名字
  • annotation,参数的注解,可能没有定义
  • default,参数的缺省值,可能没有定义
  • empty,特殊的类,用来标记default属性或者注释annotation属性的空值
  • kind,实参如何绑定到形参,就是形参的类型
    • POSITIONAL_ONLY,值必须是位置参数提供 #Python没有实现次形参类型
    • POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供
    • VAR_POSITIONAL,可变位置参数,对应*args
    • KEYWORD_ONLY,keyword-only参数,对应或者args之后的出现的非可变关键字参数
    • VAR_KEYWORD,可变关键字参数,对应**kwargs

业务应用

  • 函数参数类型检查
  • 思路

    • 函数参数的检查,一定是在函数外
    • 函数应该作为参数,传入到检查函数中
    • 检查函数拿到函数传入的实际参数,与形参声明对比
    • annotations属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用inspect模块
  • inspet模块提供获取对象信息的函数,可以检查函数和类、类型检查

  • 代码实现:
import inspect
def check(fn):
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        params = sig.parameters #是orderedDict
        values = list(params.values()) #可迭代对象转化成列表,处理后也是有序的
        for k,v in enumerate(args): #因为values、args都有序
            if values[k].annotation is not inspect._empty and not isinstance(v,values[k].annotation):
                return '{}`s input is {} type , expected {}'.format(v,type(v),values[k].annotation)
        for k,v in kwargs.items(): #kwargs与params都为字典形式,键值相同
            if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
                return '{}`s input is {} type , expected {}'.format(k,type(v),params[k].annotation)
        return fn(*args,**kwargs)
    return wrapper

@check
def add(x:int,y:int):
    return x + y

functools模块


偏函数partial

  • 把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回
  • 从partial生成的新函数,是对原函数的封装

partial源代码逻辑

def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords): # 包装函数
newkeywords = keywords.copy() #将原函数关键字参数拷贝后,
newkeywords.update(fkeywords)  #将调用新函数时的关键字参数更新
return func(*(args + fargs), **newkeywords) #调用新函数时的参数是建立偏函数时给的参
                                                                         #数,再各加上调用新函数时的参
newfunc.func = func # 保留原函数,注意:如果原函数已有装饰器,func属性内容有可能不是原函数名,而是装饰器函数名
newfunc.args = args # 保留原函数的位置参数
newfunc.keywords = keywords # 保留原函数的关键字参数参数
return newfunc
  • partial()返回一个新函数newfunc,而newfunc返回原函数,其参数是是建立偏函数时给的参数,再各加上调用新函数时的参

LRU缓存装饰器

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

  • 通过一个字典缓存被装饰函数的调用和返回值
  • 如果全为位置实参且位置与数值都相同,则视为相同实参,或者全传关键字参数则即使顺序不同也视为相同实参,即缓存能使用上;其他情况则皆视为不同实参

  • lru_cache使用_make_key生成参数缓存
  • 源码:

    def _make_key(args, kwds, typed,
             kwd_mark = (object(),),
             fasttypes = {int, str, frozenset, type(None)},
             tuple=tuple, type=type, len=len):
    """Make a cache key from optionally typed positional and keyword arguments
    
    The key is constructed in a way that is flat as possible rather than
    as a nested structure that would take more memory.
    
    If there is only a single argument and its data type is known to cache
    its hash value, then that argument is returned without a wrapper.  This
    saves space and improves lookup speed.
    
    """
    key = args
    if kwds:
        key += kwd_mark
        for item in kwds.items():
            key += item
    if typed:
        key += tuple(type(v) for v in args)
        if kwds:
            key += tuple(type(v) for v in kwds.values())
    elif len(key) == 1 and type(key[0]) in fasttypes:
        return key[0]
    return _HashedSeq(key)

lru_cache装饰器应用

  • 使用前提
    • 同样的函数参数一定得到同样的结果
    • 函数执行时间很长,且要多次执行
    • 本质是函数调用的参数=>返回值
  • 缺点
    • 不支持缓存过期,key无法过期、失效
    • 不支持清除操作
    • 不支持分布式,是一个单机的缓存
    • 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询

猜你喜欢

转载自blog.51cto.com/11281400/2106508
今日推荐