Python3(5) Python 函数式编程

本系列主要学习Python的基本使用和语法知识,后续可能会围绕着AI学习展开。
Python3 (1) Python语言的简介
Python3 (2) Python语法基础
Python3 (3) Python函数
Python3 (4) Python高级特性
Python3(5) Python 函数式编程
Python支持函数式编程,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

高阶函数

对于一直在java中学习的人来说,高阶函数还是一个陌生、高大上的名词,它有三个特点:

  • 变量可以指向函数
  • 函数名也是变量
  • 函数可以作为参数传入

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

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

print('|x|+|y| = ',add(-5, 6, abs))

输出结果:

|x|+|y| =  11

几个内置高阶函数

map/reduce

首先声明map函数与java中的map是两个名词,没有关联。map高阶函数的定义:map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

L = list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(L)

输出结果:

['1', '2', '3', '4', '5', '6', '7', '8', '9']

将list中的元素转换成字符串。

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import reduce
def fn(x,y):
    return x*10+y

L = reduce(fn, [1,2,3,4,5])

print(L)

输出结果:

12345

用list 实现一个按list序列生成一个整数,它的结果就是一个最终的数。

使用map 和 reduce 写一个字符串转整数的函数、字符串转浮点数的函数:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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):
    def fn(x, y):
        return x * 10 + y
    return reduce(fn, map(char2num, s))
def str2float(s):
    n = s.index('.')
    return reduce(lambda x,y:x*10+y,map(char2num,s[:n]+s[n+1:]))/10**n

print(str2int('10001'))
print(str2float('10001.0001'))

输出结果:

10001
1000.10001

filter

用于过滤序列,filter 与map定义的格式相同,参数接受一个函数,一个序列,返回一个Iterator。filter通过判断函数的返回值是否为True来丢弃一些元素。

filter的主要应用是实现一个筛选函数:我们来实现一个素数的序列

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

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

def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列

# 打印1000以内的素数:
for n in primes():
    if n < 20:
        print(n)
    else:
        break

输出结果:

2
3
5
7
11
13
17
19

实现原理,依次将3,5,7,9... 的倍数筛选完,最终剩下的为素数。
练习一个回数的筛选:从左向右读和从右向左读都是一样的数

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def is_palindrome(n):
   return str(n) == str(n)[::-1]
# 测试:
output = filter(is_palindrome, range(1, 200))
print('1~200:', list(output))

输出结果:

1~200: [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]

sorted

sorted 排序高阶函数,它的使用非常灵活,可以传入自定义的排序函数、反向排序,在复杂的排序中核心代码还是非常的简洁。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

L = [3,1,-5,-2,9]
#普通的用法
print(sorted(L))
#反向排序
print(sorted(L,reverse=True))
K = ['Apple','banana','Pear','tomato']
print(sorted(K))
#忽略大小写
print(sorted(K,key=str.lower))
#反向排序
print(sorted(K,key=str.lower,reverse=True))

输出结果:

[-5, -2, 1, 3, 9]
[9, 3, 1, -2, -5]
['Apple', 'Pear', 'banana', 'tomato']
['Apple', 'banana', 'Pear', 'tomato']
['tomato', 'Pear', 'banana', 'Apple']

返回函数

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回,我们来聊一聊返回函数的问题。
当我们调用一个函数时,不需要立即得到结果,想在需要的时候再进行计算,那么我们就可以返回一个函数而不是直接一个结果。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def laz_count(*args):

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

f1 = laz_count(1, 2, 3, 4, 5,6)
f2 = laz_count(1, 2, 3, 4, 5,6)
print(f1())
print(f1==f2)

输出结果:

91
False

从上面可以看出,函数返回值也是一个函数,需要再次调用才能得出结果,并且每次返回的都是一个新的函数。

闭包

闭包的定义与java中的内部类有些相似,闭包指的是函数再定义函数的情况,即:内部函数可以外部函数的参数和局部变量,当外部函数返回内部函数时,相关的参数和变量都保存在返回的函数中。这种行为称之为 “闭包”。

# -*- coding: utf-8 -*-
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())

def count():
    fs = []
    for i in range(1, 4):
        def f(i):
            def g():
                return i * i
            return g
        # f(i)立刻被执行,因此i的当前值被传入f()
        fs.append(f(i))
    return fs
f1, f2, f3 = count()

输出结果:

9 9 9
1 4 9

由于返回的函数不是立即执行,在调用执行时,i变量已经成为3,如果要输出想要的值,需要再创建一个函数将变量i 与函数绑定。

匿名函数

匿名函数其实就是lambda 表达式的使用,lambda表达式的使用场景就是匿名函数,与java 的匿名类很相似。

  • 关键字lambda表示匿名函数,冒号前面的x表示函数参数
  • 匿名函数有个限制,就是只能有一个表达式,表达式的值就是返回值,不需要return
  • 匿名函数也可以作为函数的返回值返回
# -*- coding: utf-8 -*-
L = list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(L)

def f(x,y):
    return lambda :x*x+y*y
x = f(2,3)
print(x())

输出结果:

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

装饰器

可以在这代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。decorator就是一个返回函数的高阶函数。装饰器在java中成为装饰者模式,需要通过继承,组合来实现,python中在函数层面就可以实现。这就是python 的强大之处

下面我们来通过示例学习,decorator的用法:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def now(x,y):
    print('2018-1-1',x,y)

now(5,6)
print(now.__name__)

def now(x,y):
    print('2018-1-1',x,y)

f =log(now)
f(1,2)
print(now.__name__)

输出结果:

call now():
2018-1-1 5 6
wrapper
call now():
2018-1-1 1 2
now

第一 我们定义了一个在函数调用开始前输出函数名的decorator
第二 在使用装饰器时可以通过@decorator的方式注解也可以通过传入函数的方式
第三 在使用@decorator的方式装饰器后,函数的__name__变成了 wrapper 这样显然是不合理的,我们目的是为了扩展函数的功能,不是改变函数的签名,所以python 中 内置了functools.wraps来还原函数的签名,具体如下:

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

@log('123')
def now(x,y):
    print('2018-1-1',x,y)

now(5,6)
print(now.__name__)

def now(x,y):
    print('2018-1-1',x,y)

f =log('456')(now)
f(1,2)
print(now.__name__)

输出结果:

123 now():
2018-1-1 5 6
now
456 now():
2018-1-1 1 2
now

这个示例中我们验证了@functools.wraps(func)的用法,并且多层嵌套自定义log输出的字段。
下面做一个函数执行时间的练习:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import functools
import time

def metric(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kw):
        start = time.time()
        result = fn(*args, **kw)
        end = time.time()
        print('%s 执行时间 %.2fs ms' % (fn.__name__, (end - start) * 1000))
        return result
    return wrapper

# 测试
@metric
def fast(x, y):
    time.sleep(0.0012)
    return x + y;

@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z;

f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
    print('测试失败!')
elif s != 7986:
    print('测试失败!')

输出结果:

fast 执行时间 2.00s ms
slow 执行时间 124.60s ms

偏函数

通过传入函数,和对应的规则,生成一个新的函数,方便调用 的方式成为偏函数partial

偏函数的使用:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import functools

def int2(x, base=2):
    return int(x, base)

print(int2('1111'))

int2 = functools.partial(int, base=2)

print(int2('1111'))

输出结果:

15
15

创建偏函数时,实际上可以接收函数对象、args和*kw这3个参数,偏函数是 functools 模块中提供的一种固定某些参数来简化一些函数的调用难度的作用。

参考

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317848428125ae6aa24068b4c50a7e71501ab275d52000

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348