• python:函数的高级特性


    很多语言中,都允许把函数本身做为参数,传递给其它参数:即所谓的高阶函数。python中也有类似特性:

    一、map/reduce、filter、sorted

    hadoop里的map-reduce思想在python里已经变成内置函数了。map是将某个函数逐一作用于列表中的每个元素。reduce则先从列表中取头2个元素,传到指定函数,然后将计算结果与余下元素依次重复,直到List处理完。
     
    1.1 map示例:(将List中的所有元素*10)
    def fn_map(x):
        print("fn_map->", x)
        return 10 * x
    
    
    L = [3, 4, 6, 8]
    
    print(list(map(fn_map, L)))
    print("
    ")
    

    输出:

    fn_map-> 3
    fn_map-> 4
    fn_map-> 6
    fn_map-> 8
    [30, 40, 60, 80]
    

    结合map,我们再把reduce函数加上(最终效果:将所有元素*10再平方,最终得出 “平方和”的"平方根")

    def fn_sqrt(x, y):
        print("fn_sqrt->", x, ",", y)
        return math.sqrt(x ** 2 + y ** 2)
    
    
    def fn_map(x):
        print("fn_map->", x)
        return 10 * x
    
    
    L = [3, 4, 6, 8]
    
    result = reduce(fn_sqrt, map(fn_map, L))
    print(result)
    
    print("
    ")
    print(math.sqrt((3 * 10) ** 2 + (4 * 10) ** 2 + (6 * 10) ** 2 + (8 * 10) ** 2))
    

     注:要先import math,上面的代码输出如下:

    fn_map-> 3
    fn_map-> 4
    fn_sqrt-> 30 , 40
    fn_map-> 6
    fn_sqrt-> 50.0 , 60
    fn_map-> 8
    fn_sqrt-> 78.10249675906654 , 80
    111.80339887498948
    
    
    111.80339887498948
    

    上面这个例子,可能实用性不大,下面给个实用性更强的示例,将每个单词的首字母大写,其它字母变小写。

    def normalize(name):
        return name[:1].upper() + name[1:].lower()
    
    
    L1 = ['adam', 'LISA', 'barT']
    print(list(map(normalize, L1)))
    

    输出:

    ['Adam', 'Lisa', 'Bart']

    1.2 filter

    filter跟java8里的stream的filter是类似的,可以实现对集合中的元素,按某种规则进行筛选。

    示例1:找出10以内的偶数

    result = filter(lambda x: x % 2 == 0, range(1, 11))
    print(list(result))
    
    
    # 上面的写法,等效于下面这个
    def even(x):
        return x % 2 == 0
    
    
    print(list(filter(even, range(1, 11))))
    

    输出:

    [2, 4, 6, 8, 10]
    [2, 4, 6, 8, 10]
    

    示例2:找出200以内的"回数"(即:从左向右,从右向左,都是一样的数,比如:131, 141)

    def is_palindrome1(n):
        if n < 10:
            return True
        s = str(n)
        for i in range(0, int(len(s) / 2)):
            if s[i] == s[-i - 1]:
                return True
        return False
    
    
    def is_palindrome2(n):
        s1 = str(n)
        s2 = list(reversed(s1))
        return list(s1) == s2
    
    
    print(list(filter(is_palindrome1, range(1, 201))))
    print(list(filter(is_palindrome2, range(1, 201))))
    

    输出:

    [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]
    

      

    1.3 sorted

    python内置的排序函数sorted,支持数字/字母/以及复杂对象排序,默认是从小到大排序,对于复杂对象的排序规则可以开发者自定义。参考下面的示例:

    origin = [-1, 3, -5, 2, -4, 6]
    # 从小到大排序
    a = sorted(origin)
    print(a)
    
    # 按abs绝对值,从小大到排序
    a = sorted(origin, key=abs)
    print(a)
    
    # 从大到小排序
    a = sorted(origin, reverse=True)
    print(a)
    
    origin = ["Xy", "Aa", "Bb", "dd", "cC", "aA", "Zo"]
    
    # 按字母ascii值从小到大排序
    print(sorted(origin))
    
    # 将字母转大写后的值排序(即:忽略大小写)
    print(sorted(origin, key=str.upper))
    
    # 将字母转大写后的值倒排序
    print(sorted(origin, key=str.upper, reverse=True))
    
    # 复杂对象排序
    origin = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
    
    
    def by_name(t):
        return t[0]
    
    
    # 按人名排序
    print(sorted(origin, key=by_name))
    
    
    def by_score(t):
        return t[1]
    
    
    # 按得分倒排
    print(sorted(origin, key=by_score, reverse=True))
    

    输出:

    [-5, -4, -1, 2, 3, 6]
    [-1, 2, 3, -4, -5, 6]
    [6, 3, 2, -1, -4, -5]
    ['Aa', 'Bb', 'Xy', 'Zo', 'aA', 'cC', 'dd']
    ['Aa', 'aA', 'Bb', 'cC', 'dd', 'Xy', 'Zo']
    ['Zo', 'Xy', 'dd', 'cC', 'Bb', 'Aa', 'aA']
    [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
    [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
    

    二、延迟计算/闭包

    python的函数定义可以嵌套(即:函数内部再定义函数),利用这个特性很容易实现延迟计算:

    import time
    
    
    def export1(month):
        print("export1 month:", month, " doing...")
        time.sleep(5)
        print("export1 done!")
    
    
    def export2(month):
        def do():
            print("export2 month:", month, " doing...")
            time.sleep(5)
            print("export2 done!")
    
        return do
    
    
    export1(10)
    
    print("----------------")
    
    r2 = export2(10)
    print(r2)
    r2()
    

    这里我们模拟一个耗时的导出功能(假设:要求传入月份,然后导出该月的报表数据),export1为常规版本,调用export1就会马上执行。而export2则是返回一个内部函数do(),调用export2后,返回的是一个Function,并没有实际执行(可以理解为: 返回的是业务处理算法,而非处理结果),真正需要结果的时候,再来调用"返回函数"。

    上面的代码输出如下:

    export1 month: 10  doing...
    export1 done!
    ----------------
    <function export2.<locals>.do at 0x107a24a60>
    export2 month: 10  doing...
    export2 done!
    

      

    闭包

    很多语言都支持闭包特性,python中当然少不了这个,参考下面的示例:

    def my_sqrt1(n):
        r = []
    
        def do():
            for i in range(1, n + 1):
                r.append(i ** 2)
            return r
    
        return do
    
    
    a = my_sqrt1(4)
    print(type(a))
    b = a()
    print(type(b))
    print(b)
    

    输出:

    <class 'function'>
    <class 'list'>
    [1, 4, 9, 16]  

    闭包有一个经典的坑:不要在闭包函数中使用“值会发生变化的变量"(比如:for循环中的变量)。原因是:python中的闭包本质上是是"内部函数"延时计算,如果有循环变量,循环过程中闭包函数并不会执行,等循环结束了,闭包中引用的循环变量其实是循环结束后最终的值。说起来有点绕口,看下面的示例:

    def my_sqrt2(n):
        r = []
        for i in range(1, n + 1):
            def do():
                r.append(i ** 2)
                return r
    
        return do
    
    
    a = my_sqrt2(4)
    print(type(a))
    b = a()
    print(type(b))
    print(b)
    

    输出:

    <class 'function'>
    <class 'list'>
    [16]
    

    解释一下:调用a = my_sqrt2(4)时,my_sqrt2(4)马上执行完了,这时候里面的fox循环执行完了,最后i的值停在4,然后这个值被封闭在do函数里,并没有马上执行。然后再调用a()时,这时候才真正调用do()函数,此时i值=4,所以最终r[]列表里,就只追回了一个值4*4=16 

    如果非要使用循环变量,只能想招儿把这个循环变量,也封闭到一个内部函数里,然后再使用,比如下面这样:

    def my_sqrt3(n):
        def f(j):
            def g():
                return j ** 2
    
            return g
    
        r = []
        for i in range(1, n + 1):
            r.append(f(i))
    
        return r
    
    
    a = my_sqrt3(4)
    print(type(a))
    
    for x in a:
        print(x())
    

    这个例子仔细研究下蛮有意思的,r.append(f(i)),列表里追加的并非计算结果,而是f(j)里返回的函数g,所以a = my_sqrt3(4)这里,a得到的是一个function组成的list,然后list里的每个g函数实例,都封闭了当次循环的变量i,因为闭包的缘故,i值已经被封印在g内部,不管外部的for循环如何变量,都不会影响函数g。

    输出如下:

    <class 'list'>
    1
    4
    9
    16
    

    关于闭包,最后再来看一个廖老师教程上的作业题,用闭包的写法写一个计数器:

    def create_counter1():
        r = [0]
    
        def counter():
            r[0] += 1
            return r[0]
    
        return counter
    
    
    count = create_counter1();
    
    print([count(), count(), count()])
    

    输出:

    [1, 2, 3] 

    对于有洁癖的程序员,可能会觉得要额外设置一个只保存1个元素的list,有点浪费。可以换种写法:

    def create_counter2():
        n = 0
    
        def counter():
            nonlocal n
            n += 1
            return n
    
        return counter
    
    count = create_counter2();
    
    print([count(), count(), count()])
    

    输出:

    [1, 2, 3]  

    注意这里有一个关键字nonlocal,号称是python3新引入的关键字,为的是让闭包的内部函数里面,能读写内部函数外的变量。(但是在第1种写法中,r=[0]不也是定义在外部么?区别就是list是复杂的变量类型,而第2种写法中n是简单类型的变量,做为python初学者,不是很理解这个哲学思想^_~)

    三、aop/装饰器

    aop是java生态体系中的精髓之一,而python里同样能做到,而且看上去更简洁。

    比如有一个加法函数:

    def add1(i, j):
        return i + j  

    想在add1调用时,自动把入参,返回结果,以及执行时间都记录下来,可以这么做,再定义一个log函数(类似java中的aspect切面定义)

    import time
    
    def log(fn):
        def do(*args, **kw):
            start = time.time()
            result = fn(*args, **kw)
            end = time.time()
            print("function=>", fn.__name__, ",args1=>", args, ",args2=>", kw, ",result=>", result, ",exec_time=>",
                  (end - start) * 1000, "ms")
            return result
    
        return do  

    然后在add1函数上加上这个"注解"就可以了

    @log
    def add1(i, j):
        return i + j
    
    
    print(add1(1, 2))
    

    输出:

    function=> add1 ,args1=> (1, 2) ,args2=> {} ,result=> 3 ,exec_time=> 0.0030994415283203125 ms
    3
    

    如果aop本身的"切面"也需要传参数进来,比如:在日志前想附加一段特定的前缀,可以参考下面这样:

    import time
    
    
    def log(fn):
        def do(*args, **kw):
            start = time.time()
            result = fn(*args, **kw)
            end = time.time()
            print("function=>", fn.__name__, ",args1=>", args, ",args2=>", kw, ",result=>", result, ",exec_time=>",
                  (end - start) * 1000, "ms")
            return result
    
        return do
    
    
    def log2(log_prefix):
        def around(fn):
            def do(*args, **kw):
                start = time.time()
                result = fn(*args, **kw)
                end = time.time()
                print(log_prefix, ",function=>", fn.__name__, ",args1=>", args,
                      ",args2=>", kw, ",result=>", result, ",exec_time=>",
                      (end - start) * 1000, "ms")
                return result
    
            return do
    
        return around
    
    
    @log2("调用日志:")
    @log
    def add2(i, j):
        time.sleep(1)
        return i + j
    
    
    print(add2(1, 2))
    

    输出:

    function=> add2 ,args1=> (1, 2) ,args2=> {} ,result=> 3 ,exec_time=> 1000.8599758148193 ms
    调用日志: ,function=> do ,args1=> (1, 2) ,args2=> {} ,result=> 3 ,exec_time=> 1001.0490417480469 ms
    3
    

    注:这里我们刻意把log,log2同时运用在add2上,从输出上看,二个aspect都起作用了。

    四、偏函数

    还是拿add(i,j) 这个来说事儿吧,如果我们经常会遇到一个场景:想让某个数字固定+10,在其它语言里,通常是再定义一个类似add_10(i)的重载版本(java/c#都是这么干的),但是对于python来说,可以更优雅:

    import functools
    
    
    def add(i, j):
        return i + j
    
    
    # 偏函数
    add_10 = functools.partial(add, j=10)
    
    print(add(1, 2))
    print(add_10(1))
    

    输出:

    3
    11
    

    这种把参数列表中的某些常用项固定,然后再生成一个别名函数的玩法,就称为偏函数

      

    参考文档:

    1、廖雪峰的python教程:函数式编程

  • 相关阅读:
    android中给TextView或者Button的文字添加阴影效果
    android:layout_weight详解
    android Button 颜色的变化(点击,放开,点击不放)
    Android之最简单的ImageView加边框方法
    泳道图
    使用Navicat生成ER关系图并导出
    IDEA须知
    Error running Tomcat8: Address localhost:1099 is already in use(IDEA错误)
    3分钟打动投资人:商业计划书篇
    HTML按钮属性
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/advanced-feature-of-python-function.html
Copyright © 2020-2023  润新知