Python函數(shù)式編程最佳實踐

Python并非經(jīng)典的FP(Functional Programming, 函數(shù)式編程)語言,用其原生的map/filter/reduce寫法來實現(xiàn)函數(shù)式編程顯得相當(dāng)臃腫;好在可以通過第三方庫提供的語法糖來簡化代碼烛缔。
本文將簡明扼要地示范兩個常用庫的寫法生逸;讀完,你就能寫出一手優(yōu)雅而簡潔的函數(shù)式Python代碼了

  • 使用PyFunctional來提供類似Streaming API谓传,形成鏈?zhǔn)秸{(diào)用
  • 使用fn.py來簡化lambda表達式的書寫

背景

FP (Functional Programming, 函數(shù)式編程) 是一種有效慷暂、簡潔聘殖、優(yōu)雅,且對并發(fā)相當(dāng)友好的編程范式行瑞。
可惜python并不原生地支持FP奸腺;原因之一是Python的作者Guido大爺并非FP的粉絲,下面是老爺子對FP的看法[1]

I have never considered Python to be heavily influenced by functional languages, no matter what people say or think. I was much more familiar with imperative languages such as C and Algol 68 and although I had made functions first-class objects, I didn't view Python as a functional programming language. However, earlier on, it was clear that users wanted to do much more with lists and functions.

...

It is also worth noting that even though I didn't envision Python as a functional language, the introduction of closures has been useful in the development of many other advanced programming features. For example, certain aspects of new-style classes, decorators, and other modern features rely upon this capability.

Lastly, even though a number of functional programming features have been introduced over the years, Python still lacks certain features found in “real” functional programming languages. For instance, Python does not perform certain kinds of optimizations (e.g., tail recursion). In general, because Python's extremely dynamic nature, it is impossible to do the kind of compile-time optimization known from functional languages like Haskell or ML. And that's fine.

--- Guido van Rossum

經(jīng)過一段時間的摸索和總結(jié)血久,我找到了一套適用于Python的比較簡潔的FP最佳實踐突照。具體來說就是使用文章開頭提到的兩個庫,下面分別演示一下這兩個庫的基礎(chǔ)用法氧吐,并結(jié)合幾個實際工作中的例子略作演示讹蘑。
注意:本文既然是寫B(tài)CP末盔,就打算只講干貨,盡量少些奇技淫巧衔肢;如果想進一步深入了解;我會在文末給出一些閱讀材料

PyFunctional

PyFunctional提供了Streaming API豁翎,非常方便序列的鏈?zhǔn)讲僮鹘侵瑁送膺€有提供一些IO小工具。
下面來看幾個例子心剥。

IO

pip install pyfunctional

%%bash
mkdir ./res
cat << EOF > ./res/json_lines.json
{"name":"Alice", "age":5}
{"name":"Bob", "age":6}
EOF
from functional import seq # 注意包名不是pyfunctional
seq.jsonl('./res/json_lines.json')
# [{'age': 5, 'name': 'Alice'}, {'age': 6, 'name': 'Bob'}]
seq.jsonl('./res/json_lines.json').to_pandas()
to_pandas.png

Streaming API

# range 的用法
assert seq.range(5) == seq(range(5)) 
assert seq.range(5) == seq([0,1,2,3,4])
assert seq.range(5) == seq(0,1,2,3,4)
assert seq.range(5) == [0,1,2,3,4]

accumulate / aggregate

accumulate是aggregate的一個特例邦尊;所以已經(jīng)deprecated
aggregate支持1~3個參數(shù),分別代表

  • arg1: init_value=None优烧, 聚合開始時所用的初始值
  • arg2: fn, (current, next) ==> result蝉揍, 兩兩聚合所用的函數(shù)
  • arg3: agg_func=None, 在返回結(jié)果前執(zhí)行的最后一個映射
seq.range(5).aggregate(-2, operator.add, str)
# '8'

Cartesian 笛卡爾積

s1 = range(2)
s2 = set('abc')
seq(s1).cartesian(s2).to_list() # 求s1與s2的笛卡爾積
# [(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), (1, 'b'), (1, 'c')]

