Python(四)


函数是Python内建支持的一种封装,通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

函数式编程

  1. 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
  2. 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。
  3. Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

一、高阶函数

  1. 高阶函数(Higher-order function):

  2. 函数本身也可以赋值给变量,即:变量可以指向函数。

    f = abs
    abs(-10)
    f(-10)
  3. 函数名也是变量,函数名其实就是指向函数的变量。

  4. 既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

    def add(x, y, f):
        return f(x) + f(y)
    
    a = add(-5, 6, abs)
    # 参数x,y和f分别接收-5,6和abs
    print(a) # 11
  5. map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

    def f(x):
        return x * x
    r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
    list(r)
    # map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
    L = []
    for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
        L.append(f(n))
    print(L)
    # map()作为高阶函数,事实上它把运算规则抽象了
    # 计算任意复杂的函数,比如,把这个list所有数字转为字符串:
    list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
    # ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
  6. reduce把一个函数作用在一个序列[x1,x2,x3,…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。

    # 效果:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1,x2) ,x3), x4)
    from functools import reduce
    def add(x, y):
        return x + y
    reduce(add, [1, 3, 5, 7 ,9])
    # 求和可以直接用sum()
    # 把序列[1, 3, 5, 7, 9]变换成整数13579
    def fn(x, y):
        return x * 10 + y
    reduce(fn, [1,3,5,7,9])
    
    # 把str转换为int的函数
    def char2num(s):
        digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
        return digits[s]
    
    reduce(fn, map(char2num, '246'))
    
    # 整理成一个str2int的函数
    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    def str2int(s):
        def fn(x, y):
            return x * 10 + y
        def char2num(s):
            return DIGITS[s]
        return reduce(fn, map(char2num, s))
    str2int('13579')
    
    # 用lambda函数进一步简化
    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    def char2num(s):
        return DIGITS[s]
    def str2int(s):
        return reduce(lambda x, y: x * 10 + y, map(char2num, s))
  7. filter()函数用于过滤序列,接收一个函数和一个序列,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

    # 1.在一个list中,删掉偶数,只保留奇数。
    def is_odd(n):
        return n % 2 == 1
    list(filter(is_odd, [1,2,3,4,5,6,7,8,9]))
    # [1, 3, 5, 7, 9]
    # 2.删除一个序列中的空字符
    def not_empty(s):
        return s and s.strip()
    list(filter(not_empty, ['A', '', 'b', 'C', None, '  ']))
    # ['A', 'b', 'C']
    # strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
    str1.strip('h') #删除str1中首尾的h
  8. 埃氏筛法求素数。埃拉托斯特尼筛法:要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。

    def main():
        for n in primes():
            if n < 1000:
                print(n)
            else:
                break
    # 输出1000以内的素数
    def _odd_iter(): #构造从3开始的奇数序列
        n = 1
        while True:
            n = n + 2
            yield n
    
    def _not_divisible(n): #定义筛选函数
        return lambda x: x % n > 0
    
    def primes(): #定义生成器不断返回下一个素数
        yield 2
        it = _odd_iter() #初始序列
        while True:
            n = next(it)
            yield n
            it = filter(_not_divisible(n), it)
        #这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列
    
    if __name__ == '__main__' :
        main()
  9. filter()的作用是从一个序列中筛出符合条件的元素。

  10. sorted()函数排序算法,是一个高阶函数,关键在于实现一个映射函数。它还可以接收一个key函数来实现自定义的排序。key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。

  11. 要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

    sorted([36,15,2,48,25])    
    sorted([36,15,2,-48,25,-29], key=abs) # 按绝对值大小排序
    sorted(['NUS', 'GLY', 'about', 'Dau','bob']) #字符串排序,依次比较ASCII码值
    sorted(['NUS', 'GLY', 'about', 'Dau','bob'], key=str.lower) # 排序忽略大小写,可将字符串都改为大写或小写
    sorted(['NUS', 'GLY', 'about', 'Dau','bob'], key=str.lower, reverse=True) # 反向排序
    
    from operator import itemgetter
    students = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
    print(sorted(students, key=itemgetter(0)))
    print(sorted(students, key=lambda t: t[1]))
    print(sorted(students, key=itemgetter(1), reverse=True))

二、返回函数

  1. 函数作为返回值,高阶函数可以接收函数作为参数,也可以把函数作为一个返回值。

    def sum(*args):
        def add():
            s = 0
            for i in args:
                s = s + i
            return s
        return add
    f = sum(2,4,6,8,10)
    print(f()) # 30
  2. 内部函数可以引用外部函数的参数和局部变量,上例中sum返回函数add时,相关参数和变量都保存在返回函数中。

    f1 = sum(2,4,6,8,10)
    print(f1 == f) # False
    # 每次调用都会返回一个新的函数,即使传入相同的参数,f1()和f()的调用结果互不影响。
  3. 闭包:在Python中,闭包也被称为闭包函数或者闭合函数,与局部函数类似,不同之处在于,闭包中外部函数返回的不是一个具体的值,而是一个函数。一般情况下,返回的函数会赋值给一个变量,这个变量可以在后面被继续执行调用。

  4. 返回闭包时:返回函数不要引用任何循环变量,或者后续会发生变化的变量

    def count():
        fs = []
        for i in range(1,4):
            def f():
                return i * i
            fs.append(f)
        return fs
    f1, f2, f3 = count()
    f1()
    f2()
    f3() # 全部都是9 返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

    若要引用循环变量,再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。

    def count():
        def f(j):
            def g():
                return j*j
            return g
        fs = []
        for i in range(1, 4):
            fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
        return fs
    f1, f2, f3 = count()
    print(f1()) # 1
    print(f2()) # 4
    print(f3()) # 9 缺点是代码较长,可利用lambda函数缩短代码。
  5. 使用闭包时,内层函数对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。

    def inc(): #局部函数fn()和局部函数引用到的函数外的x形成一个整体,构成了闭包
        x = 0
        def fn():
            nonlocal x
            x = x + 1
            return x
        return fn
    f = inc()
    print(f()) # 1
    print(f()) # 2
  6. 闭包的记忆功能,在 Python 中,被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆功能。

  7. 闭包就是一个函数和与其相关的引用环境组合的一个整体。

三、匿名函数

  1. 关键字lambda表示匿名函数,冒号前面的x表示函数参数。
  2. 不必担心函数名冲突。
  3. 在传入函数时,有时不需要显式地定义函数,直接传入匿名函数更方便。

四、装饰器

  1. 在代码运行期间动态增加功能的方式,称之为装饰器(Decorator)。
  2. 在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
  3. decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

五、偏函数

  1. 偏函数(Partial function):通过设定参数的默认值,可以降低函数调用的难度。

  2. functools.partial创建一个偏函数。

    import functools
    int2 = functools.partial(int, base=2)
    int2('10101100')
    int16 = functools.partial(int, base=16)
    int16('20')
  3. 当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。


文章作者: nusqx
文章链接: https://nusqx.top
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 nusqx !
评论
  目录