Python —— 高阶函数、生成器、装饰器及协程(coroutine)

 
 
 
 
 
 
 
 

高阶函数

特点(满足其一即为高阶函数):

1、接收一个或者多个函数作为参数

2、输出一个函数

 
 
 
 

高阶函数的实例

 
 

实例一、实现sorted

需求:

1、输入列表,不改变原列表并且排序后输出新列表

2、可以控制正逆序

3、可以传入一个函数来对列表中的每一个值进行操作

def mySorted(listData, reverse=False, fun=None):
    resList = [ ]

    # 判断正逆序
    if not reverse:
        exp = 'i < j'
    else:
        exp = 'i > j'

    # 通过fun修改listData
    if fun:
        newList = list(map(fun, listData))
    # 进行大小排序
    for i in newList:
        for index, j in enumerate(resList):
            if eval(exp):
                resList.insert(index, i)
                # 当已经把i插入到resList中一次后,就不再判断后面的值 避免重复插入
                # 此处的break是对内层循环的控制,即控制resList
                break
        # 没有通过break结束本次循环,则说明输入列表中的值小于输出列表中所有值,则插入到最后
        else:
            resList.append(i)

    return resList


listData = [1, 3, 2, 5, '7', 4, 6, 7, '5']
revers = False
res = mySorted(listData, revers, fun=lambda x: int(x))
print(res)

在这里插入图片描述

上面代码中的

    # 判断正逆序
    if not reverse:
        exp = 'i < j'
    else:
        exp = 'i > j'

可以替换为更灵活的形式

    def comp(a, b):
        return a > b if reverse else a < b

调用处也改为

            if comp(i, j):
                resList.insert(index, i)

 
 
 
 

函数柯里化

柯里化:将原来接收两个参数的函数,转变为一个新的接收一个参数的函数,新的函数返回一个以原有函数第二个参数为参数的函数。新函数通过连续执行两遍来达到原函数传递两个参数的效果。

既: z = f(x, y) —> z = f(x)(y)

 

原理:通过闭包来保存作用域

 

如:将 add(x, y) 变为 myAdd(x)(y)

# 原函数
def add(x, y):
    return x + y

# 新函数
def myAdd(x):
    def getAdd(y):
        return x + y
    return getAdd

print(myAdd(1)(2))

 
 
 
 
 
 
 
 

装饰器

作用:在不改变原函数的基础上,扩展其功能(如运行时间检测、开始结束状态检测、返回结果输出等)

 

原理:借助函数的闭包柯里化可变参数 (*args,**kwargs),将原函数包裹在一个新的函数中,并在新函数中补充需要的功能

 

特点:

1、需要借助闭包来增加一个对原函数的引用(即给一个函数添加装饰器后,调用时看似使用的依旧是原函数的函数名,其实该函数已经不是原函数,只是名字相同而已。 该函数是在装饰器中通过闭包返回的一个新函数,原函数通过在装饰器函数中增加了一个引用来避免被垃圾回收机制回收掉)

2、借助柯里化使装饰器不必要一次性传入原函数及其参数,而是在装饰器内部进行参数传递

3、借助可变参数来兼容更多的函数,增强通用性

 

写装饰器的关键点:

1、写一个装饰器函数,参数仅有一个,且为函数引用

2、装饰器函数内部要定义一个函数,功能扩充在该函数中实现,且参数为 *args, **kwargs

3、装饰器内部函数要 return 原函数的运行结果,来保证不破坏原函数(这里不是指改了原函数代码,而是与原函数同名的新函数要有与原函数同样的运行结果,否则看起来就像是原函数被破坏了)

4、装饰器函数要return 内部函数,否则默认return None,则无法调用

 

如:

以下代码表示普通方式,在不改变原函数的前提下给其增加功能的方式,及使用装饰器的时候

import datetime

# 原函数
def add(x, y):
    return x + y

