python语言支持多种编程范式,对于函数式,大家一定很感兴趣。但是由于python并不是严格的函数式语言(例如说haskell、erlang这样的语言),它所展现出来的不是完整的函数式语言特性。所以用python,可以是函数式的用法,也可以是过程式的用法,也可以是面向对象的用法。只有利用其中的特性,按照函数式的方式进行思考,才能真正的说函数式。
基于lambda和函数作为一等公民
函数式,顾名思义,就是函数编程,基于lambda演算, 理念是代数运算,关心的是函数的输入输出这对映射。
首先自然是函数作为头等函数和支持lambda,这是最基本的函数式语言的特性,为何这个特性很重要,是为了更重要的特性,高阶函数。例如说:
def addx(x):
def addy(y):
return x + y
return addy
y = 3
add_x = addx(y)
add_y = add_x(4)
这个例子展示了函数作为一等公民
思考方式--纯函数
对于函数式计算,是没有状态的,没有状态带来的好处就是无论何时输入同样的值,输出总是一样的,所以函数式就特别适合并发编程,或者说面向并发。所以写函数的时候,要把函数写成纯函数,和函数要区分开。
例如说:
def add_one(x):
return x + 1
这个就是无状态的纯函数,任何时候输入2,返回可定是3.
相反的,函数:
x = 1;
def add_one():
x +=1
return x
这里就是有状态的,在多线程状况下,遇到的东西是不可预测的。
haskell, erlang本身都是纯函数式语言,语言特性的关系,所以比较容易写出纯函数。
思考方式--高阶函数
接着我们用函数式的三件套展示一下高级函数的用法以及lambda表达式
def odd(x):
return x % 2 == 0
odds = map(odd, list)
#用lambda一句表达
odds = map(lambda x : x % 2 == 0, list)
可以看到高阶函数的抽象的威力,而且可以用运用这些高阶函数,用流的方式来表达。
思考方式--递归
函数式一般使用递归思考问题,例如说求阶乘,非递归的写法:
def factorial(n):\
result = 1
for i in range(n):
result *= (n + 1)
递归的写法
def factorial(n):
return factorialInner(n, 1)
def factorialInner(n, result):
if (n = 0):
return result
else:
return factorialInner(n-1, result * n)
对比一下用erlang表达,多么干净利落:
factorial(1) -> 1;
factorial(n) -> factorial(n - 1) * n.
特性--柯里化
珂里化的特性是python模拟出来的,并不是语言本身的特性。
functiontool.partial(func, *args, **kvs)
partial可以让不兼容的代码一起工作,例如按两点距离排序
import math
import functiontool
def distance(p1, p2):
x1, y1 = p1
x2, y2 = p2
return math.hypot(x2 - x1, y2 - y1)
points = [(3, 4), (5, 6), (7, 8), (1, 2)]
pt = (3, 4)
points.sort(key=functiontool.partial(distance, pt))
partial的实现是python的一个用函数实现简单对象读小技巧
def partial(func, *fargs, **fkeywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
特性--延迟计算
延迟计算的特性python是利用自己的语言特性模拟出来的,并不是语言本身的特性。
迭代器实现延迟计算
延迟计算的体现就是迭代器了,例如说制造一个数据源,从1开始,无限度递增,如果用数组,可以表示,但是问题是有那么大的内存可以保存无线长读数据吗?
class IntFromOne(object):
def __init__(self):
self.i = 0
def __iter__(self):
return self
def __next__(self):
self.i += 1
return self.i
按我的理解,迭代器就是一种接口定义,迭代器本身和延迟计算都是在python解释器中进行了包装或者说有语法糖。
例如说:
b = IntFromOne()
for i in b:
do somthing
这里的for语句中,解释器就进行了封装工作,将IntFromOne()对象转成迭代器,并不断调用next()方法,如果要停下来,就必须在next方法中返回stopiter的异常。
还有另外一个更强大一点的generator,我理解generator是利用python解释器是c运行,python函数的堆栈在堆中的特性技术,利用这种技术包装为迭代器。使用yield关键字,不仅能简单的实现迭代器,而且比较符合思考过程。至于这种技术计划再用一篇文章写一下,比较有意思。
面向对象实现延迟计算
这里主要是用描述符定义了decorator,用以描述属性
class lazypropery:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
if instanse is None:
return self
else:
value = self.func(instance)
setattr(instanse, self.func.__name__, value)
return value
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@lazyproperty
def area(self):
return math.pi * self.radius ** 2
只有调用area属性的时候它才会被计算出来,否则不会计算。
特性 -- Pipeline
用reduce可以表达
def pipe(data, funs):
return reduce(lambda a, x: x(a), funs, data)
还可以用装饰器把pipeline写成linux的流模式,但这就不在本文讨论范围内了。
总结
python的函数式, 需要按照纯函数、高阶函数、递归的方式思考问题,可以利用珂里化、延迟计算、Pipeline等函数式特性,这么玩,才算函数式。
网友评论