Python高阶函数与装饰器

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

一、 高阶函数

1. 定义

在数学和计算机科学中,高阶函数应当是至少满足下面一个条件的函数:
1)接受一个或多个函数作为参数
2)输出一个函数

2. 例子

简单计数器:

def counter(base):
    def inc(step=1):
        nonlocal base  #闭包
        base += step
        return base
    return inc

函数counter是一个高阶函数,因为它输出了一个函数inc;
f1 = counter(5)和f2 = counter(5) 具有相等的函数返回值,f1 == f2为True
f1 = counter 和 f2 = counter返回不同的的函数对象

3. 内建函数 - 高阶函数:

3.1排序

sorted(iterable[, key][, reverse])
功能:返回一个新的列表,对一个可迭代对象的所有元素排序,排序规则为key定义的函数,reverse表示是否排序翻转

3.2过滤数据

filter(function, iterable) --> filter object
功能:过滤可迭代对象的元素,返回一个迭代器,function一个具有一个参数的函数,返回bool。
例如:过滤出数列中能被3整除的数字
list(filter(lambda x: x%3==0, [1,9,55,150,-3,78,28,123]))

3.3映射

map(func, *iterables) --> map object
功能:对多个可迭代对象的元素按照指定的函数进行映射,返回一个迭代器
例如:
list(map(lambda x:2*x+1, range(5)))
dict(map(lambda x: (x%5,x) , range(500)))

4. 自定义sorted函数

仿照内建函数sorted,请自行实现一个sort函数(不使用内建函数),能够为列表元素排序
思路:
1)内建函数sorted函数是返回一个新的列表,可以设置升序或降序,可以设置一个排序的函数。自定义的sort函数也要实现这个功能
2)新建一个列表,遍历原列表,和新列表的值依次比较决定如何插入到新列表中

#自定义sort函数 - 1
def my_sort(lst):
    row = []
    for x in lst:
        for i,y in enumerate(row):
            if x>y:  #找到大的插入
                row.insert(i,x)
                break  #不加break的话,会继续执行第二层for循环,随着元素的不断加入,一直会是2>1,第二层for循环会一直执行下去,直到撑爆内存
        else:
            row.append(x)
    return row
print(my_sort([1,2,3,4,5]))
#自定义sort函数 - 2
def sort(iterable,reverse=False):
    row = []
    for x in iterable:
        for i,y in enumerate(row):
            flag = x>y if reverse else x<y
            if flag:
                row.insert(i,x)
                break
        else:
            row.append(x)
    return row
print(sort([4,2,5,1,3]))
#自定义sort函数 - 3
def sort(iterable,key=lambda a,b:a>b):
    ret = []
    for x in iterable:
        for i,y in enumerate(ret):
            if key(x,y): #函数的返回值是bool
                ret.insert(i,x)
                break
        else:
            ret.append(x)
    return ret
print(sort([4,2,5,1,3]))
#自定义sort函数 - 4
def sort(iterable,reverse=False,key=lambda x,y:x>y):
    ret = []  #未来排好序的列表
    for x in iterable:
        
        for i,y in enumerate(ret):
            if key(x,y):
                ret.insert(i,x)
                break
        else:
            ret.append(x)  
    
    return ret
sort([4,2,5,1,3])
#自定义sort函数 - 4.1
def 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 not reverse else not key(x,y)
            if flag:
                ret.insert(i,x)
                break 
        else:
            ret.append(x)  
    
    return ret
sort([4,2,5,1,3])

5. 自定义filter函数

filter函数源码:

class filter(object):
    """
    filter(function or None, iterable) --> filter object
    
    Return an iterator yielding those items of iterable for which function(item)
    is true. If function is None, return the items that are true.
    """
    def __getattribute__(self, *args, **kwargs): # real signature unknown
        """ Return getattr(self, name). """
        pass

    def __init__(self, function_or_None, iterable): # real signature unknown; restored from __doc__
        pass

    def __iter__(self, *args, **kwargs): # real signature unknown
        """ Implement iter(self). """
        pass

    @staticmethod # known case of __new__
    def __new__(*args, **kwargs): # real signature unknown
        """ Create and return a new object.  See help(type) for accurate signature. """
        pass

    def __next__(self, *args, **kwargs): # real signature unknown
        """ Implement next(self). """
        pass

    def __reduce__(self, *args, **kwargs): # real signature unknown
        """ Return state information for pickling. """
        pass

二、装饰器

1.柯里化

1,定义:指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数,如:z = f(x, y) 转换成 z = f(x)(y)的形式
2,举例:
将加法函数柯里化

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

转换如下

def add(x):
    def _add(y): 
        return x+y
    return _add 
    
add(5)(6)

通过嵌套函数就可以把函数转换成柯里化函数