# 新函数
def getRunTime(func):
    # 此处通过函数 _getRunTime来形成闭包,增加一个对原函数的引用
    # 函数的参数在此传递,形参收集
    def _getRunTime(*args, **kwargs):
        start = datetime.datetime.now()
        print("开始时间为: ", start)
        # 传递参数,实参解包
        res = func(*args, **kwargs)

        end = datetime.datetime.now()
        print("结束时间为: ", end)

        return res
    return _getRunTime


print("原函数add的id为: ", id(add))

# 将原函数 add 传入 getRunTime() 函数来增加功能
# 将变量名 add 由指向原函数 add() 的内存地址,改为指向函数 _getRunTime() 的内存地址
# 但因为在函数 getRunTime() 中增加了一条对原函数的引用计数,所以原函数不会被垃圾回收给清理掉
add = getRunTime(add)
print("新函数add的id为: ", id(add))

# 调用新的 add() 函数,此时实质实在调用 _getRunTime(*args, **kwargs)
# 但要质疑传参的数量即类型要和原函数对应
print(add(4, 5))

在这里插入图片描述

使用装饰器调用方法如下:

import datetime

# 新函数
def getRunTime(func):
    # 此处通过函数 _getRunTime来形成闭包,增加一个对原函数的引用
    # 函数的参数在此传递,形参收集
    def _getRunTime(*args, **kwargs):
        start = datetime.datetime.now()
        print("开始时间为: ", start)
        # 传递参数,实参解包(也叫参数结构)
        res = func(*args, **kwargs)

        end = datetime.datetime.now()
        print("结束时间为: ", end)

        return res
    return _getRunTime


# 原函数,添加装饰器后
@getRunTime
def add(x, y):
    return x + y


# 通过装饰器实质是省略了 "add = getRunTime(add)" 这一步
print(add(4, 5))

在这里插入图片描述

 
 
 
 

装饰器导致函数说明文档和函数名(_ _ name _ _)被替换的情况

出现的问题:被装饰器修饰的函数,实质上已经被替换为其他函数了,那么此时就需要保留下原功能函数的说明文档

 

解决:通过定义一个属性复制函数来复制原功能函数的说明文档

 

例如:

1、说明文档被替换的情况

import datetime

# 新函数
def getRunTime(func):
    # 此处通过函数 _getRunTime来形成闭包,增加一个对原函数的引用
    # 函数的参数在此传递,形参收集
    def _getRunTime(*args, **kwargs):
        '''
        This is wrapper

        :param args:
        :param kwargs:
        :return:
        '''
        start = datetime.datetime.now()
        print("开始时间为: ", start)
        # 传递参数,实参解包
        res = func(*args, **kwargs)

        end = datetime.datetime.now()
        print("结束时间为: ", end)

        return res
    return _getRunTime


# 原函数,添加装饰器后
@getRunTime
def add(x, y):
    '''
    This is add

    :param x:
    :param y:
    :return:
    '''

    return x + y


# 通过装饰器实质是省略了 "add = getRunTime(add)" 这一步
print(add(4, 5))
print(add.__doc__)
print(add.__name__)

在这里插入图片描述

2、添加属性复制函数

import datetime

# 添加属性复制函数
def copyAttr(src, dst):
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__


# 新函数
def getRunTime(func):
    # 此处通过函数 _getRunTime来形成闭包,增加一个对原函数的引用
    # 函数的参数在此传递,形参收集
    def _getRunTime(*args, **kwargs):
        '''
        This is wrapper

        :param args:
        :param kwargs:
        :return:
        '''
        start = datetime.datetime.now()
        print("开始时间为: ", start)
        # 传递参数,实参解包
        res = func(*args, **kwargs)

        end = datetime.datetime.now()
        print("结束时间为: ", end)

        return res
    # ****** 添加到这里 ******
    copyAttr(func, _getRunTime)
    return _getRunTime


# 原函数,添加装饰器后
@getRunTime
def add(x, y):
    '''
    This is add

    :param x:
    :param y:
    :return:
    '''

    return x + y


