zoukankan      html  css  js  c++  java
  • Python学习笔记(四)函数式编程

    高阶函数(Higher-order function)

    Input:

    1
    abs

    Output:

    1
    <function abs>

    Input:

    1
    abs(-10)

    Output:

    1
    10

    abs是函数本书,abs(-10)是函数调用

    Input:

    1
    2
    f = abs
    f

    Output:

    1
    <function abs>

    变量可以指向函数

    Input:

    1
    f(-10)

    Output:

    1
    10

    函数名也是变量

    函数名其实就是指向函数的变量,对于abs()这个函数,完全可以把abs看成变量,它指向一个可以计算绝对值的函数,如果把abs指向其他对象,将无法再调用计算绝对值的函数

    Input:

    1
    2
    abs = 10
    abs(-10)

    Output:

    1
    2
    3
    4
    5
    6
    ---------------------------------------------------------------------------
    TypeError Traceback (most recent call last)
    <ipython-input-6-dae1c1c42fe8> in <module>()
    1 abs = 10
    ----> 2 abs(-10)
    TypeError: 'int' object is not callable

    abs函数实际上是定义在import builtins模块中的

    传入函数

    一个函数接收另外一个函数作为参数,这种函数就称之为高阶函数

    Input:

    1
    2
    def (x, y, f):
    return f(x) + f(y)
    1
    print(add(-5, 6, abs))

    Output:

    1
    11

    map/reduce

    基本知识

    map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

    Input:

    1
    2
    3
    4
    5
    def f(x):
    return x*x

    r = map(f, [1,2,3,4,5,6,7,8,9])
    list(r)

    Output:

    1
    [1, 4, 9, 16, 25, 36, 49, 64, 81]

    reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

    reduce(f, [x1, x2, x3, x4]) = f(f(f(f(x1, x2), x3), x4)

    对一个序列求和,就可以用reduce实现

    Input:

    1
    2
    3
    4
    5
    from functools import reduce
    def (x,y):
    return x+y

    reduce(add, [1,3,5,7,9])

    Output:

    1
    25

    我们配合map(),可以写出把str转换为int的函数:

    Input:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from functools import reduce
    def fn(x, y):
    return x*10+y

    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, '13579'))

    Output:

    1
    13579

    整理成一个str2int的函数就是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from functools import reduce

    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))

    还可以用lambda函数进一步简化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from functools import reduce

    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))

    练习

    利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]:

    Input:

    1
    2
    3
    4
    def normalize(name):
    def specilization(s):
    return s.casefold().capitalize()
    return map(specilization, name)
    1
    2
    res = normalize(['adam', 'LISA', 'barT'])
    list(res)

    Output:

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

    Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:

    Input:

    1
    2
    3
    from functools import reduce
    def prod(L):
    return reduce(lambda x,y: x*y, L)
    1
    prod([3,5,7,9])

    Output:

    1
    945

    利用mapreduce编写一个str2float函数,把字符串123.456转换成浮点数123.456

    Input:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    from functools import reduce
    TRANS = {'1':1, '2':2, '3':3, '4':4, '5':5, '6':6}
    def str2float(s):
    s2 = s.split('.')
    def point_left(x, y):
    return x*10 + y
    def char2num(s):
    return TRANS[s]
    left2int = reduce(point_left, map(char2num, s2[0]))
    right2int = reduce(point_left, map(char2num, s2[1]))
    while(right2int > 1):
    right2int = right2int/10
    return left2int + right2int
    1
    2
    3
    4
    5
    print('str2float('123.456') =', str2float('123.456'))
    if abs(str2float('123.456') - 123.456) < 0.00001:
    print('测试成功!')
    else:
    print('测试失败!')

    Output:

    1
    2
    str2float('123.456') = 123.456
    测试成功!

    filter

    基本知识

    filter()函数用于过滤序列。和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

    例如,在一个list中,删掉偶数,只保留奇数,可以这么写

    Input:

    1
    2
    3
    4
    def is_odd(n):
    return n % 2 ==1

    list(filter(is_odd, [1,2,4,5,6,9,10,15]))

    Output:

    1
    [1, 5, 9, 15]

    把一个序列中的空字符串删掉

    Input:

    1
    2
    3
    4
    def not_empty(s):
    return s and s.strip()

    list(filter(not_empty, ['A','','B',None,'C',' ']))

    Output:

    1
    ['A', 'B', 'C']

    注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list

    用filter求素数

    用python实现使用埃氏筛法计算素数

    可以先构造一个从3开始的奇数序列,这是一个生成器,并且是一个无限序列

    1
    2
    3
    4
    5
    def _odd_iter():
    n = 1
    while True:
    n = n+2
    yield n

    然后定义一个筛选函数

    1
    2
    def _not_divisible(n):
    return lambda x: x%n > 0

    最后,定义一个生成器,不断返回下一个素数

    1
    2
    3
    4
    5
    6
    7
    def primes():
    yield 2
    it = _odd_iter() #初始序列
    while True:
    n = next(it) #返回序列的第一个数
    yield n
    it = filter(_not_divisible(n), it) #构造新序列
    1
    2
    3
    4
    5
    6
    # 打印30以内的素数:
    for n in primes():
    if n < 30:
    print(n)
    else:
    break

    Output:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    2
    3
    5
    7
    11
    13
    17
    19
    23
    29

    注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。

    练习

    回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:

    Input:

    1
    2
    def is_palindrome(n):
    return str(n) == str(n)[::-1]
    1
    2
    3
    4
    5
    6
    output = filter(is_palindrome, range(1, 1000))
    print('1~1000:', list(output))
    if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 大专栏  Python学习笔记(四)函数式编程ber">141, 151, 161, 171, 181, 191]:
    print('测试成功!')
    else:
    print('测试失败!')

    Output:

    1
    2
    1~1000: [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, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999]
    测试成功!

    filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。

    sorted

    基本知识

    Python内置的sorted()函数可以对list进行排序

    Input:

    1
    sorted([36, 5, -12, 9, -21])

    Output:

    1
    [-21, -12, 5, 9, 36]

    sorted()函数也是一个高阶函数,可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序

    Input:

    1
    sorted([36, 5, -12, 9, -21], key=abs)

    Output:

    1
    [5, 9, -12, -21, 36]

    对字符串进行排序

    Input:

    1
    sorted(['bob', 'about', 'Zoo', 'Credit'])

    Output:

    1
    ['Credit', 'Zoo', 'about', 'bob']

    实现忽略大小写的排序

    Input:

    1
    sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)

    Output:

    1
    ['about', 'bob', 'Credit', 'Zoo']

    进行反向排序

    Input:

    1
    sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)

    Output:

    1
    ['Zoo', 'Credit', 'bob', 'about']

    练习

    假设我们用一组tuple表示学生名字和成绩:
    L = [(‘Bob’, 75), (‘Adam’, 92), (‘Bart’, 66), (‘Lisa’, 88)]
    请用sorted()对上述列表分别按名字排序:

    Input:

    1
    2
    3
    4
    5


    L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
    def by_name(t):
    return sorted(L, key = lambda x: x[0])
    1
    by_name(L)

    Output:

    1
    [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

    再按成绩从高到低排序

    Input:

    1
    2
    def by_score(t):
    return sorted(L, key = lambda x: x[1])
    1
    by_score(L)

    Output:

    1
    [('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)]

    返回函数

    基本知识

    高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回

    定义一个求和函数,不需要立刻得到求和结果,而是在后面的代码中根据需要再计算。

    1
    2
    3
    4
    5
    6
    7
    def lazy_sum(*args):
    def sum():
    ax = 0
    for n in args:
    ax = ax + n
    return ax
    return sum

    当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数

    Input:

    1
    f = lazy_sum(1,3,5,7,9)
    1
    f

    Output:

    1
    <function __main__.lazy_sum.<locals>.sum>

    当调用函数f时,才真正计算求和的结果

    Input:

    1
    f()

    Output:

    1
    25

    在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

    闭包

    注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。

    Input:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def count():
    fs = []
    for i in range(1,4):
    def f():
    return i*i
    fs.append(f)
    return fs

    f1, f2, f3 = count()

    Input:

    1
    f1()

    Output:

    1
    9

    Input:

    1
    f2()

    Output:

    1
    9

    Input:

    1
    f3()

    Output:

    1
    9

    我们可能会认为调用f1()f2()f3()结果应该是149,但实际结果全是9。原因就在于返回的函数引用了变量i,但它非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

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

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

    Input:

    1
    2
    3
    4
    5
    6
    7
    8
    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
    1
    f1, f2, f3 = count()

    Input:

    1
    f1()

    Output:

    1
    1

    Input:

    1
    f2()

    Output:

    1
    4

    Input:

    1
    f3()

    Output:

    1
    9

    练习

    利用闭包返回一个计数器函数,每次调用它返回递增整数:

    Input:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    def createCounter():
    def f():
    i = 0
    while True:
    i = i +1
    yield i
    sun = f()
    def counter():
    return next(sun)
    return counter
    1
    2
    3
    4
    5
    6
    7
    8
    # 测试:
    counterA = createCounter()
    print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
    counterB = createCounter()
    if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print('测试通过!')
    else:
    print('测试失败!')

    Output:

    1
    2
    1 2 3 4 5
    测试通过!

    匿名函数

    基本知识

    当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。关键字lambda表示匿名函数,冒号前面的x表示函数参数

    匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数

    Input:

    1
    2
    f = lambda x: x*x
    f(5)

    Output:

    1
    25

    练习

    请用匿名函数改造下面的代码:

    Input:

    1
    2
    3
    4
    5

    def is_odd(n):
    return n % 2 == 1

    L = list(filter(is_odd, range(1, 20)))
    1
    L

    Output:

    1
    [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

    Input:

    1
    2

    L = list(filter(lambda x: x%2 == 1, range(1, 20)))
    1
    L

    Output:

    1
    [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

    装饰器

    假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)

    本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下

    1
    2
    3
    4
    5
    def log(func):
    def wrapper(*args, **kw):
    print('call %s():' % func.__name__)
    return func(*args, **kw)
    return wrapper

    观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处

    1
    2
    3

    def now():
    print('2018.01.05')

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

    Input:

    1
    now()

    Output:

    1
    2
    call now():
    2018.01.05

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

    如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本

    1
    2
    3
    4
    5
    6
    7
    def log(text):
    def decorator(func):
    def warpper(*args, **kw):
    print('%s %s():' %(text, func.__name__))
    return func(*args, **kw)
    return warpper
    return decorator
    1
    2
    3
    @log('execute')
    def now():
    print('2015-3-25')

    Input:

    1
    now()

    Output:

    1
    2
    execute now():
    2015-3-25

    和两层嵌套的decorator相比,三层嵌套的效果是now = log('execute')(now)

    偏函数

    Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

    创建一个把二进制的字符串转换为整数的偏函数

    Input:

    1
    2
    3
    import functools
    int2 = functools.partial(int, base=2)
    int2('10000')

    Output:

  • 相关阅读:
    JVM垃圾回收算法
    Java内存堆和栈的区别?
    JDK8为何要废弃永久代
    配置环境变量的一些坑
    在配置环境变量时是该用系统环境变量还是用户环境变量?
    RPC(远程过程调用协议)介绍
    如何修改博客园“插入代码”中的代码风格?(一个博客园代码高亮的方案)
    如何修改博客园插入代码的默认代码大小?
    Java值传递
    使用VS2017实现C#第一个代码
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12046833.html
Copyright © 2011-2022 走看看