目录python
在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())
内层函数inner还引用了外层函数的自由变量x,造成了闭包,因为外部函数返回了内部函数,因此这就是一个典型的高阶函数。针对上面实例还须要说明是:闭包
In [1]: 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
is
的比较作则是:先比较元素的内存地址,而后比较元素的内容。首先 foo1和foo2 属于不一样的对象,因此内存地址确定不一样,而后因为函数对象没有实现函数内容的比较因此这里返回Falseapp
从头构建一个相似于内置函数sorted的函数,来体会高阶函数的做用,顺便理解一下sorted的原理。dom
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]))
分析:函数
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 reverse: # flag = value > k # else: # flag = value < k if flag: new_list.insert(i, value) break else: new_list.append(value) return new_list # return new_list[::-1] print(sort([1,6,2,7,9,3,3,5]))
分析:学习
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 # return new_list[::-1] print(sort(['a',1,2,'b'], key=str, reverse=True))
分析:优化
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))
Python内置了不少高阶函数的应用,这里仅介绍较为经常使用的。
sorted(iterable, /, *, key=None, reverse=False)
当即返回一个新的列表,对一个可迭代对象的全部元素排序。
key
: 排序规则为key定义的函数reverse
: 表示是否进行翻转排序In [49]: lst Out[49]: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95] In [50]: import random In [51]: random.shuffle(lst) In [52]: lst Out[52]: [70, 45, 90, 40, 30, 80, 25, 55, 5, 75, 85, 95, 50, 20, 35, 15, 10, 60, 65, 0] In [53]: sorted(lst,reverse=True) Out[53]: [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0] In [54]: lst.append('a') In [55]: 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 [58]: sorted(lst,key=lambda x:str(x)) Out[58]: [0, 10, 15, 20, 25, 30, 35, 40, 45, 5, 50, 55, 60, 65, 70 75, 80, 85, 90, 95, 'a'] In [59]: lst.remove('a') In [61]: sorted(lst,key=lambda x : 100 - x ) Out[61]: [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0]
这是匿名函数最经常使用的场景之一
)功能等同于直接调用str函数
sorted直接返回一个新的列表,而列表也有sort方法,经过
list.sort()
进行排序是直接原地进行排序的。
filter(function or None, iterable) --> filter object
过滤可迭代对象的元素,返回一个迭代器
function
:表示一个函数,它的功能是:每次从可迭代对象iterable取出一个元素交给function函数处理,若是返回True,则保留该元素,不然提出该元素,若是是None,表示剔除等效False的对象iterable
:可迭代对象In [62]: lst1 = [1,9,55,150,-3,78,28,123] In [64]: list(filter(lambda x:x%3==0,lst1)) Out[64]: [9, 150, -3, 78, 123] # 等同于 In [66]: def func(iterable): ...: for i in iterable: ...: if i % 3 == 0: ...: yield i ...: In [67]: list(func(lst1)) Out[67]: [9, 150, -3, 78, 123] # 或者 In [66]: def func(iterable): ...: for i in iterable: ...: if (lambda x:x%3==0)(i): ## l这里属于函数调用:func()() ...: yield i ...:
map(func, *iterables) --> map object
对多个可迭代对象的元素按照指定的函数进行映射,返回一个迭代器
func
:一个函数,用于处理iterable的元素,在指定多个iterable对象是,函数的参数数量与iterable的数量是相等的。*iterable
:一个或多个可迭代对象In [69]: list(map(str,range(10))) Out[69]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] In [70]: list(map(lambda x:x+1,range(10))) Out[70]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] In [73]: dict(list(map(lambda x,y:(x,y),'abcde',range(10)))) Out[73]: {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4} In [74]: dict(map(lambda x:(x%5,x),range(500))) Out[74]: {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) 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 执行结果: In [76]: 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)
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(50)
以上述函数为例,咱们须要记录当函数执行超过必定时间时的日志信息,该怎么办呢?假设这个时间是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
是否是很简单? 在掌握了柯里化以及无参装饰器。
代参装饰器有以下特色:
能够看作在装饰器外层又加了一层函数
装饰器何时被执行?,还记得@logger等于什么吗? add = logger(add) 等号等于赋值,是否是要先计算右边的?因此,装饰器在函数定义阶段就已经被执行了!不是等到被装饰的函数执行时,才执行哦!真正执行时,每次都会生成一个新的wrapper被调用而已!