zoukankan      html  css  js  c++  java
  • Python函数式编程之返回函数、匿名函数、装饰器、偏函数

    参考原文

      廖雪峰Python

    返回函数

      我们已经知道了高阶函数可以接受函数作为参数外,还可以把函数作为结果值返回。我们来看一个实现可变参数的求和:

    def calc_sum(*args):
        ax = 0
        for n in args:
            ax += n
        return ax

      但是,但我们不需要立即知道求和的结果,而是在后面的代码中根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数

    def lazy_sum(*args):
        def sum():
            ax = 0
            for n in args:
                ax += n
            return ax
        return sum

      当我们调用lazy_sum()时,返回的不是求和结果而是求和函数,当我们调用函数f时,才真正计算求和的结果

    f = lazy_sum(1,2,3,4)
    print(f) # result <function lazy_sum.<locals>.sum at 0x0000025503463AE8>
    print(f()) #result 10

     闭包

      在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数sum()中,这就是“闭包(Closure)”。

      注意:当我们调用lazy_sum()时,每次调用都会返回一个新的函数即使传入相同的参数:

    f1 = lazy_sum(1,2,3,4)
    f2 = lazy_sum(1,2,3,4)
    print(f1==f2) #result False
    Tips:当一个函数返回一个函数后其内部的局部变量还被新函数所引用,所以闭包用着简单,但实现可不容易。返回的函数也不是立即执行的,直到调用返回的函数,才会执行内部的代码。

      但此时我们不一定理解清楚了"闭包",不信来看个例子:

    def count():
        fs = []
        for i in range(1, 4):
            def f():
                return i * i
            fs.append(f)
        return fs
    
    f1, f2, f3 = count()
    print(f1())
    print(f2())
    print(f3())

      在上面的例子中,每次循环都创建了一个新的函数,然后把创建的3个函数都返回了。你可能认为调用f1(),f2()f3()的结果应该是1, 4, 9 但实际结果却是:9, 9 ,9。为什么呢?原因在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,所以结果是9。

    Tips:返回闭包时要牢记一点:返回函数不要引用任何的循环变量,或者后续会发生变化的变量。

      但是如果我们一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,这样,无论该循环变量后续任何更改,已经绑定到函数参数的值不变

    def count():
        def f(j):
            def g():
                return j * j
            return g
        fs = []  #容器用来存储函数
        for i in range(1, 4):
            fs.append(f(i))
        return fs
    
    f1, f2, f3 = count()  #等价f1=count()[0], f2=count()[1], f3=count()[2]
    print(f1(), f2(), f3()) #result 1 4 9
    Tips:一个函数可以返回一个计算结果,也可以返回一个函数;返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会发生的变量。

    匿名函数

      为什么要使用匿名函数?匿名函数没有名字不需要显式定义函数不必担心函数名冲突,使用起来更加的方便。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)函数外,还可以直接传入匿名函数:

    >>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
    [1, 4, 9, 16, 25, 36, 49, 64, 81]

      通过对比可以看出,匿名函数lambda x: x * x实际上就是:

    def f(x):
        return x * x

      关键字lambda表示匿名函数,冒号前面的x表示参数。

    Tips:匿名函数只有一个表达式不用写return,返回值就是该表达式的结果。除此之外,和其它函数一样也可以赋值给变量,也可以作为返回值返回。Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。

    装饰器

      装饰器本质上是一个返回函数高阶函数,它可以让其他函数在不需要做任何代码变动下增加额外的功能:包括插入日志、性能测试、事务处理、缓存、权限校验等,这种在代码运行期间动态增加功能方式,称之为装饰器Decorator)。来看一个打印日志的decorator的定义:

    def log(func): #接收一个函数func作为参数--高阶函数的特性
        def wrapper(*args, **kw):  #可以接受任意参数的调用
            print('call %s():' % func.__name__) #func.__name__ 可以拿到函数额名字
            return func(*args, **kw)
        return wrapper

      已经定义好了一个decorator,怎么用呢?我们要借助Python的@语法,把decorator置于函数的定义处:

    @log
    def now():
        print('2018-04-19')

      调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志

    now()
    '''
    call now():
    2018-04-19
    '''

      @log放到now()函数的定义处,相当于执行了语句:

    now = log(now)

      解释:由于log()是一个decorator,返回一个函数,所以原来的now()函数仍然存在,只是现在同名的now指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

       再来看一种情况:若decorator的本身就需要传入参数的话,又该如何实现呢?此时,就应该编写一个返回decorator高阶函数。如,要自定义log的文本:

    def log(text):
        def decorator(func):
            def wrapper(*args, **kw):
                print('%s %s():' % (text,func.__name__))
                return func(*args, **kw)
            return wrapper
        return decorator

      这个3层嵌套的decorator的用法如下:

    @log('DefineText')
    def now():
        print('2018-04-19')

      执行结果如下:

    now()
    '''
    DefineText now():
    2018-04-19
    '''

      和两层嵌套的decorator相比,3层嵌套的decorator效果是这样:

    now = log('DefineText')(now)

      剖析:首先执行log(‘DefineText’),返回的是decorator函数,再调用返回的decorator函数,参数是now函数,最后返回值是wrapper函数。

      注意:函数也是对象,它有__name__等属性,但你看经过上面decorator装饰后的函数,它们的__name__已经从原来的'now'变成了'wrapper'。

    print(now.__name__) # wrapper

      哦,原来是因为最后返回的函数wrapper()函数名字‘wrapper’替换了原来的‘now’,这样有可能依赖函数签名的代码执行可能会出错,所以应该把__name__属性改回去,幸好Python内置的functools.wraps可以帮我们实现,所以一个完整的decorator的写法如下:

    #不带参数
    import functools
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    #带参数
    import functools
    
    def log(text):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                print('%s %s():' % (text,func.__name__))
                return func(*args, **kw)
            return wrapper
        return decorator

    小结:在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承组合来实现。而Python除了能支持OOP的decorator外,直接从语法层次支持decorator,可以用函数实现,也可以用实现。

    偏函数(Partial function)

      在前面说过,通过设定函数的默认值,可以降低函数调用的难度,偏函数可以为已知的函数设定参数的默认值。来看看怎么操作:

    >>> import functools  
    >>> int2 = functools.partial(int, base=2)  #functools.partial帮助我们创建偏函数
    >>> int2('1000000')
    64

      注意到上面的int2函数,仅仅是把base参数设定默认值为2,但也可在函数调用时传入其它的值:

    >>> int2('1000000', base=10)
    1000000

      最后注意,创建偏函数时,实际可以接受函数对象*args、和**kw、3个参数。如上面的创建的int2函数,实际上固定了int()函数的关键字参数base,也就是:

    int2('10010') 

      相当于这样:

    kw = { 'base': 2 }
    int('10010', **kw)
    Tips:当函数的参数个数太多时,需要简化时,使用functools.partial创建一个新的函数,这个新函数固定了原函数的部分参数,使调用时更加的简洁。
  • 相关阅读:
    [BZOJ1584] [Usaco2009 Mar]Cleaning Up 打扫卫生(DP)
    [BZOJ1583] [Usaco2009 Mar]Moon Mooing 哞哞叫(队列)
    [BZOJ1582] [Usaco2009 Hol]Holiday Painting 节日画画(线段树)
    [BZOJ1579] [Usaco2009 Feb]Revamping Trails 道路升级(分层图最短路 + 堆优化dijk)
    [ZPG TEST 115] 字符串【归类思想】
    [ZPG TEST 114] 阿狸的英文名【水题】
    [USACO 2012 Open Gold] Bookshelf【优化dp】
    [USACO 2012 Mar Silver] Landscaping【Edit Distance】
    [USACO 2012 Mar Gold] Large Banner
    [USACO 2012 Feb Gold] Cow Coupons【贪心 堆】
  • 原文地址:https://www.cnblogs.com/yunche/p/8881299.html
Copyright © 2011-2022 走看看