# 通过装饰器实质是省略了 "add = getRunTime(add)" 这一步
print(add(4, 5))
print(add.__doc__)
print(add.__name__)

在这里插入图片描述

 
 
 
 

装饰器(有参)

应用场景:

1、有参的装饰器常用于解决类似复制原函数的 说明文档、函数名 等需要传入两个函数的操作。如对功能扩充的装饰器去扩充复制文档和函数名的功能。

2、对需要传参的装饰器(非两个函数复制属性),借用 @装饰器(参数) —> 等价于,@"装饰器(参数)运行结果"的特点,将一个变量作为第一个参数,并将装饰器的内部函数再最外侧嵌套一层。

 

形式:@装饰器(第一个函数对象) —> 等价于,“装饰器(第一个函数对象)”运行结果 + @ ,即 @“装饰器(第一个函数对象)的运行结果”

 

理解记忆:带参数的装饰器,可以理解为先对装饰器进行一次带参数的调用,再在运行结果前添加 @ ,正式成为装饰器

 
 

场景1实例:

import datetime

# # 添加属性复制函数
# def copyAttr(src, dst):
#     dst.__name__ = src.__name__
#     dst.__doc__ = src.__doc__


# 添加属性复制函数
def copyAttr(src):
    def _inner(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _inner

# 新函数
def getRunTime(func):
    # 此处通过函数 _getRunTime来形成闭包,增加一个对原函数的引用
    # 函数的参数在此传递,形参收集
    
    @copyAttr(func) # 相当于先运行 copyAttr(func),返回 _inner。 然后是 @_inner ---> _getRunTime = _inner(_getRunTime)
    def _getRunTime(*args, **kwargs):
        '''
        This is wrapper

        :param args:
        :param kwargs:
        :return:
        '''
        start = datetime.datetime.now()
        print("开始时间为: ", start)
        # 传递参数,实参解包
        res = func(*args, **kwargs)
        end = datetime.datetime.now()
        print("结束时间为: ", end)
        return res
    return _getRunTime


# 原函数,添加装饰器后
@getRunTime
def add(x, y):
    '''
    This is add

    :param x:
    :param y:
    :return:
    '''

    return x + y


# 通过装饰器实质是省略了 "add = getRunTime(add)" 这一步
print(add(4, 5))
print(add.__doc__)
print(add.__name__)

 
 

场景2实例:

应用带参装饰器前,只能给一个固定的数值

import datetime, time

# 新函数
def getRunTime(func):
    def _getRunTime(*args, **kwargs):
        start = datetime.datetime.now()
        # 传递参数,实参解包
        res = func(*args, **kwargs)
        time.sleep(1)
        end = datetime.datetime.now()
        dur = end - start
        print("运行时间为: ", dur)
        durSec = dur.total_seconds()
        print("运行时间为: ", durSec)

        # ****** 重点在这,变量t需要从外部传入 ******
        if durSec > 1:
            print("函数%s运行超时" % func.__name__)
        return res
    return _getRunTime


# 原函数,添加装饰器后
@getRunTime
def add(x, y):
    return x + y


# 通过装饰器实质是省略了 "add = getRunTime(add)" 这一步
print(add(4, 5))

在这里插入图片描述

应用带参装饰器后,可以灵活控制时间由外部传入

import datetime, time

# 新函数
def getRunTime(t):
    def _inner(func):
        def _getRunTime(*args, **kwargs):
            start = datetime.datetime.now()
            # 传递参数,实参解包
            res = func(*args, **kwargs)
            time.sleep(1)
            end = datetime.datetime.now()
            dur = end - start
            print("运行时间为: ", dur)
            durSec = dur.total_seconds()
            print("运行时间为: ", durSec)

            # 重点在这,变量t需要从外部传入
            if durSec > t:
                print("函数%s运行超过%s秒" % (func.__name__, t))
            return res
        return _getRunTime
    return _inner


# ****** 带参装饰器 ******
@getRunTime(0.8)
def add(x, y):
    return x + y


# 通过装饰器实质是省略了 "add = getRunTime(add)" 这一步
print(add(4, 5))

运行流程:

带参装饰器 @getRunTime(0.8) 先运行 getRunTime(0.8) —> 返回 _inner —> @_inner装饰器生效

 
 
 
 

functools模块

 
 

functools.update_wrapper()

 

功能:类似于上面的属性复制装饰器,将原函数的属性赋值给装饰器新生成的同名函数,在赋值属性的同时 还通过 .wrapped__属性保留了原函数,可通过 . _ wrapped _ ()来执行原函数。

 

语法:functools.update_wrapper( wrapperFunc, wrappedFunc, assignedAttr=WARPPER_ASSIGNMENTS, update=WRAPPER_UPDATES )

参数:

  • wrapperFunc —— 包装函数
  • wrappedFunc —— 被包装的函数
  • WARPPER_ASSIGNMENTS —— wrapperFunc 中要被覆盖的属性
  • WRAPPER_UPDATES —— wrappedFunc 中的属性, __dict__属性字典

 

实例

import datetime, functools


# 新函数
def getRunTime(func):
    # 此处通过函数 _getRunTime来形成闭包,增加一个对原函数的引用
    # 函数的参数在此传递,形参收集
    def _getRunTime(*args, **kwargs):
        '''
        This is wrapper

        :param args:
        :param kwargs:
        :return:
        '''
        start = datetime.datetime.now()
        print("开始时间为: ", start)
        # 传递参数,实参解包
        res = func(*args, **kwargs)

        end = datetime.datetime.now()
        print("结束时间为: ", end)

        return res
    # 同样的属性复制,通过functools简单快捷,还能保留原函数
    functools.update_wrapper(_getRunTime, func)
    return _getRunTime


# 原函数,添加装饰器后
@getRunTime
def add(x, y):
    '''
    This is add

    :param x:
    :param y:
    :return:
    '''

    return x + y


# 通过装饰器实质是省略了 "add = getRunTime(add)" 这一步
print(add(4, 5))
print(add.__doc__)
print(add.__name__)

# 通过functools模块,调用被保留下的原函数
print(add.__wrapped__(4, 50))

在这里插入图片描述

 
 

@functools.wraps() —— 带参装饰器

 

功能:functools.update_wrapper()的装饰器版本

 

使用:@functools.wraps( wrappedFunc, assignedAttr=WARPPER_ASSIGNMENTS, update=WRAPPER_UPDATES )

 

实例:

import datetime, functools


def getRunTime(func):
    # 此版本更加常用,更加高效
    @functools.wraps(func)
    def _getRunTime(*args, **kwargs):
        '''
        This is wrapper

        :param args:
        :param kwargs:
        :return:
        '''
        start = datetime.datetime.now()
        print("开始时间为: ", start)
        # 传递参数,实参解包
        res = func(*args, **kwargs)

        end = datetime.datetime.now()
        print("结束时间为: ", end)

        return res

    return _getRunTime



@getRunTime
def add(x, y):
    '''
    This is add

    :param x:
    :param y:
    :return:
    '''

    return x + y


print(add(4, 5))
print(add.__doc__)
print(add.__name__)

print(add.__wrapped__(4, 50))

在这里插入图片描述

 
 

functools.partial()

作用:偏函数,把函数的部分参数固定下来,相当于为部分的参数添加了一个固定值,形成一个新的参数返回

 

应用场景:在不修改原函数的情况下,给原函数的某些参数添加默认值。 返回的新函数只是用来标识并传参,实际功能调用的还是原来的函数

 

使用方式: newFun = functools.partial(fun, 1,2,3)

若fun函数有4个参数,则前三个被固定为1,2,3,且在新函数newFun中已经没有前三个参数的形参,无论调用newFun的时候传什么值都无法改变,仅会对第四个参数有影响

如:

当使用functools.partial()对add()进行固定参数后,新函数newadd即没有了形参x,y。

import functools
import inspect


def add(x, y, *args):
    return x + y


newadd = functools.partial(add, 1, 2, 3, 4)

print(inspect.signature(add))
print(inspect.signature(newadd))
print(newadd(x=11, y=22))

在这里插入图片描述

又如

import functools
import inspect


def add(x, y, *args):
    return x + y


newadd = functools.partial(add, 1, 2, 3, 4)

print(inspect.signature(add))
print(inspect.signature(newadd))
print(newadd(11, 22))

在这里插入图片描述

 
 

@functools.lru_cache()

作用:将被装饰的函数的运行结果保存起来,保存的形式为字典,key —> 调用参数, value —> 函数运行结果。当再次使用相同的参数进行调用的时候,则直接返回之前运行的结果。

 

含义:lru —— Least-recently-used装饰器,即最近最少使用。

 

适用前提(缓存是用空间换时间,并非没有代价):
1、同样的函数,使用同样的参数,一定可以获得同样的结果
2、函数的执行时间较长,且需要多次执行的情况

 

缺点:
1、不支持缓存过期,缓存字典的key无法过期、失效
2、不支持清除操作
3、不支持分布式,仅可在单机缓存
4、单进程中有效

 

应用场景:常用于对递归函数的加速。以斐波那契数列为例,当使用递归的方式调用斐波那契数列的时候,计算到40位左右就会很费时间,这是由于要计算fib(40),则需要层层递归计算fib(39)、fib(38)…,但是如果使用缓存缓存结果的话,只要之前计算过的 没有超出缓存数量限制的结果均会被保存,则不需要再调用函数计算,直接取结果。

即,当之前计算过一次fib(35),那么由于函数是递归调用则会对fib(34)~fib(1)全部调用一遍,在调用之后,所有的结果均会被保存到缓存字典中去。 此时若再计算fib(40),则仅仅需要计算fib(39)、fib(38)、fib(37)、fib(36)即可,当需要计算fib(35)和之前的值时,直接从缓存字典中取值

 

使用方法: functools.lru_cache(maxsize=128, typed=False)

当maxsize=None,则禁用LRU功能,并且缓存可以无限制的增加。

当maxsize=2**n,2的幂次方则LRU具有最好的性能

当typed=True,则不同类型的函数将单独缓存,例如f(3)和f(3.0)将被视作具有不同结果的不同调用

 

如:
以下函数add(4, 5)第一次调用时,没有缓存,则会进入函数运行需要time.sleep(3)。但是第二次再次以相同参数执行的时候,此时有了缓存,则直接从缓存中读取上次的运行结果,此时则不需要进入函数运行time.sleep(3),所以速度会非常快

import functools
import datetime
import time
import inspect


def getRunTime(func):
    @functools.wraps(func)
    def _wrapper(*args, **kwargs):
        start = datetime.datetime.now()
        print('start at: ', start)
        res = func(*args, **kwargs)
        finish = datetime.datetime.now()
        print('finish at: ', finish)
        cost = finish - start
        print('total cost: ', cost)
        return res

    return _wrapper


@getRunTime
@functools.lru_cache()
def add(x, y):
    time.sleep(3)
    return x + y


print(1, add(1, 2))
print(2, add(1, 2))

 
 
 
 
 
 
 
 

什么是生成器

生成器是指生成器对象,是一个可迭代的对象,即它也是一个迭代器。可由生成器表达式生成 或 借助yield关键字产生生成器函数,通过该函数来生成生成器对象

 

特点:延迟计算,惰性求值

 

应用场景:
1、无限产生某些对象
2、迭代读取某些数据,避免同时将所有数据加载入内存

 

关于 "延迟计算,惰性求值"的理解:普通的函数会在调用的时候,直接将其中的所有语句顺序执行直至结束。但是生成器函数在调用时,不会直接执行,而是先生成生成器对象,在等待next()的调用 才会执行。

如:

def gen():
    print(1)
    yield 1
    print(2)
    yield 2
    print(3)
    return 3

gen()

在这里插入图片描述
 

多次调用依旧不会执行

def gen():
    print(1)
    yield 1
    print(2)
    yield 2
    print(3)
    return 3

gen()
gen()
gen()
gen()
gen()

在这里插入图片描述
 

使用next()调用

def gen():
    print(1)
    yield 1
    print(2)
    yield 2
    print(3)
    return 3

next(gen())

在这里插入图片描述

 

多次使用next()进行调用,此处全部打印1是因为每一条的next(gen())都是对应的不同的生成器,如果对同一个生成器调用next()则最多正常调用两次(因为有两个yield)。

def gen():
    print(1)
    yield 1
    print(2)
    yield 2
    print(3)
    return 3

next(gen())
next(gen())
next(gen())
next(gen())
next(gen())

在这里插入图片描述

 
 

生成器函数

特点:函数主体中包含 yield 语句,返回生成器对象

 

关于yeidl的理解:

1、yield 类似于 return 会返回一个东西,只不过被返回的数据类型是 “generator” ,所有通过 yield 的值都会被加入到 “generator” 中 等待下一步的调用。

2、yield 不像 return 会直接结束掉函数的执行,而是将函数暂停在该处,等待下次next()的调用则继续执行后面的语句

 

如:

def gen():
    for x in range(5):
        yield x

print("函数返回值为:", gen())
print("函数返回值类型为:", type(gen()))

genObj = gen()
genObj2 = gen()

print("迭代器genObj为:", genObj)
print("迭代器genObj2为:", genObj2)
print("genObj第一次调用next():", next(genObj))
print("genObj第二次调用next():", next(genObj))
print("genObj第三次调用next():", next(genObj))

在这里插入图片描述

 

生成器的另一个特点:在当前的上下文中,会保存当前"游标"的位置,并且结束后不会自动回到开始。即当一个生成器对象中所有的值都被迭代完后,再调用next()则会抛出异常

def gen():
    for x in range(5):
        yield x

genObj = gen()
for each in genObj:
    print(each)

for each in genObj:
    print(each, "***")

next(genObj)

在这里插入图片描述

 
 

当生成器迭代完后,阻止抛出异常

next(genObj, “end”) —— 当生成器没有值可以进行迭代的时候,返回"end" ,将"end"改为别的则返回对应内容。 类似字典的 dictData.get()

 
 
 
 

生成器常用写法优化

优化一(Python 3.3开始的语法糖)

优化前

def f():
    for x in range(10):
        yield x

优化后

def f():
    yield from range(10)

 
 
 
 
 
 
 
 

通过生成器去理解 lambda

1、lambda表达式在python中被认为是匿名函数

2、lambda表达式的形式为 lambda params:expression

3、lambda表达式是一个函数定义,需要另外执行(即 return lambda x:x+1 返回的是一个function,而不是 x+1的结果,而 return (lambda x:x+1)() 则返回函数运行的结果

4、lambda在执行后才会自行返回表达式的结果

def inc():
    def counter():
        i = 0
        while True:
            i += 1
            yield i
    # 创建一个生成器
    c = counter()
    # 返回的是lambda的一个函数,而不是lambda的执行结果
    return lambda : next(c)

foo = inc()
print(type(foo))
print(foo)

在这里插入图片描述

 
 
 
 
 
 
 
 

协程

特点:

1、协程是基于线程的,比进程和线程更轻量

2、在用户空间调度函数,不进入内核空间,节省了用户空间与内核空间切换的资源消耗(高效)

3、python3 的异步IO ( asynio )基于此实现

4、python3.5 使用 async、await 关键字直接原生支持协程

5、协程是生成器的高级用法

6、协程是非抢占式的调度,与进程与线程不同

 
 

协程的基本思路:

1、有两个生成器 A 、 B

2、先执行 next(A),A执行到 yield 处暂停,接着去执行 next(B),B执行到 yield 处暂停,回到A继续执行 next(A),再回到B 执行 next(B) 往复循环实现调度

3、切换方式可由调度的策略来控制

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值