python学习笔记-函数式编程-20200310

函数式编程

  • 函数:面向过程的程序设计的基本单元
  • 函数式编程----Functional Programming,是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

特点: 允许把函数本身作为参数传入另一个函数,还允许返回一个函数
Python不是纯函数式编程语言

高阶函数(Higher-order function)

  • 变量可以指向函数,如f = abs,此时把函数abs赋值给了变量f
    同时,可以通过该变量来调用这个函数,如:
f = abs
f(-10)
10
  • 函数名也是变量
    函数名其实就是指向函数的变量,故函数名可以看成变量,
    如:abs = 10,此时就无法调用abs(-10)这个函数了
  • 传入函数:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
    一个最简单的高阶函数:
def add(x,y,f)
    return f(x)+f(y)
add(-5,6,abs)   #调用

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

map/reduce

1.map()

  • map()函数接收两个参数,一个是函数,一个是Iterable。
  • map()将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
    如:
    函数:f(x)=x^2
    list:[1,2,3,4,5,6,7,8,9]
    把函数作用在list上,用map()实现:
def f(x):
    return x*x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])        #调用
list(r)

一行程序调用:
list( map(f, Iterable))
2. reduce()

  • 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
#转换整数
def fn(x , y)
    return x * 10 + y
reduce(add,[1,3,5,7,9])         #调用

**注意:**调用reduce前要加一句from functools import reduce

  • 补充:lambda用法:lambda 变量:表达式
    练习1
    利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]:
def normalize(name):
	name=name[0].upper()+name[1:].lower()
	return name

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

def prod(L):
	def fn(x,y):
		return x*y
	return reduce(fn,L)

练习3
利用map和reduce编写一个str2float函数,把字符串’123.456’转换成浮点数123.456:

def str2num(s):
	D={'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'0':0,'.':'.'}
	def digits(x):
		return D[x]
	I=list(map(digits,s))
	d=I.index('.')
	I.remove('.')
	def fn(x,y):
		return x*10+y
	return reduce(fn,I[:d])+reduce(fn,I[d:len(I)])*10**(-1*(len(I)-d))

其中:** 表示幂次

filter

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

  • 用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。
    e.g:
def is_odd(n):
    return n % 2 == 1     #删除偶数
    
def not_empty(s):
    return s and s.strip()     #删除序列中的空字符串
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))   #调用
  • strip() :用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
  • filter()函数返回的是一个Iterator,也就是一个惰性序列
用filter求素数
# 构造一个从3开始的奇数序列
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n
        
# 定义一个筛选函数
def _not_divisible(n):
    return lambda x: x % n > 0   #返回取余大于零的x,此时n为一个序列的首元素,x是序列中的全部元素
    
# 定义一个生成器,不断返回下一个素数
def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列,此时会去掉序列的第一个数
exercise

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

  • 个人解法
def is_palindrome(n):
    a=str(n)
    x=0
    if n//10==0:
        return n
    else:
        while x <len(a)//2:
            if a[x]!=a[len(a)-1-x]:
                return False
            else:
                x=x+1
            return n
  • 参考大神解法
def is_palindrome(n):
    return int(str(n)[::-1])==n

其中:s[::-1] 为将字符串反转

sorted-排序算法
  • sorted():可以对list进行排序
  • sorted()可以接收一个key函数来实现自定义的排序,key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序例如按绝对值大小排序:
sorted([36, 5, -12, 9, -21], key=abs)
  • 对字符串排序:按照ASCII的大小比较的
  • 对字符串排序,按照字母序排序,秩序加key=str.lower
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
  • 反向排序,可以再次传入参数reverse=True
