Python函数式(一)——初探

Python函数式系列

本篇文章标志着新系列:Python函数式的开始。本系列将详细讲述Python在函数式编程范式中如何发挥着它优雅简洁的特性。

函数式编程范式

函数式编程是一种编程范式,它的核心是一套紧密完善的数学理论,称之为λ演算(λ-Calculus)。

按照编程范式来区分语言(或者说按照如何分解一个问题来区分),可以有如下四类:

过程式:通过一步步连续的指令来告诉计算机该做什么,最常见的过程式语言:C,Unix Shells,Pascal等等;

声明式:描述一个问题并让语言的底层来实现计算的过程,是声明式编程。最常见的声明式语言是SQL。我们在利用SQL查询数据的时候,通常这么写:“请到A表里把b是1的那一项的c和d属性给我。”

selectc, dfrom`A`whereb=1;

面向对象:将问题抽象为数据和方法的集合。Java便是纯正的面向对象编程语言;

函数式:将问题分解为一些小的函数的集合,每一个函数都有输入和输出,并且输出值只受输入值影响,而不受函数内部状态的影响。Haskell是纯函数式编程语言。

支持以上多种编程范式的语言,叫做多范式语言,例如Lisp,C++以及我们的Python。要理解函数式编程,首先要明白编程中的两个概念:语句(statement)和表达式(expression)。语句指一段可执行的代码,类似一个命令,例如,或,通常,IO操作都是语句,例如打印;而表达式(可以直接理解为函数)指一段可以输出一个结果的代码,例如会输出。函数式编程要求尽可能地仅使用表达式来完成程序编写。此外,函数式编程还有如下几个特点:

函数为“一等公民”所谓“一等公民”,即函数在程序中与其他数据类型处于相同地位。函数也可以作为函数参数函数返回值,也可以定义在别的函数内部等等。

内外隔离这里指函数内部与外部保持独立,内部不会对外部的任何东西产生影响(或者称为副作用)。简单来说就是内部不会引用外部的全局变量

无状态性函数内部不存在状态,这一点同面向对象中的对象正好相反,对象存储的正是数据的状态,并随着程序运行,状态也发生着变化。函数式编程强调无论什么时候,只要输入值一定,输出值就是一定的

更多关于函数式编程理论性的东西,可以在文末参考文献中学习。本篇文章重点介绍Python中如何采用函数式编程范式来coding。

Python函数式风格

1

“一等公民”特性

既然函数是“一等公民”,那么它和普通的变量没什么区别,可以把函数名作为普通变量做很多事,只有在函数后面括上小括号,它才开始了调用过程:

a=1

defb():

return

# 调用

print(b())

# 0

# 普通变量

print(b)

#

# 也可以被覆盖

b=a

print(b)

# 1

2

匿名函数

匿名函数可以说是函数式中的基本单元,很多地方都有它的身影。Python中匿名函数由关键字定义,其结构是

f=lambdax,y:x+y

这里定义了一个匿名函数,接收两个参数和,函数返回和的和。将这个匿名函数赋值给,即可利用来调用:

print(f(1,2))

# 3

匿名函数要求函数体不能超过一个表达式,并且自动将计算结果返回,不需写。上述匿名函数的普通写法是:

deff(x,y):

returnx+y

匿名函数的意义在于可以在需要的地方直接定义一个函数,而不是在别的地方定义再在这里传入,下面例子中均有涉及。

匿名函数当然也可以不加参数,甚至直接返回一个。这在一些需要函数进行测试的地方会很有帮助。需要注意一点的是,如果你定义了一个匿名函数,却把它赋值给了一个标识符(例如前面的),你应该用普通定义来完成

3

高阶函数

所谓高阶函数,即前面所说将函数作为其他函数的参数。例如,这里实现一个简易的计算函数,可以返回和经过运算的结果:

defcompute(method,x,y):

returnmethod(x,y)

这里method是函数,它可以是任何种二元运算函数:

# 整数加法

print(compute(int.__add__,4,2))

# 6

# 乘法

print(

compute(float.__mul__,4.0,2)

)

# 8.0

# 开方

importmath

print(math.pow(4,1/2))

# 2.0

print(compute(math.pow,4,1/2))

# 2.0

# 匿名函数直接定义

print(

compute(

lambdax,y:x+y-1,

4,

2

)

)

# 5

不知道__add__和__mul__什么意思?看这里→

传送门

4

嵌套定义

函数内可以定义函数,例如:

deffunc1(x,y):

# 定义另一个函数

deffunc2():

print(x)

# 这里直接调用

func2()

func1(1,2)

# 1

5

返回函数

函数也可以作为其他函数的返回值,例如,上述可以作为的返回值返回出去:

deffunc1(x,y):

# 定义另一个函数

deffunc2():

print(x)

# 这里直接调用

func2()

