在 Python 中一切皆对象,当然也包括函数。函数在 Python 中是 一等公民(First Class Object)。即函数与字符串、数组、整型无异,它可以被命名,可以被赋值,可以当作参数被传进另一个函数,也可以被另一个函数当作返回值,可以放在任何位置,简单来说:
那什么是高阶函数呢?在 Python 中我们可以理解为:当一个函数接受另一个函数作为参数使用,或者一个函数最后返回了另外一个函数。在这两种情况下,这个函数就可以称之为高阶函数(满足以上任意一种情况即可,不必同时满足)。
数学概念: y = g(f(x))
def outer(x):
def inner(step=1):
nonlocal x # 声明局部变量 x 不是一个局部变量,应该在外层寻找
x += step
return x
return inner
foo1 = outer(10)
foo2 = outer(10)
print(foo1())
print(foo2())
# 输出
11
11
内层函数 inner 还引用了外层函数的自由变量 x,形成了闭包,由于外部函数返回了内部函数,所以这就是一个典型的高阶函数。针对上面实例还需要说明是:
def outer(x):
def inner(step=1):
nonlocal x # 声明局部变量 x 不是一个局部变量,应该在外层寻找
x += step
return x
return inner
foo1 = outer(10)
foo2 = outer(10)
foo1 is foo2
# 输出
False
is
的比较规则是:先比较元素的内存地址,然后比较元素的内容。首先 foo1 和 foo2 属于不同的对象,所以内存地址肯定不同,函数对象无需进行函数内容的比较,所以这里返回 False
从头构建一个类似于内置函数 sorted 的函数,来体会高阶函数的作用,顺便理解一下 sorted 的原理。
def sort(iterable, *, key=None, reverse=False):
new_list = []
for value in iterable:
for i, k in enumerate(new_list):
if value < k:
new_list.insert(i, value)
break
else:
new_list.append(value)
return new_list
print(sort([1,6,2,7,9,3,5]))
# 输出
[1, 2, 3, 5, 6, 7, 9]
分析:
def sort(iterable, *, key=None, reverse=False):
new_list = []
for value in iterable:
for i, k in enumerate(new_list):
flag = value > k if reverse else value < k
if flag:
new_list.insert(i, value)
break
else:
new_list.append(value)
return new_list
print(sort([1,6,2,7,9,3,3,5], reverse=True))
# 输出
[9, 7, 6, 5, 3, 3, 2, 1]
分析:
def sort(iterable, *, key=None, reverse=False):
new_list = []
for value in iterable:
value_new = key(value) if key else value
for i, k in enumerate(new_list):
k_new = key(k) if key else k
flag = value_new > k_new if reverse else value_new < k_new
if flag:
new_list.insert(i, value)
break
else:
new_list.append(value)
return new_list
print(sort(['a',1,2,'b'], key=str, reverse=True))
# 输出
['b', 'a', 2, 1]
分析:
def sort(iterable, *, key=None, reverse=False):
new_list = []
for value in iterable:
value_new = key(value) if callable(key) else value
for i, k in enumerate(new_list):
k_new = key(k) if callable(key) else k
flag = value_new > k_new if reverse else value_new < k_new
if flag:
new_list.insert(i, value)
break
else:
new_list.append(value)
return new_list
print(sort(['a',1,2,'b'], key=lambda x:str(x), reverse=True))
# 输出
['b', 'a', 2, 1]
Python 内置了很多高阶函数的应用,这里仅介绍较为常用的。
sorted(iterable, /, *, key=None, reverse=False)
立即返回一个新的列表,对一个可迭代对象的所有元素排序。
key
:排序规则为 key 定义的函数reverse
:表示是否进行翻转排序
In : lst
Out: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
In : import random
In : random.shuffle(lst)
In : lst
Out: [70, 45, 90, 40, 30, 80, 25, 55, 5, 75, 85, 95, 50, 20, 35, 15, 10, 60, 65, 0]
In : sorted(lst, reverse=True)
Out: [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0]
In : lst.append('a')
In : sorted(lst, key=str, reverse=True)
Out[55] :['a', 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 5, 45, 40, 35, 30, 25, 20, 15, 10, 0]
In : sorted(lst, key=lambda x:str(x))
Out: [0, 10, 15, 20, 25, 30, 35, 40, 45, 5, 50, 55, 60, 65, 70 75, 80, 85, 90, 95, 'a']
In : lst.remove('a')
In : sorted(lst,key=lambda x : 100 - x)
Out: [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0]
这是匿名函数最常用的场景之一
)sorted 直接返回一个新的列表,而列表也有 sort 方法,通过
list.sort()
进行排序是直接原地进行排序的。
filter(function or None, iterable) --> filter object
过滤可迭代对象的元素,返回一个迭代器
function
:表示一个函数,它的功能是:每次从可迭代对象 iterable 取出一个元素交给 function 函数处理,如果返回 True,则保留该元素,否则剔除该元素,如果是 None, 表示剔除等效 False
的对象iterable
:可迭代对象
In : lst1 = [1,9,55,150,-3,78,28,123]
In : list(filter(lambda x:x%3==0, lst1))
Out: [9, 150, -3, 78, 123]
# 等同于
In : def func(iterable):
...: for i in iterable:
...: if i % 3 == 0:
...: yield i
...:
In : list(func(lst1))
Out: [9, 150, -3, 78, 123]
# 或者
In : def func(iterable):
...: for i in iterable:
...: if (lambda x:x%3==0)(i): # 这里属于函数调用:func()()
...: yield i
...:
map(func, *iterables) --> map object
对多个可迭代对象的元素按照指定的函数进行映射,返回一个迭代器
func
:一个函数,用于处理 iterable 的元素,在指定多个 iterable 对象时,函数的参数个数
与 iterable 对象的个数
是相等的。*iterable
:一个或多个可迭代对象
In : list(map(str,range(10)))
Out: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
In : list(map(lambda x:x+1, range(10)))
Out: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
In : dict(list(map(lambda x,y:(x,y), 'abcde', range(10))))
Out: {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4}
In : dict(map(lambda x:(x%5,x), range(500)))
Out: {0: 495, 1: 496, 2: 497, 3: 498, 4: 499}
map 对象的长度,等同于最小的 iterable 长度。(木桶效应)
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。(来自维基百科)
总结一下:柯里化指的就是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。其数学表达式为:
z = f(x, y)
# 转换成
z = f(x)(y)
# 的形式
这里把经典的 add 函数拿来进行转换:
def add(x, y):
return x + y
def new_add(x):
def inner(y):
return x + y
return inner
print(add(4, 5)) # 9
print(new_add(4)(5)) # 9
print(add(4, 5) == new_add(4)(5)) # True
通过函数嵌套,就可以完成函数的柯里化了,对柯里化有所了解以后,那么我们就可以继续来看 Python 中对我们小白来说的第一个难点:装饰器
什么是装饰器?概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。我们接下来从一个需求开始学习装饰器。
现在有如下函数,我们需要将这个函数的日志打印到终端
def add(x,y):
print(' 我被调用执行啦 ') # 新增打印日志语句
return x + y
add(100,200)
# 输出
我被调用执行啦
但是仔细思考,打印日志是一个独立的功能,它和 add 函数本身并没有什么关联关系,我们说一个函数是为完成一个工程的,所以直接写在函数里面不是不可以,但是不建议,并且打印日志属于调试信息功能,与业务无关,不应该放在业务函数加法中。
如果不需要写在函数的里面,那么我们得想办法写在函数的外面。
def add(x, y):
return x + y
def logger(fn, x, y):
print(' 函数开始执行 ')
res = fn(x, y)
print(' 函数执行完毕 ')
return res
logger(add,4,5)
def add(x, y):
return x + y
def logger(fn, *args, **kwargs):
print(' 函数开始执行 ')
res = fn(*args, **kwargs)
print(' 函数执行完毕 ')
return res
logger(add,4,5)
使用 logger(add,4,5)
来调用我们的 add 函数真是太丑了,给其他人看,可能人家也不知道你要干啥,结合前面所学的柯里化,我们进行如下变动
def add(x, y):
return x + y
def logger(fn):
def wrapper(*args, **kwargs):
print(' 函数被执行了 ')
res = fn(*args, **kwargs)
print(' 函数执行完毕 ')
return res
return wrapper
logger(add)(4,5)
这样看起来是不是就好看多了?当指定 logger(add)(4,5) 时,才会打印日志,但如果想要在所有调用 add 函数的地方,我们还需要在所有调用 add 的地方修改为 logger(add)(参数), 想一想,如果我能把 logger(add) 变成 add 是不是就可以直接写成 add(4,5) 了呢?
logger(add)(4,5)
--------
add = logger(add)
add(4,5) # 将 add 重新指向了新的函数 wrapper
按照柯里化的原型中 logger(add) 返回了一个函数 wrapper,而我们的(4,5) 其实是传递给了 wrapper,结合我们前面所学的高阶函数,这里的 wrapper,是一个闭包函数,因为在内部对 fn 进行了执行,而且增加了打印日志的功能,我们在执行 wrapper 的同时,也会执行原来的函数 fn,并且添加了打印日志的功能,所以 logger 就是一个装饰器函数
!!!
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得・蘭丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。 语法糖让程序更加简洁,有更高的可读性。Python 针对我们刚刚编写的 logger(add) 函数,进行了语法糖优化,下面是我们使用语法糖之后的:
def logger(fn):
def wrapper(*args, **kwargs):
print(' 函数被执行了 ')
res = fn(*args, **kwargs)
print(' 函数执行完毕 ')
return res
return wrapper
@logger # 等于 add = logger(add)
def add(x, y):
return x + y
add(4,5)
当解释器执行到
@logger
时,会自动把它下面的函数当作参数,传给 logger 函数,所以这里@logger
其实就等于add = logger(add)
另外,logger 必须要定义在 add 函数之前才可以被装载!这一点很重要!
利用装饰器计算如下函数的运行时间
import time
import datetime
def logger(fn):
def wrapper(*args, **kwargs):
start = datetime.datetime.now()
res = fn(*args, **kwargs)
duration = (datetime.datetime.now() - start).total_seconds()
print(' 函数:{} 执行用时:{}'.format(wrapper.__name__, duration))
return res
return wrapper
@logger
def add(x, y):
time.sleep(2)
return x + y
# 执行结果:
In : add(4,5)
函数:wrapper 执行用时:2.000944
这里
__name__
表示函数的名称.
什么鬼?这里为什么打印的是 wrapper 啊,为什么不是 add 呢?这样的话,别人不就发现我把这个函数给偷偷换掉了吗?不行不行,我得想个办法把函数的属性复制过来,由于这个功能和打印用时的装饰器不是一个功能,那么我们还得给装饰器再加一个装饰器。-_-!
import time
import datetime
def copy_properties(old_fn):
def wrapper(new_fn):
new_fn.__name__ = old_fn.__name__
return new_fn
return wrapper
def logger(fn):
@copy_properties(fn) # wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
start = datetime.datetime.now()
res = fn(*args, **kwargs)
total_seconds = (datetime.datetime.now() - start).total_seconds()
print(' 函数:{} 执行用时:{}'.format(wrapper.__name__,total_seconds))
return res
return wrapper
@logger
def add(x, y):
time.sleep(2)
return x + y
add(4,5)
@copy_properties(fn)
时,会把下面的 wraper 装入,等于wrapper = copy_properties(fn)(wrapper)
__name__
一个,其他的怎么办呢?Python 的内置模块 functools 中,内置了很多常用的高阶函数,其中 wraps 就是用来拷贝函数的属性及签名信息的。利用 wraps,我们就不需要自己编写 copy_properties 函数了,下面是修改后的版本
import time
import datetime
import functools
def logger(fn):
@functools.wraps(fn) # wrapper = functools.wraps(fn)(wrapper)
def wrapper(*args, **kwargs):
start = datetime.datetime.now()
res = fn(*args, **kwargs)
total_seconds = (datetime.datetime.now() - start).total_seconds()
print(' 函数:{} 执行用时:{}'.format(wrapper.__name__,total_seconds))
return res
return wrapper
@logger
def add(x, y):
time.sleep(2)
return x + y
add(4,5)
通过使用@functools.wraps(fn)
我们可以方便的拷贝函数的属性签名信息,比如:__module__
、__name__
、__qualname__
、__doc__
、__annotations__
等,这些属性信息,将在后续部分进行讲解,这里知道即可。
上面章节讲到的是带一个参数(函数) 的装饰器,在 Python 中这种装饰器被称为无参装饰器,因为语法糖的表现形式就是 @logger
, 下面要说的是带参数的装饰器,即 @logger(args)
以上述函数为例,我们需要记录当函数执行超过一定时间时的日志信息,该怎么办呢?假设这个时间是 5 秒,那么很显然,我们需要把这个时间变量传入到装饰器中进行判断。也就是说我们需要写成这种形式:
logger(5)(add)
looger(5) 返回的是一个函数,否则无法将 add 传入
import time
import datetime
import functools
def logger(var):
def inner(func):
def wrapper(*args, **kwargs):
start = datetime.datetime.now()
res = func(*args, **kwargs)
total_seconds = (datetime.datetime.now() - start).total_seconds()
if total_seconds > var:
print(' 函数执行时间过长 ')
return res
return wrapper
return inner
@logger(5) # logger(5)(add)
def add(x, y):
time.sleep(6)
return x + y
在掌握了柯里化以及无参装饰器后,是不是变得很简单?
带参装饰器有如下特点:
@function_name(参数列表)
方式调用装饰器什么时候被执行?还记得 @logger 等于什么吗? add = logger(add) 等号等于赋值,是不是要先计算右边的?所以,装饰器在函数定义阶段就已经被执行了!不是等到被装饰的函数执行时,才执行哦!真正执行时,每次都会生成一个新的 wrapper 被调用而已!