sorted(['bob],'about,'Zoo','Credit],key=str.lower,reverse=True)
exercise

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

# 方法一:
def by_name(t):
    return t[0]
def by_score(t):
    return t[1]
L2 = sorted(L, key=by_name)
L2 = sorted(L, key=by_score)
#方法二:
# 直接在sorted函数里面写lambda函数
L2=sorted(L,lambda t : t[0])
L2=sorted(L,lambda t : t[1])

返回函数

函数作为返回值

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

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

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

f = lazy_sum(1, 3, 5, 7, 9)     # 此时f为求和函数
f()                                   # 调用函数f时才真正计算求和的结果
  • 当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
  • f1和f2的调用结果互不影响
闭包
  • 上述例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
  • 返回的函数并没有立刻执行,而是直到调用了f()才执行
  • 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
  • 如果一定要引用循环变量,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
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

缺点是代码较长,可利用lambda函数缩短代码。

exercise

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

# 方法一:生成器
def createCounter():
    def num_generator():
        num=0
        while True:
            num+=1
            yield num
    int_num=num_generator()
    def counter():
        return next(int_num)
    return counter
        
# 方法二:直接用变量count计数
def createCounter():
    count=0
    def counter():
        nonlocal=counter
        count=count+1
        return count
    return counter()
    
# 方法三:使用list对象计数
def createCounter():
    count=[0]
    def counter():
        count[0]+=1
        return count[0]
    return counter()

注:

    1. 使用生成器,按照生成器的做法在counter()函数中调用next()即可
    1. 使用闭包知识,且使用变量n(int对象),需要了解的是变量的作用域遵循LEGB规则,然后在counter()函数内声明nonlocal n
    1. 使用闭包知识,使用列表做计数器,无需在counter()函数内声明nonlocal n

匿名函数

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。


匿名函数: lambda 变量列表:表达式
如以map()函数为例,计算f(x)=x^2:

list(map(lambda x : x * x,[1,2,3,4,5,6]]))
  • 匿名函数只能由一个表达式,不用写return,返回值就是该表达式的结果
  • 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
f=lambda x : x * x
f(5)
  • 也可以把匿名函数作为返回值返回:
def builf(x , y) :
    return lambda : x * y
erercise

请用匿名函数改造下面的代码:
def is_odd(n):
return n % 2 == 1

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

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

Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。

装饰器

  • 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数
  • 函数对象有一个__name__属性,可以拿到函数的名字
    如:函数def now():
    print(‘a’)
    f=now()
    now.__name__ —>‘now’
定义

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)

  • 本质上,decorator就是一个返回函数的高阶函数
使用示例
  1. 定义一个能打印日志的decorator
# 函数
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
# 调用
@log
def now():                    # -->now=log(now),这里的func就是now()
    print('2015-3-25')
# 执行    
now()
  1. 要自定义log的文本,此时decorator本身需要传入参数,新韩淑要返回decorator:
# 函数
def log(text) :
    def decorator(func) :
        def wrapper(*args.**kw) :
            print('s% s%():'  %  (text,func.__name__))
            return func(*args,**kw)
        return wrapper
    return decorator
# 调用
@log('execute')
def now():
    print('2015-3-25')  
# 执行
now()              -->now = log('execute')(now)
# 结果
execute now():
2015-3-25
  • 经过decorator装饰之后的函数,__\name__发生改变,此时需要使用functools.wraps使得函数名字不变。
    在定义装饰函数wrapper()之前加一句@functools.wrap(func)
import functools
def log(func):
    @functools.wrap(func)
    def wrapper(*args,**kw):
        print('call %s()' % func.__name__)
        return func(*args,**kw)
    return wrapper
exercise

请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

import functools

def log(text):
    if isinstance(text,str):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args,**kw):
                print(text)
                print('begin call')      #在函数调用的前后打印出'begin call'和'end call'的日志。
                print('%s executed in %s ms' % (func.__name__, 10.24))
                print('end call')
                return func(*args,**kw)
            return wrapper
        return decorator
    else:
        @functools.wraps(text)
        def wrapper(*args,**kw):
            print('begin call')
            print('%s executed in %s ms' % (text.__name__, 10.24))
            print('end call')
            return text(*args,**kw)
        return wrapper

@log
def f1(x,y):
	print(x+y)

@log('execute')
def f2(x,y):
	print(x*y)

f1(1,2)
f2(1,2)

小结
  • 在面向对象(OOP)的设计模式中,decorator被称为装饰模式
  • OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator
  • Python的decorator可以用函数实现,也可以用类实现

偏函数

  • Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)
  • int(‘12345’, base=8) #将字符串转换为八进制,若不写base,默认为转换为十进制

创建偏函数的方法

  • functools.partial-----把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,如# b:,其中int为int()函数,base=2为固定其参数
  • 创建偏函数时,实际可接收函数对象、*agrs和 ** kw这三个参数,如# c:
# 函数,字符串转化为二进制整数
# a.
def int2(x,base=2):
    return int(x,base)
# b. 
import functools
int2=functools.partial(int,base=2)   #b比a更简单

# c.
kw={'base':2}
int('10010',**kw)
## 三种写法等价,都固定了int()函数的关键字参数base
# 调用
int2('100000')

当然也可以更改默认值: int2('10000',base=10)

  • 使用functools前要先输入import functools
小结

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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

该用户没有用户名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值