count / count_by_key / count_by_value

f = X%2==0
seq.range(5).count(f) # 這個f必填
# 3

# count_by_key 已經(jīng)過時畦娄,用 reduce_by_key近似
seq([('a', 1), ('b', 2), ('b', 3), ('b', 4), ('c', 3), ('c', 0)]) .reduce_by_key(operator.add).to_list()
# [('a', 1), ('b', 9), ('c', 3)]

# count_by_value不可用又沾,用Counter近似
from collections import Counter
s = seq(['a', 'a', 'a', 'b', 'b', 'c', 'd'])
Counter(s)
# Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1})

dict

import numpy as np

# 可以直接傳固定值
# d = seq([('a', 1), ('b', 2)]).dict('nan')

# 也可以傳default_func,就像 defaultdict(default_func) 一樣
d = seq([('a', 1), ('b', 2)]).dict(np.random.rand)
d['a'],d['c'],d['d'],d['e'] # 每次求新值的時候熙卡,要運行一下defaultdict的init_value函數(shù)
# (1, 0.1045269208888322, 0.2721328917391167, 0.8890019300218747)

d['a'],d['c'],d['d'],d['e'] # 多次執(zhí)行有緩存
# (1, 0.1045269208888322, 0.2721328917391167, 0.8890019300218747)

drop / drop_right / drop_while

seq([1, 2, 3, 4, 5]).drop(2) # 去掉開頭的2個元素
seq([1, 2, 3, 4, 5]).drop_right(2) # 去掉結(jié)尾的2個元素
seq([1, 2, 3, 4, 5, 9, 2]).drop_while(X < 3) # 一直drop杖刷,直到遇到條件為False
# [3, 4, 5, 9, 2]

exists / for_all

seq(1,2).exists(X>=2)
# True

def is_even_log(n):
    print('log: {}'.format(n))
    return n%2==0
seq.range(10).for_all(is_even_log) # for_all是當(dāng)且僅當(dāng)序列中的全部元素都能算出True時,才為True驳癌;從log看滑燃,有l(wèi)azy_eval
# log: 0
# log: 1
# Out[31]: False

flatmap / flatten

seq([[1, 2], [3, 4], [5, 6]]).flatten() # 將 arr_or_arr 展開打平 
# [1, 2, 3, 4, 5, 6]

arr_of_arr = [[1, 2, 3], [3, 4], [5, 6]]
fn = X
seq(arr_of_arr).flat_map(lambda a: [min(a),]*4)
# 對arr_of_arr中的每個子序列執(zhí)行fn映射,得到新的子序列(記作ARR)颓鲜;然后對ARR組成的大序列執(zhí)行flatten
# [1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 5, 5]

fold_left / fold_right

# 從起始值開始表窘, 逐個調(diào)用傳入的fold函數(shù)
seq.range(3).fold_left('a', lambda c,n: '{}_{}'.format(c,n) ) 
# 'a_0_1_2'

# 從起始值開始, 逐個調(diào)用傳入的fold函數(shù)甜滨; 注意lambda中兩個參數(shù)的順序互換了
seq.range(3).fold_right('a', lambda n,c: '{}_{}'.format(c,n) ) 
# 'a_2_1_0'

group_by / group_by_key / grouped

seq(["abc", "ab", "z", "f", "qw"]).group_by(len).list()
# [(1, ['z', 'f']), (2, ['ab', 'qw']), (3, ['abc'])]

seq([('a', 1), ('b', 2), ('b', 3), ('b', 4), ('c', 3), ('c', 0)]).group_by_key().list()
# [('a', [1]), ('b', [2, 3, 4]), ('c', [3, 0])]

# group支持相鄰元素大致分組乐严,可以用slicing近似(后文有提到)
# 這里的兩個list函數(shù),是為了方便觀察(不然就打印出一堆generator)
seq([1, 2, 3, 4, 5, 6, 7, 8]).grouped(3).map(list).list()
# [[1, 2, 3], [4, 5, 6], [7, 8]]

