Python高阶函数


高阶函数

  • 一切皆为对象(地址、数据成员、类型)

  • First Class Object

    • 函数在Python中是一等公民
    • 函数也对象,可调用的对象
    • 函数可以作为普通变量、参数、返回值等等
  • 高阶函数

    • 数学中概念y=g(f(x))
    • 在数学和计算机科学中,高阶函数至少应该满足下面的一个条件的函数

      • 接收一个或多个函数的引用作为参数的函数
      • 返回值是一个函数引用的函数
    • 函数的引用 :函数的名字

    • 函数的执行结果:func() is return 的结果
  • 计数器

    def counter(base):
        def inc(step=1):
            base += step
            return base
        return inc 
  • 分析:

    • 函数counter是不是一个高阶函数
    • 上面的代码有没有什么问题,怎么改进
    • 如何调用完成计数功能
    • f1 = counter(5) 和 f2= counter(5) f1和f2相等吗?

自定义my_sort函数

  • 排序问题
    • 仿照内置函数sorted,自行设计一个my_sort函数(不适用内置函数)
  • 思路
    • 内建函数sorted函数返回一个新的列表,可以设置升序或降序,可以设置一个排序函数,自定义的my_sort函数也要拥有相同的功能。
    • 新建一个列表,遍历原列表,和新列表的值一次比较决定如何插入到新列表中。
  • 思考my_sort函数的实现原理,扩展到map\filter函数中。
  • 实现简单排序
# 实现简单排序
lis = [1, 6, 2, 5, 4, 3, 7]


def my_sort(iterable):
    ret = []
    for x in iterable:
        for i, y in enumerate(ret):
            if x < y:
                ret.insert(i, x)
                break
        else:
            ret.append(x)
    return ret


new_lis = my_sort(lis)
print(new_lis)
  • 实现升序/降序
# 实现reverse功能
def my_sort(iterable, reverse=False):
    ret = []
    for x in iterable:
        for i, y in enumerate(ret):
            flag = x > y if reverse else x < y
            if flag:
                ret.insert(i, x)
                break
        else:
            ret.append(x)
    return ret


new_lis = my_sort(lis)
print(new_lis)
# 将比较函数单独定义
def comp(x, y, reverse):
    return x > y if reverse else x < y


def my_sort(iterable, reverse=False):
    ret = []
    for x in iterable:
        for i, y in enumerate(ret):
            if comp(x, y, reverse):
                ret.insert(i, x)
                break
        else:
            ret.append(x)
    return ret
# 使用高阶函数方法重新实现
def my_sort(iterable, reverse=False, key=lambda x, y: x > y):
    ret = []
    for x in iterable:
        for i, y in enumerate(ret):
            flag = key(x, y) if reverse else key(y, x)
            if flag:
                ret.insert(i, x)
                break
        else:
            ret.append(x)
    return ret
# 优化一下,让这个函数更接近sorted函数
def my_sort(iterable, reverse=False, key=lambda x: x):
    ret = []
    for x in iterable:
        for i, y in enumerate(ret):
            a, b = key(x), key(y)
            flag = a > b if reverse else a < b
            if flag:
                ret.insert(i, x)
                break
        else:
            ret.append(x)
    return ret
  • 函数是一步一步抽取功能实现的。

内建函数-高阶函数

sorted(iterable[,key][,reverse]) 排序
filter(func,iterable) --> filer objet
map(func,*iterables) --> map object 映射

sorted

功能 返回一个新的列表对一个可迭代对象的素有元素排序排序规则是由key定义revere表示是否翻转
sorted(lis,key=str)  # 返回新列表
list.sort(key=lambda x:len(str(x))) # 原地修改

filter

功能过滤可迭代对象的元素返回一个迭代器
funtion一个具有一个参数的函数返回bool布尔
例如过滤出数字中所有的偶数
list(filter(lambda x:x%2 ==0,range(100)))

map

作用对多个可迭代对象的元素按照指定的函数进行映射返回一个迭代器
list(map(lambda x:2*x+1,range(5)))
dict(map(lambda x: (str(x), x), range(500)))

reduce

# reduce函数 累计 函数必须能够接受两个参数,让序列中的值累计运算,前两个在函数中执行的执行结果在作为参数与下一个元素运算。
from functools import reduce

res2 = reduce(lambda x,y: x*y,[1,2,3,4,5])
print(res2)

柯里化Currying

  • 柯里化

    • 是指将原来接收多个参数的函数,变成新的接收一个参数的函数的过程。
    • z = f(x,y) 转换成z=f(x)(y)的形式
    • 通过嵌套函数可以普通函数改变为柯里化函数
  • 举例

    # 将加法柯里化
    def add(x):
        def _add(y):
            return x + y

        return _add


    n = add(10)(3)
    print(n)

装饰器

Python的装饰器(decorator)可以说是Python的一个神器,它可以在不改变一个函数代码和调用方式的情况下给函数添加新的功能。Python的装饰器同时也是Python学习从入门到精通过程中必需要熟练掌握的知识。

  • 装饰器就是一个嵌套函数,要满足以下特点

    • 外层函数要接受一个要包装的函数作为参数
    • 内层函数可以执行被包装的函数
    • 外层函数要返回内层函数的引用
  • 需求增强add函数,让它具有输出功能,但不能改变它

    def add(x,y):
        return x + y
  • 实现功能
    def add(x, y):
        return x + y


    def logger(fn):
        print("begin")
        res = fn(3, 7)
        print(res)
        print("end")


    logger(add)
    # 优化一下
    def logger(fn):
        def _logger(*args, **kwargs):
            print("begin")
            res = fn(*args, **kwargs)
            print(res)
            print("end")

        return _logger


    # add = logger(add)
    # add(3, 7)
    logger(add)(4,6)
  # 使用装饰器语法糖
  @logger # 等价于 add = logger(add)
  def add(x,y):
    return x + y

  add(2,8)

