Python学习笔记(四)函数式编程

函数式编程

高阶函数

将函数作为参数传入另一个函数

map/reduce

map

map(函数名,Iterable对象)将函数作用在Iterable对象的每一个元素,返回Iterator。Iterator是惰性的,因此如果要浏览结果需要list(map(…))转化。使用map而非for循环,代码逻辑更清晰,抽象程度更高,可读性强

reduce

结构和map相同,但它是把结果和迭代对象的下一个元素做累计计算,即reduce(f, [x1,x2,x3]) = f(f(x1,x2),x3)。对于形式较为简单的函数,可以使用lambda来简化代码,可以省去def f的部分(见练习3的解答)。

lambda函数是个好东西,C++11新标准也引入了lambda函数。

练习1:规范化英文名,将一个英文名List中的各个元素首字母大写其他字母小写

def normalize(name):
    return name.capitalize()#Python中有直接实现str规范化的函数

L = ['lia', 'wR', 'BAR', 'Ra']
print(list(map(normalize, L)))

练习2:求积

from functools import reduce

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

print(prod([1,2,3,4]))

练习3:字符串转化为浮点数

from functools import reduce

def char2num(c):
    return {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}[c]

def str2float(s):
    s1,s2 = s.split('.')#Python提供str的split分割函数实现根据分隔符切片
    return reduce(lambda x,y: x*10+y, map(char2num, s1))+reduce(lambda x,y: x/10+y, map(char2num, s2[::-1]))/10

print(str2float('123.456'))

filter

filter()map()类似,也将一个函数作用于一个序列,但会根据返回值剔除元素 。同样地,filter也返回惰性序列,完成计算结果需要list(fitler())

使用埃氏筛选法构造素数生成器:

#先构造从3开始的奇数生成器
def _odd_iter():
    n = 1
    while True:
        n += 2
        yield n

#筛选函数
def _not_divisible(n):
    return lambda x: x%n>0

#构造素数生成器
def primes():
    yield 2
    it = _odd_iter() #从3开始的奇数序列
    while True:
        n = next(it) #序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) #过滤掉n的倍数,剩下的数组成新的序列

#生成素数
for n in primes():
    if n < 1000:
        print(n)
    else:
        break

练习:回数生成器

def is_palindrome(n):
    return str(n) == str(n)[::-1] #[::-1]的使用获得了非常简洁的代码,一行搞定
output = filter(is_palindrome, range(1,1000))
print(list(output))

sorted

排序函数sorted(序列, key = 函数, reverse = False),将函数作用于序列之后,根据结果序列,从小到大重排序列。如果reverse = True则反向排列

返回函数

函数也可以作为返回值。

def f1(...):
    ...
    def f2():
        ...
    return f2

f = f1(...)
g = f2(...)

函数返回时并未执行,调用f()时才会计算结果,这样可以在需要计算的时候才计算结果,实现延迟计算。

f2作为f1的内部函数,可以引用外部函数f1的参数和局部变量。f1返回f2时,参数和变量都保存在f2中,这种结构成为闭包Closure。注意每次返回的f2虽然形式相同,即使参数相同也是不同的函数。以上的代码中f和g是不同的。

使用闭包要注意,返回函数不要饮用任何循环变量或者后续会发生变化的变量,否则因为函数返回时并未执行,计算结果都是根据变化变量最新的值计算。如果一定要用到的话,就要再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。这样程序嵌套就变多了,显得臃肿

匿名函数

lambda 变量:表达式实现匿名函数。可以用它赋值给变量来定义函数f = lambda ...,可以作为返回值,可以作为参数传入函数。不过表达式只能有一个,因此支持情形的比较简单

装饰器

使用装饰器可以在不修改函数定义的情况下给函数增加功能。比如下面这个装饰器让函数调用时先打印hello再执行函数:

def log(f):
    def wrapper(*args, **kw):
        print('hello')
        return f(*args, **kw)
    return wrapper

@log
def now():
    print('now')

now()
  • log(f)返回的是一个函数。

  • @log称为语法糖,出现在函数定义前相当于执行了语句f = log(f),将函数重定向到log(f),从而扩展了f的功能。

  • log中的wrapper引用了log中的变量,因此是个闭包。为什么使用闭包很多人表示疑惑,我也进行了自己的思考和查阅资料。如果不用闭包而使用以下形式:

def log(f):
    print('begin')
    f()
    print('end')
    return f

@log
def f1():
    ...

注意上面的代码log返回的是f本身,则第一次调用f1之后,执行完了log的内容,f1又变成了原来的f1,并不能起到扩展功能的装饰效果。使用闭包以后每次调用f1实际上都在调用wrapper,修饰的内容才算加上去了

还可以使用带参数的装饰器,则要采取以下的形式:

def log(arg):
    def dec(f):
        def wrap():
            ...
            return f
        return warp
    return dec

@log(a1)
def f1():
    ...

此时的log(a1)相当于f1 = log(a1)(f1)log(a1)返回dec函数,因此相当于f1 = dec(f1),不过a1作为参数已经传递进闭包了。

f.__name__可以拿到函数名字。但函数在经过修饰之后名字会变。对于依赖函数签名的代码则需要在构造装饰器的时候传入函数名。这时候需要使用functools模块(用法见下列代码)。这一节的练习题是编写一个既支持有参数(以字符串为例)装饰器又支持无参数装饰器,还是很有难度的,交作业的网友们没有一个的代码可以跑通,在网上搜相关的内容也没有可行的。对这道题我思考了很长的时间,这里给出两个版本的解法,第二版是更符合要求的,但第一版也贴出来进行一些辨析,以加深对装饰器的理解。

#版本1
import functools

def log(text = None):
    if isinstance(text, str): 
        @functools.wraps(text)
        def dec(f):
            def wrapper():
                print('begin')
                print('%s %s' %(text, f.__name__))            
                f()
                print('end')
                return f
            return wrapper
        return dec
    else:
        def d(f):
            def wrapper():
                print('begin')
                f()
                print('end')
                return f
            return wrapper
        return d

@log('excute')
def f2():
    print('f2 is running')

@log()    #这里不符合要求
def f1():
    print('f1 is running')

f1()
f1()
f2()
f2()

仔细一看修饰f1的时候log不是标准形式的无参@log,而是@log()。如果使用后者会报错,因为f1 = log()(f1)相当于f1 = d(f1),而@log则是f1 = log(f1),进入else之后变成了f1 = d,没有参数传入d了,因此会报错。显然这一版本是不太符合要求的。

#版本2
import functools

def log(text):
    if isinstance(text, str): 
        @functools.wraps(text)
        def dec(f):
            def wrapper():
                print('begin')
                print('%s %s' %(text, f.__name__))            
                f()
                print('end')
                return f
            return wrapper
        return dec
    else:
        def wrapper():
            print('begin')
            text()
            print('end')
            return text
        return wrapper

@log
def f1():
    print('f1 is running')

@log('excute')
def f2():
    print('f2 is running') 

f1()
f1()
f2()
f2()

看完版本2之后觉得思路还是比较清晰的,有参的log需要三层嵌套,无参只需要两层嵌套。有参数的部分仍然和版本1一致,无参部分的处理实际上和最开始的例子一样,无非把f换成了text。现在看来真是非常简单,基本上变都不用变,两个组合起来加上判断就行了。之前想了那么长时间,看来还是没有理解装饰器的本质。

偏函数

ff = functools.partial(f, arg = n)将函数f的某个参数arg设为固定值n,构造一个新的函数ff,就是偏函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值