init / inits / tail / tails

assert seq.range(5).init() == seq.range(5).drop_right(1)
# 命名很詭異衣摩,init不是(initialization, 初始化)麦备,而是除最后一個元素以外的子序列
seq.range(5).init()
# [0, 1, 2, 3]

seq.range(5).inits().list() # inits 是含開頭元素的所有子序列
# [[0, 1, 2, 3, 4], [0, 1, 2, 3], [0, 1, 2], [0, 1], [0], []]

seq.range(5).tail() # 除開頭元素以外的子序列
# [1, 2, 3, 4]

seq.range(5).tails().list() # 含尾元素在內(nèi)的所有子序列
# [[0, 1, 2, 3, 4], [1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []]

join

seq([('a', 1), ('b', 2), ('c', 3)]).join([('a', 2), ('c', 5)], "inner") # inner是默認行為,可省略
# [('a', (1, 2)), ('c', (3, 5))]

seq([('a', 1), ('b', 2)]).join([('a', 3), ('c', 4)], "left")
# [('a', (1, 3)), ('b', (2, None))]

seq([('a', 1), ('b', 2)]).join([('a', 3), ('c', 4)], "right")
seq([('a', 1), ('b', 2)]).join([('a', 3), ('c', 4)], "outer")

make_string

其實就是str.join昭娩,但是join關(guān)鍵字已經(jīng)被用了

seq(['a','b',1,{'name':'jack'}]).make_string('@')
# "a@b@1@{'name': 'jack'}"

order_by

from fn import F # 這是fn.py提供的復(fù)合函數(shù)語法糖凛篙,后文有講
seq(1,'abc',55,9999,718).order_by(F(str)>>len)
# [1, 55, 'abc', 718, 9999]

partition

seq.range(-5,5).partition(X>0) # 返回 (truthy, falsy)
# [[1, 2, 3, 4], [-5, -4, -3, -2, -1, 0]]

reduce_by_key

seq([('a', 1), ('b', 2), ('b', 3), ('b', 4), ('c', 3), ('c', 0)]).reduce_by_key(X+X)
# [('a', 1), ('b', 9), ('c', 3)]

sliding 滑動窗口

第一個參數(shù)是size,第二個是step(默認=1)

# 模擬100ms的語音栏渺,每25ms為一幀呛梆,滑動窗口為10ms
seq.range(100).sliding(25, 10)

# [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
# [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34],
# ...

sorted

seq('a1','c9','b3','b2').sorted(key=X[1], reverse=True)
# ['c9', 'b3', 'b2', 'a1']

starmap / smap

starmap是把序列中的元素作為參數(shù),逐個用func作映射

seq([(2, 3), (-2, 1), (0, 10)]).smap(X+X)
# [5, -1, 10]

zip / zip_with_index

seq.range(3).zip('abc').list()
# [(0, 'a'), (1, 'b'), (2, 'c')]

seq(list('abc')).zip_with_index(start=9).list()
# [('a', 9), ('b', 10), ('c', 11)]

fn.py