2.装饰器(无参)

1,装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景

2,形成过程
一个加法函数,想增强它的功能,能够输出被调用过程以及调用的参数信息

def add(x,y):
    return x + y
#增加信息输出功能
def add(x,y):
    print("call add,x + y") #日志输出到控制台
    return x + y
add(4,5)
#输出结果
call add,x + y
Out[1]:
9

上面的加法函数是完成了需求,但是有以下的缺点:

  • 打印语句的耦合太高
  • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中
    所以进一步改进:
def add(x,y):
    return x + y

def logger(fn):
    print('begin')
    x = fn(4,5)
    print('end')
    return x

print(logger(add))
#输出结果
begin
end
9

在先前的基础上做到了业务分离功能,但是fn函数调用传参是个问题

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

def logger(fn,*args,**kwargs):
    print('begin')
    x = fn(*args,**kwargs)
    print('end')
    return x

print(logger(add,4,y=5))

解决了传参问题,进一步改变,柯里化+高阶函数:

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

def logger(fn):
    def wrapper(*args,**kwargs):
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    return wrapper

print(logger(add)(4,y=5))
#或者换种写法:
add = logger(add)
print(add(x=4,y=5))

最后,引入装饰器语法糖

def logger(fn):
    def wrapper(*args,**kwargs):
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    return wrapper

#装饰器语法糖
@logger #add = logger(add)
def add(x,y):
    return x + y

print(add(4,5))

@logger就是装饰器的语法
综上,我们可以得出:装饰器(无参)是一个函数,函数作为它的形参,返回值也是一个函数,可以使用@functionname方式,简化调用;装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)

3.文档字符串和带参装饰器

Python文档字符串Documentation Strings
在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号;惯例是首字母大写,第一行写概述,空一行,第三行写详细描述;可以使用特殊属性doc访问这个文档
例如:

def add(x,y):
    """This is a function of addition"""
    a = x + y
    return x + y

print("name={}\ndoc={}".format(add.__name__,add.__doc__)) #注意是双下划线
print(help(add))
#输出结果
name=add
doc=This is a function of addition
Help on function add in module __main__:

add(x, y)
    This is a function of addition

None

而如果我们要使用装饰器的话,会发现原函数对象的属性都被替换了,例如:

def logger(fn):
    def wrapper(*args,**kwargs):
        'I am wrapper' 
        print('begin')
        x = fn(*args,**kwargs) 
        print('end')
        return x
    return wrapper

@logger #add = logger(add) 
def add(x,y):
    '''This is a function for add''' 
    return x + y

print("name={}, doc={}".format(add.__name__, add.__doc__))
#输出结果
name=wrapper, doc=I am wrapper  #输出的属性是wrapper函数的不是我们想要的add函数的

但是我们的需求是查看被封装函数的属性,解决方法为:

def copy_properties(src, dst): # 可以改造成装饰器 
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__
    
def logger(fn):
    def wrapper(*args,**kwargs):
        'I am wrapper' 
        print('begin')
        x = fn(*args,**kwargs) 
        print('end')
        return x
    copy_properties(fn, wrapper) 
    return wrapper

@logger #add = logger(add) 
def add(x,y):
    '''This is a function for add''' 
    return x + y

print("name={}, doc={}".format(add.__name__, add.__doc__))

方法就是通过copy_properties函数将被包装函数的属性覆盖掉包装函数,凡是被装饰的函数都需要复制这些属性,这个函数很通用,可以将复制属性的copy_properties函数构建成装饰器函数,即带参装饰器。

def copy_properties(src): 
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _copy

def logger(fn):
    @copy_properties(fn) #wrapper = copy_properties(fn)(wrapper) 
    def wrapper(*args,**kwargs):
        'I am wrapper' 
        print('begin')
        x = fn(*args,**kwargs) 
        print('end')
        return x
    #copy_properties(fn, wrapper) 
    return wrapper

@logger #add = logger(add) 
def add(x,y):
    '''This is a function for add''' 
    return x + y

print("name={}, doc={}".format(add.__name__, add.__doc__))

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,910评论 17 410
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 有一位哲学家曾这样说: “优秀,是一种习惯,而优秀的人是不需要恋爱的,所以谈了恋爱的人都不是优秀的人。” 作为一只...
    f伐木累阅读 620评论 0 0
  • 期末须事结束了,别的同学都回家了,只有我一个人去老家,我们开车去老家,车上有四个人,一个是我的爷爷,还...
    王卓函阅读 260评论 0 0
  • 我对普通朋友这四个字的理解很简单:我在路上走着,遇见了你,大家点头微笑,结伴一程。缘深缘浅,缘聚缘散,该分别时分别...
    whb3246阅读 178评论 0 0