带参装饰器

  • 三层嵌套的装饰器函数可以实现带参装饰器
  • 案例:实现求其他函数n次执行的平均时间的装饰器函数
import time

# 最外层接收装饰器的参数
def timer(n=1):
    # 中间层接收的是函数 
    def _timer(fn):
        # 最内层接收函数的参数
        def __timer(*args, **kwargs):
            lis = []
            print("begin")
            start_time = time.time()
            for i in range(n):
                result = fn(*args, **kwargs)
                lis.append(result)   
            print(f"执行{n}次后的平均运行时间是:{(time.time()-start_time)/n}")
              return lis 
        # 最内层函数  
        return __timer
    # 返回中间层
    return _timer


@timer(10)
def func():
    s = 0
    for i in range(1000):
        for j in range(10000):
            s += i * j
    print(s)


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

装饰器的应用

  • 日志、监控、权限、设计、参数检查、路由处理。

  • 这些功能与业务无关,很多业务都需要公共功能,所以适合独立出来,需要的时候对目标进行增强。

    上面的装饰器已经非常完美了,但是有我们正常情况下查看函数信息的方法在此处都会失效:

    def index():
      '''这是一个主页信息'''
      print('from index')

    print(index.__doc__)    #查看函数注释的方法
    print(index.__name__)   #查看函数名的方法
# 解决方法
from functools import wraps

def deco(func):
    #加在最内层函数正上方,用func的属性替换掉wrapper原先的属性
    @wraps(func) 
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

@deco
def index():
    '''哈哈哈哈'''
    print('from index')

print(index.__doc__)
print(index.__name__)

functools模块

from functools import update_wrapper

# 将被包装函数的所有特殊属性覆盖掉包装函数
update_wrapper(wrapper, # 包装函数
               wrapped, # 被包装函数
               assigned = WRAPPER_ASSIGNMENTS,
               updated = WRAPPER_UPDATES)
# WRAPPER_ASSIGNMENTS元组 要被覆盖的属性
WRAPPER_ASSIGNMENTS = ('__module__', # 模块名
                       '__name__',   # 函数名
                       '__qualname__', # 限定名
                       '__doc__',  # 文档
                       '__annotations__' # 参数注解
                      )
# 更新包含函数的字典
WRAPPER_UPDATES = ('__dict__',)
# 解决方法
from functools import update_wrapper

def deco(func):
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    update_wrapper(wrapper,func)
    return wrapper

@deco
def index():
    '''哈哈哈哈'''
    print('from index')

print(index.__doc__)
print(index.__name__)

Python类型注解

python函数定义的弊端

  • Python是动态语言,变量随时可以被赋值,且赋值可以为不同的类型

  • Python不是静态编译语言,变量类型是机器运行决定的。

  • 动态语言很灵活,但是这种特性也是弊端

    def add(x,y):
        return x+y 
    print(add(4,5)) 
    print(add("hello","world"))
    print(add(5,"abcd"))
  • 错误难以发现,由于不做类型检查,直到运行期间才会显现问题

  • 难使用:函数使用者看到函数时,并不知道你的函数设计,并不知道函数的类型。

解决弊端的方法
  • 1.增加文档
    def add(x,y):
        """
        :param x:int
        :param y:int
        :return int 
        """
        return x + y
    print(help(add))
* 这只是一个惯例,不是强制标准
* 函数更新了,文档未必更新
  • 2.函数注解Function Annotations
def add(x:int,y:int)->int:
    """
    :param x:int
    :param y:int
    :return int 
    """
    return x + y
print(help(add))
  • Python3.5引入

  • 对函数的参数进行类型的注解

  • 对函数的返回值进行类型的注解

  • 对函数的参数做一个辅助的说明,并不对函数参数进行类型检查

  • 提供第三方工具,做代码分析,发现隐藏bug

  • 函数注解的信息,保存在__annotations__

    print(add.__annotations__)
    {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
  • 变量注解

    • Python3.6中引入
    • i:int = 100
  • inspet模块提供了获取对象信息函数,可以检查函数、类的类型检查。

    inspect模块中的signature(callable)函数
    获取对象签名
    import inspect
    def add(x:int,y:int)->int:
        """
        :param x:int
        :param y:int
        :return int 
        """
        return x + y
    sig = inspect.signature(add)
    print(sig)
    print(sig.parameters)
    print(sig.return_annotation)
inspect模块主要提供了四种用处
"""
1.对是否是模块、框架、函数进行类型检查
2.获取源码
3.获取类或者函数的参数信息
4.解析堆栈
"""
inspect.ismodule(object) 是否为模块
inspect.isclass(object)是否为类
inspect.ismethod(object)是否为方法bound method written in python
inspect.isfunction(object)是否为函数(python function, including lambda expression)
inspect.isgeneratorfunction(object)是否为python生成器函数
inspect.isgenerator(object):是否为生成器