백엔드 개발 파이썬 튜토리얼 利用Fn.py库在Python中进行函数式编程

利用Fn.py库在Python中进行函数式编程

Jun 06, 2016 am 11:25 AM
python

尽管Python事实上并不是一门纯函数式编程语言,但它本身是一门多范型语言,并给了你足够的自由利用函数式编程的便利。函数式风格有着各种理论与实际上的好处(你可以在Python的文档中找到这个列表):

  •     形式上可证
  •     模块性
  •     组合性
  •     易于调试及测试

虽然这份列表已经描述得够清楚了,但我还是很喜欢Michael O.Church在他的文章“函数式程序极少腐坏(Functional programs rarely rot)”中对函数式编程的优点所作的描述。我在PyCon UA 2012期间的讲座“Functional Programming with Python”中谈论了在Python中使用函数式方式的内容。我也提到,在你尝试在Python中编写可读同时又可维护的函数式代码时,你会很快发现诸多问题。

fn.py类库就是为了应对这些问题而诞生的。尽管它不可能解决所有问题,但对于希望从函数式编程方式中获取最大价值的开发者而言,它是一块“电池”,即使是在命令式方式占主导地位的程序中,也能够发挥作用。那么,它里面都有些什么呢?
Scala风格的Lambda定义

在Python中创建Lambda函数的语法非常冗长,来比较一下:

Python

map(lambda x: x*2, [1,2,3])

로그인 후 복사

Scala

代码如下:


List(1,2,3).map(_*2)


Clojure

代码如下:


