Python-高阶函数、柯里化、@装饰器、functools.wraps

1、高阶函数

1.1、一等公民

函数在Python是一等公民(First-Class Object)
函数也是对象,是可调用对象
函数可以作为普通变量,也可以作为函数的参数、返回值

1.2、什么是高阶函数

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

1.3、示例分析

def counter(base):
    def inc(step=1):
        nonlocal base # 形参base也是外部函数counter的local变量
        base += step
        return base
    return inc

c1 = counter(5)
print(c1()) # 6
print(c1()) # 7
print(c1()) # 8

f1 = counter(5)
f2 = counter(5)
print(f1 == f2) # 相等吗? False

2、柯里化

2.1、什么是柯里化

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

2.2、示例分析

2.2.1、代码

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

原来函数调用为 add(4, 5) ,柯里化目标是 add(4)(5) 。如何实现?

2.2.2、实现效果

每一次括号说明是函数调用,说明 add(4)(5) 是2次函数调用。

add(4)(5)
等价于
t = add(4)
t(5)

2.3、柯里化代码

def add(x):
    def fn1(y):
        return x+y
    return fn1

add(2)(3) # 5

3、装饰器

3.1、由来分析

3.1.1、需求:为一个加法函数增加记录实参的功能

def add(x, y):
    print('add called. x={}, y={}'.format(x, y)) # 增加的记录功能
    return x + y
add(4, 5)

上面的代码满足了需求,但有缺点:
记录信息的功能,可以是一个单独的功能。显然和add函数耦合太紧密。加法函数属于业务功能,输出信息属于非功能代码,不该放在add函数中

3.1.2、提供一个函数logger完成记录功能

# 简单的实现
def add(x, y):
    return x + y

def logger(fn):
    print('调用前增强')
    ret = fn(4, 5)
    print('调用后增强')
    return ret

print(logger(add))

# 改进后
def add(x, y):
    return x + y
 
def logger(fn, *args, **kwargs):
    print('调用前增强')
    ret = fn(*args, **kwargs) # 参数解构
    print('调用后增强')
    return ret

print(logger(add, 4, 5))

3.1.3、柯里化

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

def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        ret = fn(*args, **kwargs) # 参数解构
        print('调用后增强')
        return ret
    return wrapper

# 调用方式1
print(logger(add)(4, 5))


# 调用方式2
inner = logger(add)
x = inner(4, 5)
print(x)

# 调用方式3
add = logger(add)
print(add(100, 200))

3.1.4、装饰器语法

def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        ret = fn(*args, **kwargs) # 参数解构
        print('调用后增强')
        return ret
    return wrapper

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

print(add(100, 200))

@logger就是装饰器语法
等价式非常重要,如果你不能理解装饰器,开始的时候一定要把等价式写在后面

3.2、无参装饰器

上例的装饰器语法,称为无参装饰器
@符号后是一个函数
虽然是无参装饰器,但是@后的函数本质上是单参函数
上例的logger函数是一个高阶函数

3.3、日志记录装饰器实现

import time
import datetime

def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs) # 参数解构
        print('调用后增强')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('Function {} took {}s.'.format(fn.__name__, delta))
        return ret
    return wrapper
 
@logger # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
    time.sleep(2)
    return x + y

print(add(100, 200))

3.4、装饰器本质图

4、文档字符串

4.1、简介

Python文档字符串Documentation Strings
在函数(类、模块)语句块的第一行,且习惯是多行的文本,所以多使用三引号
文档字符串也算是合法的一条语句
惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
可以使用特殊属性__doc__访问这个文档

4.2、示例

def add(x, y):
    """这是加法函数的文档"""
    return x + y
print("{}'s doc = {}".format(add.__name__ , add.__doc__))

4.3、*.__name__、*.__doc__打印有问题的示例

import time
import datetime

def logger(fn):
    def wrapper(*args, **kwargs):
        "wrapper's doc"
        print('调用前增强')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs) # 参数解构
        print('调用后增强')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('Function {} took {}s.'.format(fn.__name__, delta))
        return ret
    return wrapper

@logger # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
    """add's doc"""
    time.sleep(0.1)
    return x + y
print("name={}, doc={}".format(add.__name__ , add.__doc__)) # name=wrapper, doc=wrapper's doc  发现打印是包装函数的信息

4.4、functools.wraps解决 *.__name__、*.__doc__打印有问题的示例

import time
import datetime
from functools import wraps

def logger(fn):
    @wraps(fn) # 用被包装函数fn的属性覆盖包装函数wrapper的同名属性
    def wrapper(*args, **kwargs):
        "wrapper's doc"
        print('调用前增强')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs) # 参数解构
        print('调用后增强')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('Function {} took {}s.'.format(fn.__name__, delta))
        return ret
    return wrapper

@logger # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
    """add's doc"""
    time.sleep(0.1)
    return x + y
print("name={}, doc={}".format(add.__name__ , add.__doc__)) # name=add, doc=add's doc 打印是装饰函数的属性数据

5、带参装饰器

5.1、简介

@之后不是一个单独的标识符,是一个函数调用
函数调用的返回值又是一个函数,此函数是一个无参装饰器
带参装饰器,可以有任意个参数
@func()
@func(1)
@func(1, 2)

5.2、示例

import time
import datetime
from functools import wraps

def logger(fn):
    @wraps(fn) # 用被包装函数fn的属性覆盖包装函数wrapper的同名属性
    def wrapper(*args, **kwargs): # wrapper = wraps(fn)(wrapper)
        "wrapper's doc"
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs) # 参数解构
        delta = (datetime.datetime.now() - start).total_seconds()
        print('Function {} took {}s.'.format(fn.__name__, delta))
        return ret
    return wrapper

@logger # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
    """add func"""
    pass

@logger
def sub(x,y):
    """sub func"""
    pass
    
print(add.__name__ ,sub.__name__) # add sub

 

posted @ 2023-07-02 22:09  小粉优化大师  阅读(61)  评论(0)    收藏  举报