Python学习笔记四:函数式编程

学习资源:廖雪峰Python教程

教程链接点击此处

重点记录与c,java有区别的知识点,红色部分重点注意。


四、函数式编程

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量。由于Python允许使用变量,因此Python不是纯函数式编程语言


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

(1)什么是高阶函数?

①变量可以指向函数:

f = abs #abs为内置的绝对值函数,将这个函数名赋给一个变量
print(f(-10)) #输出10,说明变量f指向了绝对值函数abs


②函数名也是变量:

对于abs()这个函数,可以把函数名abs看作一个变量,它指向一个绝对值函数:

abs = 10 #将整数10赋给abs
print(abs) #输出10
print(abs(-10)) #报错,此时abs不再指向绝对值函数,而是一个整数10

函数能够以函数作为参数(传入函数)

def add(x, y, f):
    return f(x) + f(y)

x, y = -2, 3
f = abs
result = add(x, y, f) #将abs()函数作为第三个参数传入add()函数
print(result) #输出5,说明abs()成功作为一个参数传入了函数add()


④把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。


(2)map/reduce

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

def f(x):
    return x * x;

r = map(f, list(range(1, 9))) #将第一个参数指向的函数对象,依次作用于第二个参数指向的序列中的每一个元素
print(list(r)) #输出[1, 4, 9, 16, 25, 36, 49, 64] #map返回的是一个Iterator,是惰性序列,list()函数将这个Iterator计算成一个list

#输出['1', '2', '3', '4', '5', '6', '7', '8'],即将1-9的整数转化为1-9的字符串
print(list(map(str, list(range(1, 9)))))


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

#这个求和例子清晰说明reduce()函数的过程
from functools import reduce

def add(x, y):
    return x + y

result = reduce(add, list(range(1, 5))) #将1,2,3,4相加
print(result) #输出10

③map()配合reduce()实现str转化为int的函数:

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)) #map()函数将字符串s依次转化为单个数字1,2,3,4,5
                                        #reduce再将fn()函数作为第一个参数,1,2,3,4,5作为第二个参数进行计算

result = str2int('12345')
print(result) #输出12345整数


(3)filter

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

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

result = list(filter(is_odd, [1, 2, 3, 4, 5, 6])) #利用filter()函数筛选奇数
print(result) #输出[1, 3, 5]


②利用filter实现埃及筛法求素数

#这是一个生成无限序列的生成器,构造一个从3开始的奇数序列:3,5,7,...
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

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

#一个不断返回下一个素数的生成器
#这个函数中的it从初始序列3,5,7,9,11,13...经过filter函数的筛选,不断获得新序列
#5,7,11,13,17,...  不能被3整除的序列
#7,11,13,17,19,...  不能被5整除的序列
#11,13,17,19,23,...  不能被7整除的序列
def primes():
    yield 2 #2也是素数,先返回一个2
    it = _odd_iter() #获得序列3,5,7,...
    while True:
        n = next(it) #返回当前序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) #构造新序列

for n in primes():
    if n < 100:
        print(n)
    else:
        break


(4)sorted

sorted()是Python内置的排序函数:

L = [8, 5, -1, 9, -4]

result1 = sorted(L)
result2 = sorted(L, reverse = True) #reverse参数将排序结果反序
result3 = sorted(L, key = abs) #接收一个key实现自定义排序
                               #key先将指定的函数将作用于list的每一个元素上,再key函数返回的结果序列进行排序
                               #按元素的绝对值排序

print(result1) #输出排序后的结果[-4, -1, 5, 8, 9]
print(result2) #输出排序后的结果[9, 8, 5, -1, -4]
print(result3) #输出排序后的结果[-1, -4, 5, 8, 9]


2.返回函数

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

def lazy_sum(*args):
    def sum(): #内部函数sum()可以引用外部函数lazy_sum的参数和局部变量
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

f = lazy_sum(1, 2, 3, 4) #此时的f指向一个函数对象sum(1, 2, 3, 4)
result = f() #再次调用函数f()
print(result) #输出求和结果10

f1 = lazy_sum(1, 2, 3, 4)
f2 = lazy_sum(1, 2, 3, 4)
print(f1 == f2) #输出False,因为每次都会返回一个新的函数 ②


②闭包:如果在一个函数的内部定义了另一个函数,外部的称为外函数,内部的称为内函数。在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。


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

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

f1, f2, f3 = count()
print(f1(), f2(), f3()) #输出9 9 9,并非预想中的1 4 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(), f2(), f3())


3.匿名函数

L = list(map(lambda x : x * x, list(range(1,9))))
print(L) #输出[1, 4, 9, 16, 25, 36, 49, 64]

关键字lambda表示匿名函数,冒号前面的x表示函数参数

限制:只能有一个表达式,不写return,返回值就是该表达式的结果

好处:不用担心函数名冲突,可以赋给一个变量,也可以作为返回值


4.装饰器(Decorator)

①装饰器本质上是一个高级函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象

#log()是一个装饰器,接受一个函数作为参数并返回一个函数
def log(func):
    def wrapper(*args, **kw): #wrapper()可以接受任意参数的调用
        print('call %s():' % func.__name__) #函数对象有一个__name__属性,可以拿到函数的名字
        return func(*args, **kw)
    return wrapper

@log #用@将装饰器log()置于函数now()的定义处,相当于执行了语句 now = log(now)
def now():
    print('2018-2-8 15:46')

now() #输出call now():
      #    2018-2-8 15:46
      #在运行now()之前,先运行装饰器log()

在wrapper内部,首先打印,然后接着调用原始函数


②带参数的装饰器:

#带参数的装饰器
def log(text):
    def decorate(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorate

@log('excute') 
def now():
    print('2018-2-8 15:57')

now()

运行now()时,相当于执行log('excute')(now)。这个语句首先执行log('excute')获得指向decorate()函数对象的返回值。再执行decorate(now),最终返回的是wrapper()函数对象


③上述经过装饰器装饰后的函数的__name__属性已经变为‘wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中。一个完整的装饰器写法如下:

import functools

#log()是一个不带参数的装饰器,接受一个函数作为参数并返回一个函数
def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw): #wrapper()可以接受任意参数的调用
        print('call %s():' % func.__name__) #函数对象有一个__name__属性,可以拿到函数的名字
        return func(*args, **kw)
    return wrapper

@log #用@将装饰器log()置于函数now()的定义处,相当于执行了语句 now = log(now)
def now():
    print('2018-2-8 15:46')

now() #输出call now():
      #    2018-2-8 15:46
      #在运行now()之前,先运行装饰器log()

或者带参数的装饰器:

#带参数的装饰器
import functools

def log(text):
    def decorate(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorate

@log('excute') 
def now():
    print('2018-2-8 15:57')

now()


5.偏函数

偏函数的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单

import functools

def add(x, y):
    return x + y

add2 = functools.partial(add, y = 2) #将add()函数的y参数设置默认值为2,构成函数add2()

result = add2(3) #3 + 2
print(result) #输出5

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

你可能感兴趣的:(Python,学习笔记)