(map #(* % 2) '(1 2 3))


Haskell

代码如下:


map (2*) [1,2,3]


受Scala的启发,Fn.py提供了一个特别的_对象以简化Lambda语法。

from fn import _

assert (_ + _)(10, 5) = 15
assert list(map(_ * 2, range(5))) == [0,2,4,6,8]
assert list(filter(_ < 10, [9,10,11])) == [9]

로그인 후 복사

除此之外还有许多场景可以使用_:所有的算术操作、属性解析、方法调用及分片算法。如果你不确定你的函数具体会做些什么,你可以将结果打印出来:

from fn import _ 

print (_ + 2) # "(x1) => (x1 + 2)" 
print (_ + _ * _) # "(x1, x2, x3) => (x1 + (x2 * x3))"

로그인 후 복사

流(Stream)及无限序列的声明

Scala风格的惰性求值(Lazy-evaluated)流。其基本思路是:对每个新元素“按需”取值,并在所创建的全部迭代中共享计算出的元素值。Stream对象支持<<操作符,代表在需要时将新元素推入其中。

惰性求值流对无限序列的处理是一个强大的抽象。我们来看看在函数式编程语言中如何计算一个斐波那契序列。

Haskell

代码如下:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

Clojure

代码如下:

(def fib (lazy-cat [0 1] (map + fib (rest fib))))

Scala

代码如下:

def fibs: Stream[Int] =
0 #:: 1 #:: fibs.zip(fibs.tail).map{case (a,b) => a + b}

现在你可以在Python中使用同样的方式了:

from fn import Stream 
from fn.iters import take, drop, map
from operator import add

f = Stream()
fib = f << [0, 1] << map(add, f, drop(1, f))

assert list(take(10, fib)) == [0,1,1,2,3,5,8,13,21,34]
assert fib[20] == 6765
assert list(fib[30:35]) == [832040,1346269,2178309,3524578,5702887]

로그인 후 복사

蹦床(Trampolines)修饰符

fn.recur.tco是一个不需要大量栈空间分配就可以处理TCO的临时方案。让我们先从一个递归阶乘计算示例开始:

def fact(n):
   if n == 0: return 1
   return n * fact(n-1)

로그인 후 복사

这种方式也能工作,但实现非常糟糕。为什么呢?因为它会递归式地保存之前的计算值以算出最终结果,因此消耗了大量的存储空间。如果你对一个很大的n值(超过了sys.getrecursionlimit()的值)执行这个函数,CPython就会以此方式失败中止:

>>> import sys
>>> fact(sys.getrecursionlimit() * 2)
... many many lines of stacktrace ...
RuntimeError: maximum recursion depth exceeded

로그인 후 복사

这也是件好事,至少它避免了在你的代码中产生严重错误。

我们如何优化这个方案呢?答案很简单,只需改变函数以使用尾递归即可:

def fact(n, acc=1):
   if n == 0: return acc
   return fact(n-1, acc*n)

로그인 후 복사

为什么这种方式更佳呢?因为你不需要保留之前的值以计算出最终结果。可以在Wikipedia上查看更多尾递归调用优化的内容。可是……Python的解释器会用和之前函数相同的方式执行这段函数,结果是你没得到任何优化。

fn.recur.tco为你提供了一种机制,使你可以使用“蹦床”方式获得一定的尾递归优化。同样的方式也使用在诸如Clojure语言中,主要思路是将函数调用序列转换为while循环。

from fn import recur

@recur.tco 
def fact(n, acc=1):
   if n == 0: return False, acc
   return True, (n-1, acc*n)

로그인 후 복사

@recur.tco是一个修饰符,能将你的函数执行转为while循环并检验其输出内容:

  • (False, result)代表运行完毕
  • (True, args, kwargs)代表我们要继续调用函数并传递不同的参数
  • (func, args, kwargs)代表在while循环中切换要执行的函数

函数式风格的错误处理

假设你有一个Request类,可以按照传入其中的参数名称得到对应的值。要想让其返回值格式为全大写、非空并且去除头尾空格的字符串,你需要这样写:

class Request(dict):
   def parameter(self, name):
     return self.get(name, None)

r = Request(testing="Fixed", empty=" ")
param = r.parameter("testing")
if param is None:
   fixed = ""
else:   
   param = param.strip()
   if len(param) == 0:
     fixed = ""
   else:
    fixed = param.upper() 

로그인 후 복사

额,看上去有些古怪。用fn.monad.Option来修改你的代码吧,它代表了可选值,每个Option实例可代表一个Full或者Empty(这点也受到了Scala中Option的启发)。它为你编写长运算序列提供了简便的方法,并且去掉除了许多if/else语句块。

from operator import methodcaller
from fn.monad import optionable

class Request(dict):
   @optionable
   def parameter(self, name):
     return self.get(name, None)

r = Request(testing="Fixed", empty=" ")
fixed = r.parameter("testing") 
     .map(methodcaller("strip")) 
     .filter(len) 
     .map(methodcaller("upper")) 
     .get_or("")

로그인 후 복사

fn.monad.Option.or_call是个便利的方法,它允许你进行多次调用尝试以完成计算。例如,你有一个Request类,它有type,mimetype和url等几个可选属性,你需要使用最少一个属性值以分析它的“request类型”:

from fn.monad import Option 

request = dict(url="face.png", mimetype="PNG") 
tp = Option \ 
     .from_value(request.get("type", None)) \ # check "type" key first 
     .or_call(from_mimetype, request) \ # or.. check "mimetype" key 
     .or_call(from_extension, request) \ # or... get "url" and check extension 
     .get_or("application/undefined")

로그인 후 복사

其余事项?

我仅仅描述了类库的一小部分,你还能够找到并使用以下功能:

  •     22个附加的itertools代码段,以扩展内置module的功能的附加功能
  •     将Python 2和Python 3的迭代器(iterator)(如range,map及filtter等等)使用进行了统一,这对使用跨版本的类库时非常有用
  •     为函数式组合及partial函数应用提供了简便的语法
  •     为使用高阶函数(apply,flip等等)提供了附加的操作符

正在进行中的工作

自从在Github上发布这个类库以来,我从社区中收到了许多审校观点、意见和建议,以及补丁和修复。我也在继续增强现有功能,并提供新的特性。近期的路线图包括以下内容:

  •     为使用可迭代对象(iterable),如foldl,foldr增加更多操作符
  •     更多的monad,如fn.monad.Either,以处理错误记录
  •     为大多数module提供C-accelerator
  •     为简化lambda arg1: lambda arg2:…形式而提供的curry函数的生成器
  •     更多文档,更多测试,更多示例代码

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Linux 시스템에서 Python 통역사를 삭제할 수 있습니까? Linux 시스템에서 Python 통역사를 삭제할 수 있습니까? Apr 02, 2025 am 07:00 AM

Linux 시스템과 함께 제공되는 Python 통역사를 제거하는 문제와 관련하여 많은 Linux 배포판이 설치 될 때 Python 통역사를 사전 설치하고 패키지 관리자를 사용하지 않습니다 ...

파이썬에서 맞춤형 데코레이터의 Pylance 유형 감지 문제를 해결하는 방법은 무엇입니까? 파이썬에서 맞춤형 데코레이터의 Pylance 유형 감지 문제를 해결하는 방법은 무엇입니까? Apr 02, 2025 am 06:42 AM

Pylance 유형 감지 문제 솔루션 Python 프로그래밍에서 사용자 정의 데코레이터를 사용할 때 Decorator는 행을 추가하는 데 사용할 수있는 강력한 도구입니다 ...

Python의 HTTPX 라이브러리를 사용하여 HTTP/2 Post 요청을 보내는 방법은 무엇입니까? Python의 HTTPX 라이브러리를 사용하여 HTTP/2 Post 요청을 보내는 방법은 무엇입니까? Apr 01, 2025 pm 11:54 PM

Python의 HTTPX 라이브러리를 사용하여 HTTP/2를 보내십시오 ...

Python 3.6 피클 파일로드 오류 modulenotfounderRor : 피클 파일 '__builtin__'를로드하면 어떻게해야합니까? Python 3.6 피클 파일로드 오류 modulenotfounderRor : 피클 파일 '__builtin__'를로드하면 어떻게해야합니까? Apr 02, 2025 am 06:27 AM

Python 3.6에 피클 파일 로딩 3.6 환경 오류 : ModulenotFounderRor : nomodulename ...

Fastapi와 Aiohttp는 동일한 글로벌 이벤트 루프를 공유합니까? Fastapi와 Aiohttp는 동일한 글로벌 이벤트 루프를 공유합니까? Apr 02, 2025 am 06:12 AM

파이썬 비동기 라이브러리 사이의 호환성 문제 파이썬에서 비동기 프로그래밍은 동시성과 I/O의 프로세스가되었습니다 ...

Python 3.6에 피클 파일을로드 할 때 '__builtin__'모듈을 찾을 수없는 경우 어떻게해야합니까? Python 3.6에 피클 파일을로드 할 때 '__builtin__'모듈을 찾을 수없는 경우 어떻게해야합니까? Apr 02, 2025 am 07:12 AM

Python 3.6에 피클 파일로드 3.6 환경 보고서 오류 : modulenotfounderror : nomodulename ...

See all articles