fn.py提供了一些高級的FP工具磕诊,本身也提供了類似于PyFunctional的一部分基礎(chǔ)FP功能(e.g. map/reduce/filter)填物;但是這些功能在fn.py中不支持鏈?zhǔn)秸{(diào)用語法纹腌,不太方便,本文就不講了
除此之外滞磺,fn.py還提供了一些高級的FP特性升薯,包括:

  • underscore表達式
  • 函數(shù)復(fù)合管道F()
  • 無窮Stream
  • Monad Optional
  • TCO 尾遞歸優(yōu)化
  • Immutable容器
  • ...

初次看到這些概念的同學(xué)可以會對一部分術(shù)語感到陌生,但是不用擔(dān)心击困,后面我會在例子中講清楚

underscore表達式

熟悉前端的同學(xué)應(yīng)該聽說過涎劈,有個Javascript庫叫作Underscore.js;它提供了一種用于書寫lambda表達式的語法糖[2]
fn.py也提供了類似的功能

Identity 常函數(shù)

先安裝: pip install fn

from fn import _ as X # 在ipython環(huán)境中阅茶,下劃線有特征語義蛛枚,要回避開
f = X # 空的underscore本身就是Identity函數(shù)
print(f) # (x1) => x1
print(f(10)) # 10

Arithmetic 基礎(chǔ)算術(shù)

f = X+2
f(3) # 5

# 還支持 乘方/取模/移位/取反/位運算 ...
(X ** 2)(3) # 9
(X % 2)(3) # 1
(X << 2)(8) # 32
(1 & X)(0) # 0

多參數(shù)

(X-X)(5,3) # 2

property/attribute getter

class Foo(object):
    def __init__(self):
        self.attr1 = 10
        self.attr2 = 'mystr'
    @property
    def prop1(self):
        return self.attr2.upper()
    def method1(self):
        return self.attr2.capitalize()
    
foo = Foo()
(X.attr1*-2)(foo) # -20
(X.attr2)(foo) # 'mystr'
(X.prop1)(foo) # 'MYSTR'
(X.method1())(foo) # ArityError, 不支持這樣調(diào)method,正確用法見下文

method caller

f = X.call('method1')
f(foo) # 'Mystr'

f = X.call('split','-')
f('abc-def') # ['abc', 'def']

d = {'num':32}
f = X.call('update', num=42)
f(d) # None
d # {'num': 42}

str

可以用一種可能讀較好的方式把underscore表達式打印出來脸哀,還能自動處理優(yōu)先級

f = X + X * X 
str(f) # '(x1, x2, x3) => (x1 + (x2 * x3))'

函數(shù)復(fù)合管道F()

from fn import F

f = X * 2
g = X + 10
h = X * 10

func = F(f) << g << h
func(5) # [(?*10) + 10] * 2
# 注意 << 是向內(nèi)復(fù)合蹦浦,好記:箭頭從右向左指,表示先執(zhí)行右邊的撞蜂,再代入左邊的函數(shù)

(F(f) << F(g) << F(h))(5)  # 多個F對象也可以復(fù)合

# F()中預(yù)填入一部分參數(shù)盲镶,就是partial
import operator
f = F(operator.sub, 10)
f(3) # 7

# Pipe Composition 向外復(fù)合
f = F(X**2) >> (X + 10)
f(5) # 35

無窮Stream

這是一個比較難懂的語法,我也沒空去讀源碼搞透徹蝌诡;大概意思就是一個懶漢式的生成器的串聯(lián)徒河。

Lazy-evaluated Scala-style streams. Basic idea: evaluate each new element "on demand" and share calculated elements between all created iterators. Stream object supports << operator that means pushing new elements when it's necessary.

--- 官網(wǎng)解釋

下面的demo盡量體會一下就好,不求甚解送漠。

from fn import Stream

# 遞歸slice
s = Stream() << range(10)
list(s[2:9][:3])  # s[2:9]是[2,3,4,5,6,7,8]顽照,對這個Stream再取[:3]就是[2,3,4]

# fibonacci
s = Stream() << [0, 1]
fib = s << iters.map(operator.add, s, iters.drop(1, s))
# 注意最右邊的<<是一直在往Stream里面插入新值,導(dǎo)致s的cursor往后走
list(iters.take(5, fib))

Monad Optional[3]

跟Java8的Optional差不多闽寡,用來包裹Nullable對象

基本用法

from fn import monad

monad.Option(10, checker=X > 70) # Empty()
monad.Empty(5) # Empty()
monad.Full(10) # Full(10)
monad.Option.from_value(10) # Full(10)
monad.Option.from_call(X**2, 5) # Full(25)
monad.Option.from_call(X[X], dict(k='v'), 'bad_k', exc=KeyError) # Empty()

# 自動flatten代兵,說明這個庫內(nèi)部還是有優(yōu)化的
monad.Option(monad.Option(monad.Option(10))) # Full(10)

map/filter, 這個比較實用,用函數(shù)式的方法從Nullable中安全地取值

class Request(dict):  # 注意這里有個繼承
    def param(self, name):
        return monad.Option(self.get(name, None))  # 注意這里爷狈,手動包了一層monad.Option

    # 注意植影,這個函數(shù)跟上面功能相同;只是自動被包裹了一層 Option涎永;不用自己寫monad.Option(...)思币;可讀性更好,且對舊代碼改動小
    @monad.optionable
    def optParam(self, name):
        return self.get(name, None)
# 定義好對象
req = Request(testing='Fixed', empty='')

# demo1: 順利取值
(req.param('testing')  # 拿到monad參數(shù)
 .map(operator.methodcaller('strip'))  # 去掉首尾空白字符
 .filter(len)  # 去掉空串
 .map(operator.methodcaller('upper'))  # 轉(zhuǎn)大寫
 .get_or('empty_value')  # 取出值來
)
# 'FIXED'

# demo2: 注解式寫法的optParam跟上面等效
(req.optParam('testing')  # optParam函數(shù)用的是注解式寫法
 .map(operator.methodcaller('strip')).filter(len)
 .map(operator.methodcaller('upper')).get_or('empty_value'))
# 'FIXED'

# demo3: 用filter濾掉指定的值后羡微,變成Empty
(req.param('empty').map(operator.methodcaller('strip'))
 .filter(len)  # 去掉空串, 變成Monad(None)
 .map(operator.methodcaller('upper')).get_or('empty_value'))
# 'empty_value'

# demo4: 一開始就到Optional(None)谷饿,即Empty;后面的鏈?zhǔn)絤ap/filter步驟skip
(req.param('missing')  # missing字段不存在妈倔,拿到的是Monad.Empty
 .map(operator.methodcaller('strip')).filter(len)
 .map(operator.methodcaller('upper')).get_or('empty_value'))
# 'empty_value'

or_call調(diào)用博投,通過函數(shù)來取fallback值

# demo1: 直接取到值
r = dict(type='jpeg')
# 注意第一步就用monad.Option給包裹起來了,后面才能方便的鏈?zhǔn)秸{(diào)用or_call/map等方法
(monad.Option(r.get('type', None))  # 直接得到Full值盯蝴,下面的or_call被跳過
 .or_call(from_mimetype, r)
 .or_call(from_extension, r)
 .map(operator.methodcaller('upper'))
 .get_or('unknown'))
# 'JPEG'
 
# demo2: 直接用返回值None表示Empty
from_mimetype = X.call('get', 'mimetype', None) # 可以直接返回值(None)表示Empty
r = dict(mimetype='png')
(monad.Option(r.get('type', None))  # monad.Empty
 .or_call(from_mimetype, r) # 上面是Emtpy毅哗,觸發(fā)這一步or_call的執(zhí)行,得monad.Full('png')
 .or_call(from_extension, r) # 已經(jīng)是Full值听怕,這個or_call被跳過
 .map(operator.methodcaller('upper'))
 .get_or('unknown'))
 
# demo3: 顯式地返回Empty
def from_extension(r):
    return (monad.Option(r.get('url', None)).map(X.call('split', '.')[-1]))
r = dict(url='image.gif')
(monad.Option(r.get('type', None))  # monad.Empty
 .or_call(from_mimetype, r) # 這一步調(diào)用返回None,還是Empty
 .or_call(from_extension, r)  # 這個函數(shù)是顯式地返回了Empty,跟上面的demo等效,得monad.Full('gif')
 .map(operator.methodcaller('upper'))
 .get_or('unknown'))
 
# demo4: 最后的fallback
r = dict()
(monad.Option(r.get('type', None)) # 這次r是空字典虑绵,get到的是Option(None)尿瞭,即Empty
 .or_call(from_mimetype, r) # 沒有mimetype信息,返回的是None翅睛,仍然Empty
 .or_call(from_extension, r)  # 同上声搁,仍然Emtpy
 .map(operator.methodcaller('upper')) # Empty對象跳過map映射
 .get_or('unknown')) # 取到fallback值

TCO(Tail Call Optimization, 尾遞歸優(yōu)化)

對很多問題來說,遞歸是一種很直觀/順手的寫法宏所,描述能力強酥艳,不費腦細胞摊溶。
然而爬骤,遞歸由于需要額外的函數(shù)調(diào)用開銷,性能一般比循環(huán)的寫法差一些莫换;而且函數(shù)棧的嘗試是有限的霞玄,超過之后就會報錯。
在某些語言中拉岁,內(nèi)置了尾調(diào)用優(yōu)化(建議讀一讀)的特性:當(dāng)最后一個語句是純粹的遞歸調(diào)用時坷剧,棧深度不變,只改動參數(shù)表喊暖,性能上相當(dāng)于goto語句惫企。
Python并非經(jīng)典的函數(shù)式語言,并沒有內(nèi)置TCO機制陵叽;但是可以用fn.py來近似狞尔。

準(zhǔn)備工作

!pip install memory_profiler
%load_ext memory_profiler
import sys
sys.getrecursionlimit() # 1000, 下面的python原生寫法中,如果超過這個深度會報錯

用經(jīng)典的遞歸寫法實現(xiàn)fibonacci巩掺,測時間&內(nèi)存開銷

%%time
def factorial_normal_recurive(n): # 最直觀的寫法偏序, f(n) = f(n-1) * n
    if n==1:
        return 1
    else:
        return factorial_normal_recurive(n-1) * n

%memit factorial_normal_recurive(900)
# peak memory: 38.27 MiB, increment: 0.86 MiB
# CPU times: user 46 ms, sys: 56.5 ms, total: 103 ms
# Wall time: 242 ms

寫成尾遞歸,執(zhí)行時間快一點

%%time
def factorial_tail_recursive(n, acc=1): # 尾遞歸胖替,把子遞歸的結(jié)果直接返回研儒; f(n,acc) = f(n-1, acc*n)
    if n==1:
        return acc
    else:
        return factorial_tail_recursive(n-1, acc*n)
%memit factorial_tail_recursive(900)
# peak memory: 39.48 MiB, increment: 1.18 MiB
# CPU times: user 41.6 ms, sys: 30.4 ms, total: 72 ms
# Wall time: 208 ms

開啟fn.py的TCO,調(diào)用棧深度可以超過解釋器的限制独令;而且會被翻譯成循環(huán)/goto調(diào)用端朵,性能也更好(這個沒測)

# 為了方便TCO, 原先函數(shù)的返回語句要寫成以下3種格式之一:
# (False, result) means that we finished
# (True, args, kwargs) means that we need to call function again with other arguments
# (func, args, kwargs) to switch function to be executed inside while loop # f1和f2相互遞歸調(diào)用時用到

%%time
from fn import recur
@recur.tco
def factorial_tco(n, acc=1): # 跟尾遞歸同理,但是寫成了fn.py指定的格式燃箭;便于開啟TCO自動優(yōu)化
    if n==1:
        return False, acc
    else:
        return True, (n-1, acc*n)
%memit factorial_tco(90000) # 注意逸月,這里的尾調(diào)用已經(jīng)被優(yōu)化成了循環(huán),所以可以加大深度超過解釋器的限制
# 性能不用看遍膜,這次測試的是90000碗硬,比上面幾次實驗大多了
# peak memory: 44.66 MiB, increment: 5.17 MiB
# CPU times: user 3.11 s, sys: 36.4 ms, total: 3.15 s
# Wall time: 3.3 s

Immutable容器

眾所周知瓤湘,python中的str是immutable的,字符串無法inplace modify恩尾,只能通過賦值指向新的地址弛说。[4]

strA='hello'
print(id(strA)) # 4367427544
strA+=' world'
print(id(strA)) # 4373528112
print(strA) # hello world

這種immutable的設(shè)計,正好契合了FP中“無副作用的函數(shù)”的理念翰意。

SkewHeap / PairingHeap

基礎(chǔ)用法

from fn.immutable import SkewHeap, PairingHeap, LinkedList, Stack, Queue, Vector, Deque

heap = SkewHeap()
for i in [9,3,5,2]:
    heap = heap.insert(i) # 注意是Immutable木人,不能inplace改,只能賦值改引用
list(heap) # 不指定cmp時冀偶,用的是自然排序
# [2, 3, 5, 9]

ele, remain = heap.extract()
ele, list(remain)
# (2, [3, 5, 9])

指定key/cmp

from collections import namedtuple
Student = namedtuple('Student', ['age','name'])
s1 = Student(7, 'Tora')
s2 = Student(5, 'Ponyo')
s3 = Student(6, 'Sousuke')

heap = SkewHeap(key=operator.attrgetter('age')) # 指定key
for s in [s1,s2,s3]:
    heap = heap.insert(s)
list(heap)
# [Student(age=5, name='Ponyo'),
#  Student(age=6, name='Sousuke'),
#  Student(age=7, name='Tora')]

heap = SkewHeap(cmp=X.age-X.age) # 指定cmp
for s in [s1,s2,s3]:
    heap = heap.insert(s)
list(heap)
# [Student(age=5, name='Ponyo'),
#  Student(age=6, name='Sousuke'),
#  Student(age=7, name='Tora')]

支持存儲Pair的堆

ph = PairingHeap() # 支持存Pair的堆
ph = ph.insert((15,'a'))
ph = ph.insert((12,'b'))
ph = ph.insert((13,'c'))
list(ph)
# [(12, 'b'), (13, 'c'), (15, 'a')]

Else

除了堆以外醒第,還對常見的各種數(shù)據(jù)結(jié)構(gòu)進行了Immutable封裝;不多說了进鸠,詳見官網(wǎng)文檔

來幾枚大栗子

朕累了稠曼,寫不動了;以后遇到合適的例子就補到這里客年。

tired_meow.jpeg

More

感興趣的同學(xué)可以自己探索以下資源


  1. 摘自StackOverflow上的一條回答 ?

  2. ES6中的lambda表達式可能會好寫一點霞幅,我了解不多,歡迎感興趣的同學(xué)評論留言 ?

  3. Monad和Optional不是一碼事量瓜;前于前者可以看阮一峰的這篇文章司恳,后者可以參考Java8 Optional來理解 ?

  4. 關(guān)于python中的可變/不可變類型,這篇文章總結(jié)得不錯 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绍傲,一起剝皮案震驚了整個濱河市扔傅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烫饼,老刑警劉巖猎塞,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異枫弟,居然都是意外死亡邢享,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門淡诗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骇塘,“玉大人,你說我怎么就攤上這事韩容】钗ィ” “怎么了?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵群凶,是天一觀的道長插爹。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么赠尾? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任力穗,我火速辦了婚禮,結(jié)果婚禮上气嫁,老公的妹妹穿的比我還像新娘当窗。我一直安慰自己,他們只是感情好寸宵,可當(dāng)我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布崖面。 她就那樣靜靜地躺著,像睡著了一般梯影。 火紅的嫁衣襯著肌膚如雪巫员。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天甲棍,我揣著相機與錄音简识,去河邊找鬼。 笑死救军,一個胖子當(dāng)著我的面吹牛财异,可吹牛的內(nèi)容都是我干的倘零。 我是一名探鬼主播唱遭,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呈驶!你這毒婦竟也來了拷泽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤袖瞻,失蹤者是張志新(化名)和其女友劉穎司致,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聋迎,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡脂矫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了霉晕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庭再。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牺堰,靈堂內(nèi)的尸體忽然破棺而出拄轻,到底是詐尸還是另有隱情,我是刑警寧澤伟葫,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布恨搓,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斧抱。R本人自食惡果不足惜常拓,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辉浦。 院中可真熱鬧墩邀,春花似錦、人聲如沸盏浙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽废膘。三九已至竹海,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丐黄,已是汗流浹背斋配。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灌闺,地道東北人艰争。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像桂对,于是被迫代替她去往敵國和親甩卓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容