func1(1,2)

# 1

返回出来的函数怎么用呢?用一个变量接收,再调用这个变量:

f=func1(1,2)

# f就是func2

f()

# 1

也可以用匿名函数定义返回值:

deffunc1(x,y):

returnlambdaz:x+y+z

f2=func1(1,2)

print(f2(3))

# 6

函数返回值有什么用呢?请接着看。

6

闭包

deffunc():

l= []

foriinrange(4):

i+=2

l.append(i)

returnl

print(func())

# [2, 3, 4, 5]

print(i)

# NameError: name 'i' is not defined

现在我们来改写一下它:

deffunc():

l= []

foriinrange(4):

l.append(lambda:i)

returnl

这里func返回了一个包含4个匿名函数的列表,匿名函数返回了i的值,i是函数内部的变量。我们试着在外部调用一下他们:

fl=func()

# 先看看fl是什么

print(fl)

# [. at 0x0000020D22FF3378>, . at 0x0000020D22FF3488>, . at 0x0000020D22FF3510>, . at 0x0000020D22FF3598>]

# 调用列表中最后一个函数

print(fl[-1]())

# 3

咦?函数内部的变量在外部也可以访问了?是因为这个变量存进了这个函数里吗?再看剩下3个函数:

print(fl[-2]())

# 3

print(fl[-3]())

# 3

print(fl[-4]())

# 3

???为什么全是3??明明是从0增加到3的。

这里体现了闭包的两个特性:

内部变量被保留了下来(在内存里),可以在函数外部访问到;

惰性特点,内部变量被保留的只是最终的状态。

很显然,当你调用fl函数的时候,i早已经变成了3。而闭包直到函数调用时刻才会去读取i的值,当然最后全部是3了。巧妙利用闭包可以收获很大的简洁性,然而,使用不当则会造成很多问题。前面惰性就可能造成一定的问题。而闭包另一大问题是将函数内部变量保存下来,不再销毁,导致内存占用量上升,严重情况下可能会造成内存泄漏。此外闭包让调试也变得更困难(试想一下,你会想起外面的i居然是定义在一个函数内部的?)。所以虽然闭包构建了函数内外的桥梁,但不合理的过桥可能会压垮你的程序。

7

偏函数

这里偏函数并不是数学上的偏函数,而是指你可以为一个函数指定默认的调用参数,将其作为一个新函数名给你,这样你在调用时可以调用新函数而不必总是为旧函数的参数赋值。例如,求幂函数要求两个参数做输入,一个底数,一个幂。我们可以利用偏函数生成一个专门负责求以3为底的各个幂次的偏函数:

importmath

print(math.pow(3,3))

# 27.0

print(math.pow(3,4))

# 81.0

# 直接做一个求以3为底的各个幂次的新函数

importfunctools

pow3=functools.partial(

math.pow,3

)

# pow3只接收一个参数,即幂次

print(pow3(3))

# 27.0

print(pow3(4))

# 81.0

print(pow3(5))

# 243.0

有人问,可以做一个求任意数的4次幂的新函数吗?答案是,用partial做不到,因为pow只支持关键字参数(什么是关键字参数?→传送门)。来看一下怎么用闭包实现 :

importmath

ppow=lambday:\

lambdax:math.pow(x,y)

pow4=ppow(4)

pow4(2)

# 16.0

pow4(3)

# 81.0

pow4(4)

# 256.0

小例子

下面以一个小例子体会函数式编程思维:

例如,给你一个数,让你在一个序列中找到距离这个数最近的一项并输出:

importrandom

l= [433,787,868,915]

f=random.random()*1000

# 生成一个1000以内的随机数

print(f)

# 790.9193597866413

过程式思维是这样的,循环去用减的每一个值,找到差值最小的一个就是距离最短的一个

out=l[]

dist=abs(f-out)

foreleinl:

d=abs(f-ele)

ifdist>d:

dist=d

out=ele

print(out)

# 787

函数式的思维不会循环列表,解决这个问题可以先将序列映射为一个到距离的序列(),再从中找出最小值的索引(),再返回中的该元素:

defargmin(seq):

importoperator

returnmin(

enumerate(seq),

key=operator.itemgetter(1)

)[]

out=lambdaf,l:l[

argmin(map(

lambdax:abs(x-f),l

))

]

print(out(f,l))

# 787

关于例子中用到的知识,留待以后几期讲解。

参考文献

https://docs.python.org/3/howto/functional.htmlhttp://www.ruanyifeng.com/blog/2017/02/fp-tutorial.htmlhttp://www.ruanyifeng.com/blog/2012/04/functional_programming.htmlhttps://www.inf.fu-berlin.de/lehre/WS03/alpi/lambda.pdf

友情链接: https://vonalex.github.io/

欢迎关注 它不只是Python

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180610G0YCW400?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券