• 21.Python函数(六)【函数式编程 下半篇】


    每篇前言:


    在这里插入图片描述

    Python函数(六)

    1.1 函数式编程

    1.1.1 闭包

    • 注意到在上篇文章中的【函数作为返回值】那一节中返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
    • 另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
    def count():
        fs = []
        for i in range(1, 4):
            def f():
                 return i*i
            fs.append(f)
        return fs
    
    f1, f2, f3 = count()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
      你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
    >>> f1()
    9
    >>> f2()
    9
    >>> f3()
    9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。
    • 返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
    • 如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
    def count():
        fs = []
        for i in range(1, 4):
            def f(j):
                def g():
                    return j*j
                return g
            fs.append(f(i))
        return fs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    >>> f1, f2, f3 = count()
    >>> f1()
    1
    >>> f2()
    4
    >>> f3()
    9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 缺点是代码较长,可利用lambda表达式缩短代码:
    # -*- coding: utf-8 -*-
    """
    __author__ = 小小明-代码实体
    """
    def count():
        fs = []
        for i in range(1, 4):
            f = lambda j: (lambda: j * j)
            fs.append(f(i))
        return fs
    
    f1, f2, f3 = count()
    print(f1(), f2(), f3())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    1.1.2 装饰器

    下面讲解所使用的完整示例:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 函数对象有一个__name__属性,可以拿到函数的名字:
    >>> def now():
    ...     print('2015-3-25')
    ...
    >>> now.__name__
    'now'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
    • 本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
    def log(func):
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。
    • 观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
    @log
    def now():
    	print('2015-3-25')
    
    • 1
    • 2
    • 3
    • 把@log放到now()函数的定义处,相当于执行了语句:
    now = log(now)
    
    • 1
    • 调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
    >>> now()
    call now():
    2015-3-25
    
    • 1
    • 2
    • 3
    • 如果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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 这个3层嵌套的decorator用法如下:
    @log('execute')
    def now():
        print('2015-3-25')
    
    • 1
    • 2
    • 3
    • 和两层嵌套的decorator相比,3层嵌套的效果是这样的:
    >>> now = log('execute')(now)
    
    • 1

    执行结果如下:

    >>> now()
    execute now():
    2015-3-25
    
    • 1
    • 2
    • 3
    • 以上两种decorator的定义都没有问题,但经过decorator装饰之后的函数,它们的__name__已经从原来的’now’变成了’wrapper’:
    >>> now.__name__
    'wrapper'
    
    • 1
    • 2
    • Python内置的functools.wraps能达到wrapper.name = func.__name__的效果,所以,一个完整的decorator的写法如下:

    解释一下就是:Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变,比如上面你会发现函数名变成了wrapper),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和函数属性

    import functools
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 或者针对带参数的decorator:
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 练习
      设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间,可以计算任何函数执行时间:
    # -*- coding: utf-8 -*-
    """
    __author__ = 小小明-代码实体
    """
    import functools, time
    
    def metric(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kw):
            start_time = time.time() * 1000
            result = fn(*args, **kw)
            run_time = time.time() * 1000 - start_time
            print('%s executed in %s ms' % (fn.__name__, run_time))
            return result
        return wrapper
    
    @metric
    def fast(x, y):
        time.sleep(0.003)
        return x + y
    
    @metric
    def slow(x, y, z):
        time.sleep(0.1257)
        return x * y * z
    
    f = fast(11, 22)
    s = slow(11, 22, 33)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述

    1.1.3 偏函数

    • 假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
    def int2(x, base=2):
        return int(x, base)
    
    • 1
    • 2
    • functools.partial可以创建一个偏函数,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
    >>> import functools
    >>> int2 = functools.partial(int, base=2)
    >>> int2('1000000')
    64
    >>> int2('1010101')
    85
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数,当传入:
    int2 = functools.partial(int, base=2)
    
    • 1
    • 实际上固定了int()函数的关键字参数base,也就是:
    int2('10010')
    
    • 1
    • 相当于:
    kw = { 'base': 2 }
    int('10010', **kw)
    
    • 1
    • 2
    • 当传入:
    max2 = functools.partial(max, 10)
    
    • 1
    • 实际上会把10作为*args的一部分自动加到左边,也就是:
    max2(5, 6, 7)
    
    • 1
    • 相当于:
    args = (10, 5, 6, 7)
    max(*args)
    
    • 1
    • 2
    • 结果为10。
  • 相关阅读:
    TypeScript 小结
    正点原子嵌入式linux驱动开发——TF-A使用
    C和指针 第11章 动态内存分配 11.11 编程练习
    深入详解高性能消息队列中间件 RabbitMQ
    【UniApp】-uni-app-全局数据和局部数据
    简单环(状压dp)-----Java题解
    VUE-npm ERR! C:\rj\node-v14.4.0-win-x64\nod
    endpoint=DefaultEndpoint{ serviceUrl=‘http://127.0.0.1:10086/eureka/
    【译】如何在 Visual Studio 中安装 GitHub Copilot
    eclipse启动tomcat是出现Server Tomcat v9.0 Server at localhost failed to start.错误
  • 原文地址:https://blog.csdn.net/qq_44907926/article/details/125370454