廖雪峰的python教程(2)

教程网址: https://www.liaoxuefeng.com/wiki/1016959663602400

函数式编程

1.高阶函数: 将函数作为参数的函数

1.1 map,reduce

如果你读过Google的那篇大名鼎鼎的论文 “MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce的概念。

map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,map()作为高阶函数,事实上它把运算规则抽象了.

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

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

from functools import reduce

def f(x):
    return x*x

r = [f(x) for x in range(10)]
print(r) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# map
r = map(f,[x for x in range(10)])
print(r) # <map object at 0x0000026B26382190>
print(list(r)) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 对一个序列求和
def add(x,y):
    return x + y

r = reduce(add,[x for x in range(10)])
print(r) # 45
print(sum([x for x in range(10)])) # 45

# 把序列[1, 3, 5, 7, 9]变换成整数13579

def f2(x,y):
    return x*10 + y

print(reduce(f2,[1, 3, 5, 7, 9])) # 13579

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

def normalize(name):
    return name.capitalize()

L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2) # ['Adam', 'Lisa', 'Bart']

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

def prod(L):
    def product(x,y):
        return x*y

    return reduce(product,L)

print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
    print('测试成功!')
else:
    print('测试失败!')
'''
3 * 5 * 7 * 9 = 945
测试成功!
'''

1.2 filter

filter()函数用于过滤序列。函数用于过滤序列。

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

  • 用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。
  • 注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。
  • filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。
import math
# 仅保留奇数
def is_odd(n):
    return n % 2 == 1

r = list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
print(r) # 结果: [1, 5, 9, 15]

# 计算素数的一个方法:埃氏筛法
'''
根据素数的定义反推:因为如果一个数不是素数那么它一定是两个数的乘积。 
 n = sqrt(n) * sqrt(n) 
 假设 n是 i*j ,那么i 和j一定有一个是<= sqrt(n) ,另一个>=sqrt(n)
 因此只看较小那个除数存不存在就可以判断n是否素数。
'''
def gen_sqrt(n):
    # 生成器 2~ sqrt(n)+1
    yield  2
    i = 2
    while i<=int(math.sqrt(n)) +1:
        i+=1
        yield i

print(gen_sqrt(10))
print(list(gen_sqrt(10)))
'''
<generator object gen_sqrt at 0x000001EA035D6AC0>
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
'''

# 定义筛选函数
def f(n):
    # n 是否为素数
    if n in [2,3]:
        return True

    g = gen_sqrt(n)

    for i in g:
        if n%i==0:
            return False
    return True

# 求指定范围内的素数
def primes(n_start,n_max):
    r = filter(f,[x for x in range(n_start,n_max+1)])
    return r

print(list(primes(2,1000)))
'''
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 
101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 
197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 
311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421,
 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 
 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 
 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 
 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 
 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
'''

1.2 sorted

  • Python内置的sorted()函数可以对序列排序
  • sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序.key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序
L = [36, 5, -12, 9, -21]

print(sorted(L)) # [-21, -12, 5, 9, 36]
print(sorted(L,key=abs)) # [5, 9, -12, -21, 36]

S= ['bob', 'about', 'Zoo', 'Credit']
# 按字母大小写顺序排列
print(sorted(S))  # ['Credit', 'Zoo', 'about', 'bob']
# 忽略大小写
print(sorted(S,key=str.lower)) # ['about', 'bob', 'Credit', 'Zoo']

# 学生名字和成绩
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
# 按名字排序
def by_name(t):
    return t[0]

L2 = sorted(L, key=by_name)
print(L2) # [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

# 按成绩
def by_score(t):
    return t[1]

L2 = sorted(L, key=by_score)
print(L2) # [('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)]

2. 返回函数: 函数作为返回值

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

相关参数和变量都保存在返回的函数中.

当一个函数返回了一个函数后,其内部的局部变量如果被新函数引用, 多个函数引用指向的是同一个局部变量, 因为调用该函数对象返回新的函数对象时, 该函数并没有变, 其内部局部变量可能变了, 并被多个返回的函数变量所引用.

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

f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
f3 = lazy_sum(1, 3, 5, 7, 9,11)
# f1,f2 可以看作lazy_sum(*args) 的两个不同的保存了各自局部变量的函数的实例
print(f1==f2) # False
print(f1) # <function lazy_sum.<locals>.sum at 0x0000025AF1E59040>
print(f1()) # 25
print(f3()) # 36

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

# f1, f2, f3 均引用了  count() 中的同一个局部变量 i
# i 对 f1, f2, f3 来讲, 就像一个全局变量
f1, f2, f3 = count()

print(f1()) # 9
print(f2()) # 9
print(f3()) # 9

3.匿名函数

  • 当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便.
  • 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
  • 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。
  • 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
  • Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
# 关键字lambda表示匿名函数,冒号前面的x表示函数参数
print(list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# [1, 4, 9, 16, 25, 36, 49, 64, 81]

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

4.装饰器

  • 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数, 变量可以看作是函数的别名。
  • 函数对象有一个__name__属性,可以拿到函数的名字:定义函数的名字
  • 假如我们定义了一个函数 now(), 打印当前日期.假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。所以装饰器是一个以函数作为参数的高阶函数
import datetime


def now():
    print(datetime.date.today())

f = now
f() # 2021-07-13
print(now.__name__) # now
print(f.__name__)   # now

使用装饰器:

import datetime

# 定义一个能打印日志的decorator装饰器 log
def log(func): # 函数作为其参数
    # wrapper()函数的参数定义是(*args, **kw)
    # 因此,wrapper()函数可以接受任意参数的调用。
    def wrapper(*args, **kw):
        # 对调用函数的增强功能: 打印调用的函数名
        print('call {}():'.format(func.__name__))
        # 调用被作为参数的函数
        return func(*args, **kw)

    return wrapper

# 把@log放到now()函数的定义处,相当于执行了语句:now = log(now)
# 由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在
# 只是现在同名的now变量指向了新的函数
# 于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
@log
def now():
    print(datetime.date.today())



f = now
f() 
'''
call now():
2021-07-13
'''
print(now.__name__) # wrapper
print(f.__name__)   # wrapper
© Licensed under CC BY-NC-SA 4.0

知识上的投资总能得到最好的回报。——本杰明.富兰克林

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!