python函数式编程之functools、itertools、operator详解
文章目錄
- 寫在篇前
- itertools
- 無窮迭代器
- 最短停止迭代器
- 排列組合迭代器
- operator
- 基本運算符函數
- 屬性查詢
- functools
- partial & partialmethod
- cmp_to_key
- @total_ordering
- @lru_cache
- @singledispatch
- 附錄
- 可變對象 & 不可變對象
- 閉包
- reference
寫在篇前
??這篇博客主要介紹什么是函數式編程、在Python中怎么進行函數式編程。函數式編程(Functional Programming)是一種編程范式,它將計算機運算視為函數運算,并且避免使用程序狀態以及mutable對象。因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之為沒有副作用。而允許使用變量的程序設計語言,由于函數內部的變量狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。
??函數式編程的一個特點就是,允許把函數本身作為參數傳入另一個函數,還允許返回一個函數!Python對函數式編程提供部分支持, 包括標準庫itertools、operator、functools,下面將會一一介紹。由于Python允許使用變量,因此,Python不是純函數式編程語言。本文代碼均放在github 倉庫。
itertools
? 受APL,Haskell和SML的啟發,本模塊實現一系列 iterator ,用于快速創建高效率的迭代器。本模塊中包含的迭代器創建函數可分為以下三個部分:
無窮迭代器
無窮迭代意味著可以迭代無窮次而不會拋出StopIteration異常,在程序中需要設置一定條件主動停止迭代以避免無限循環。
| count() | start, [step] | start, start+step, start+2*step, … | count(10) --> 10 11 12 13 14 ... |
| cycle() | p | p0, p1, … plast, p0, p1, … | cycle('ABCD') --> A B C D A B C D ... |
| repeat() | elem [,n] | elem, elem, elem, … 重復無限次或n次 | repeat(10, 3) --> 10 10 10 |
-
這些函數中最需要注意的是cycle(),它會創建一個迭代器,返回iterable中所有元素并保存一個副本,因此該函數可能需要相當大的輔助空間。其實現大致相當于:
def cycle(iterable):# cycle('ABCD') --> A B C D A B C D A B C D ...saved = []for element in iterable:yield elementsaved.append(element)while True:for element in saved:yield element -
repeat()函數比較常用方式是給map和zip提供常數:
>>> list(map(pow, range(10), repeat(2))) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
最短停止迭代器
最短停止迭代器,即Iterators terminating on the shortest input sequence,迭代器迭代次數取決于傳入的最短序列(下面的zip_longest()除外)。這一類迭代器函數一共有12個,下面我們挑選幾個具體舉例:
-
accumulate(iterable, func=None)函數接受一個可迭代對象以及一個callable對象為參數,返回序列累積(如數學累加、累乘)結果或則最大值最小值,總之只要func接受兩個參數進行允許的操作即可,其實現大致等同如下:
import operator def accumulate(iterable, func=operator.add):'Return running totals'# accumulate([1,2,3,4,5]) --> 1 3 6 10 15# accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120it = iter(iterable)try:total = next(it)except StopIteration:returnyield totalfor element in it:total = func(total, element)yield total默認實現一個累加的效果,我們也可以實現一個累乘的效果:
>>> import operator >>> list(accumulate([1, 2, 3, 4, 5], func=operator.mul)) [1, 2, 6, 24, 120] >>> list(accumulate([1, 2, 3, 4, 5], func=lambda x, y: x*y)) [1, 2, 6, 24, 120] -
dropwhile()、takewhile()、filterfalse()這三個函數比較相似,均是按一定條件迭代iterable中的元素,下面給出一個例子說明他們的區別:
# -----------------------dropwhile---------------------------------print(list(dropwhile(lambda x: x < 3, [1, 2, 3, 4, 5]))) # starting when pred fails# -----------------------filterfalse------------------------------print(list(filterfalse(lambda x: x < 3, [1, 2, 3, 4, 5]))) # get elements of seq where pred(elem) is false# -----------------------takewhile--------------------------------print(list(takewhile(lambda x: x < 3, [1, 2, 3, 4, 5]))) # seq[0], seq[1], until pred fails[1, 2, 4] [3, 4, 5] [3, 4, 5] -
groupby()是對iterable進行自定義分組的一個迭代器創建函數,其一般與sorted()函數配合使用,達到最佳的分組效果,如下面例子對水果進行分類,原諒我才用了比較奇怪的分組方式,我將英文單詞長度一樣的水果分為一組:
def key_func(x):return len(x)fruits = ['apple', 'banana', 'grape', 'pear', 'chestnut', 'orange'] fruits = sorted(fruits, key=key_func) # 可以注釋這句話查看不一樣的效果 group = groupby(fruits, key=key_func)for k, v in group:print('{0}:{1}'.format(k, list(v)))4:['pear'] 5:['apple', 'grape'] 6:['banana', 'orange'] 8:['chestnut'] -
zip_longest()這是一個比較特殊的函數,與zip()函數相比,前者的迭代次數是取決于更長的輸入序列,并未短序列填充一個固定值:
for x, y in zip_longest([1, 1, 1, 1], [2, 2, 2], fillvalue=None): # 于zip對比print('(%s, %s)' % (x, y), end='\t')# output (1, 2) (1, 2) (1, 2) (1, None)
所有迭代器列表如下,其使用方法其實都是同一套路:
| accumulate() | p [,func] | p0, p0+p1, p0+p1+p2, … | accumulate([1,2,3,4,5]) --> 1 3 6 10 15 |
| chain() | p, q, … | p0, p1, … plast, q0, q1, … | chain('ABC', 'DEF') --> A B C D E F |
| chain.from_iterable() | iterable | p0, p1, … plast, q0, q1, … | chain.from_iterable(['ABC', 'DEF']) --> A B C D E F |
| compress() | data, selectors | (d[0] if s[0]), (d[1] if s[1]), … | compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F |
| dropwhile() | pred, seq | seq[n], seq[n+1], starting when pred fails | dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1 |
| filterfalse() | pred, seq | elements of seq where pred(elem) is false | filterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8 |
| groupby() | iterable[, key] | sub-iterators grouped by value of key(v) | |
| islice() | seq, [start,] stop [, step] | elements from seq[start:stop:step] | islice('ABCDEFG', 2, None) --> C D E F G |
| starmap() | func, seq | func(*seq[0]), func(*seq[1]), … | starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000 |
| takewhile() | pred, seq | seq[0], seq[1], until pred fails | takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4 |
| tee() | it, n | it1, it2, … itn splits one iterator into n | |
| zip_longest() | p, q, … | (p[0], q[0]), (p[1], q[1]), … | zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- |
排列組合迭代器
該類函數創建的迭代器和排列組合有關,如product()是進行笛卡爾積運算;permutations()即數學中的排列;combinations()即數學中的組合;combinations_with_replacement()即又放回的組合。
| product() | p, q, … [repeat=1] | cartesian product, equivalent to a nested for-loop |
| permutations() | p[, r] | r-length tuples, all possible orderings, no repeated elements |
| combinations() | p, r | r-length tuples, in sorted order, no repeated elements |
| combinations_with_replacement() | p, r | r-length tuples, in sorted order, with repeated elements |
下面給出幾個簡單的例子,其中需要說明的是perm和comb是scipy中實現排列組合的函數,返回排列組合的組合個數。
from itertools import product, permutations, combinations, combinations_with_replacement from scipy.special import comb, perm# -----------------------product----------------------------- print(list(product([1, 2, 3, 4], [1, 2], repeat=1))) # *iterables, 他們之間進行笛卡爾積# -----------------------permutations------------------------- per = list(permutations([1, 2, 3, 4], r=2)) # 有序 assert len(per) == perm(4, 2) # A^2_4, 排列組合問題 print(per)# -----------------------combinations-------------------------per = list(combinations([1, 2, 3, 4], r=2)) # 無放回抽樣 assert len(per) == comb(4, 2) # C^2_4 print(per)# -----------------------combinations-------------------------per = list(combinations_with_replacement([1, 2, 3, 4], r=2)) # 有放回抽樣 assert len(per) == comb(4, 2, repetition=True) # 10 print(per)?
? 雖然itertools模塊一下子提供了這么多cool的迭代器生成函數,但是更多靈活的應用還需要多思考、多查閱文檔。另外,在python中談到迭代器,不得不談到列表生成式、生成器、可迭代對象等,在這篇文章不準備細談。如有需要,請參考我之前的博客Python面向對象、魔法方法最后一個部分。另外,限于篇幅原因,更多例子請參考我放在github倉庫中的代碼,我對每一個迭代器都給出了詳細例子。
operator
基本運算符函數
? 上面也用到了operator模塊,該模塊提供了一套與Python的內置運算符對應的高效率函數,包括:對象的比較運算、邏輯運算、數學運算以及序列運算。該模塊一共定義了54個(python 3.7.2)運算符函數,可以通過operator.__all__查看,比較常用的一些如下列表所示:
| 加法 | a + b | add(a, b) |
| 字符串拼接 | seq1 + seq2 | concat(seq1, seq2) |
| 包含測試 | obj in seq | contains(seq, obj) |
| 除法 | a / b | truediv(a, b) |
| 除法 | a // b | floordiv(a, b) |
| 按位與 | a & b | and_(a, b) |
| 按位異或 | a ^ b | xor(a, b) |
| 按位取反 | ~ a | invert(a) |
| 按位或 | a | b | or_(a, b) |
| 取冪 | a ** b | pow(a, b) |
| 一致 | a is b | is_(a, b) |
| 一致 | a is not b | is_not(a, b) |
| 索引賦值 | obj[k] = v | setitem(obj, k, v) |
| 索引刪除 | del obj[k] | delitem(obj, k) |
| 索引取值 | obj[k] | getitem(obj, k) |
| 左移 | a << b | lshift(a, b) |
| 取模 | a % b | mod(a, b) |
| 乘法 | a * b | mul(a, b) |
| 矩陣乘法 | a @ b | matmul(a, b) |
| 否定(算術) | - a | neg(a) |
| 否定(邏輯) | not a | not_(a) |
| 正數 | + a | pos(a) |
| 右移 | a >> b | rshift(a, b) |
| 切片賦值 | seq[i:j] = values | setitem(seq, slice(i, j), values) |
| 切片刪除 | del seq[i:j] | delitem(seq, slice(i, j)) |
| 切片取值 | seq[i:j] | getitem(seq, slice(i, j)) |
| 字符串格式化 | s % obj | mod(s, obj) |
| 減法 | a - b | sub(a, b) |
| 真值測試 | obj | truth(obj) |
| 比較 | a < b | lt(a, b) |
| 比較 | a <= b | le(a, b) |
| 相等 | a == b | eq(a, b) |
| 不等 | a != b | ne(a, b) |
| 比較 | a >= b | ge(a, b) |
| 比較 | a > b | gt(a, b) |
這里重點提一下setitem()、delitem()、getitem()三個函數,能方便的對序列進行操作,并且其效果是in-place的,下面會繼續講到in-place,請注意關注!
>>> from operator import setitem, delitem, getitem >>> list_a = [1,2,3,4,5,6] >>> getitem(list_a, 0) Out[4]: 1 >>> getitem(list_a, slice(1, 5)) Out[7]: [2, 3, 4, 5]>>> delitem(list_a, slice(1, 2)) >>> list_a Out[9]: [1, 3, 4, 5, 6]>>> setitem(list_a, 1, 2) >>> list_a Out[11]: [1, 2, 4, 5, 6]看到這里可能你會想,So, 有什么用呢? 我為什么不直接用運算符呢?operator庫提供的函數一來是方便將運算符作為函數快捷的傳入,二是效率一般也更高:
import operator from itertools import accumulate from time import timestart = time() list(accumulate(range(100000000), func=operator.mul)) print('operator:', time()-start) start = time() list(accumulate(range(100000000), func=lambda x, y: x*y)) print('lambda', time()-start)# Output operator: 6.269657135009766 lambda 9.311992883682251operator模塊中運算符函數部分還提供了In-place版本,當調用inplace版本時,計算和賦值會分開進行。對于不可變對象附錄1,如字符串、數字、元祖,值更新操作會進行,但是賦值操作會略過:
>>> a = 'hello' >>> iadd(a, ' world') 'hello world' >>> a 'hello'而對于可變對象附錄1,如列表、字典,inplace method會依次進行計算、賦值操作:
>>> a = [1,2,3]>>> add(a, [4,5,6]) [1, 2, 3, 4, 5, 6] >>> a [1, 2, 3]>>> iadd(a, [4,5,6]) [1, 2, 3, 4, 5, 6] >>> a [1, 2, 3, 4, 5, 6]屬性查詢
operator塊還定義了用于通用屬性查找的接口。
-
attrgetter(*attrs)
attrgetter函數常用于map、sorted等接受一個函數作為參數的函數,用于獲取對象的屬性,該函數返回一個函數g(obj)。其實現其實很簡單,就是利用getattr函數以及閉包附錄2的特性獲取對象的屬性,其實現等同如下代碼:
def attrgetter(*items):if any(not isinstance(item, str) for item in items):raise TypeError('attribute name must be a string')if len(items) == 1:attr = items[0]def g(obj):return resolve_attr(obj, attr)else:def g(obj):return tuple(resolve_attr(obj, attr) for attr in items)return gdef resolve_attr(obj, attr):for name in attr.split("."): obj = getattr(obj, name)return obj其效果是,如果指定f = attrgetter('name'),則f(obj)返回obj.name;如果f = attrgetter('name', 'age'),則f(obj)返回(obj.name, obj.age);如果f = attrgetter('name.first', 'name.last'),則f(b)返回(b.name.first, b.name.last)。舉個例子,對學生按年齡進行排序,將attrgetter作為sorted函數的key函數。
from operator import attrgetterclass Student:def __init__(self, name, grade, age):self.name = nameself.grade = gradeself.age = agedef __str__(self):return self.name__repr__ = __str__students = [Student('jane', 96, 12),Student('john', 95, 12),Student('dave', 98, 10), ]print(sorted(students, key=attrgetter('age'))) print(sorted(students, key=lambda o: o.age)) print(sorted(students, key=attrgetter('age', 'grade'), reverse=True)) -
itemgetter(*items)
itemgetter()函數的實現類似于attrgetter(),調用該函數返回一個函數,用于獲取序列數據。當然,這里說獲取序列數據也許并不嚴謹,應該說可以獲取任何實現了__getitem__魔法方法的類對象元素信息。其實現等同于:
def itemgetter(*items):if len(items) == 1:item = items[0]def g(obj):return obj[item]else:def g(obj):return tuple(obj[item] for item in items)return g比如可用于字符串截取:
>>> itemgetter(1)('ABCDEFG') 'B' >>> itemgetter(1,3,5)('ABCDEFG') ('B', 'D', 'F') >>> itemgetter(slice(2,None))('ABCDEFG') 'CDEFG' -
methodcaller(name[, args...])
? 毫無疑問,methodcaller()的實現方式也是類似的,用于配合接受一個函數作為參數的函數調用對象的方法,其內部實現等同于如下代碼:
def methodcaller(name, *args, **kwargs):def caller(obj):return getattr(obj, name)(*args, **kwargs)return caller? 如果指定f = methodcaller('name'), 則 f(b) 返回 b.name()函數的調用結果;如果指定f = methodcaller('name', 'foo', bar=1), 則 f(b) returns b.name('foo', bar=1)的調用結果。
functools
? functools模塊設計了一系列應用于高階函數的接口,可以通過functools.__all__查看所有可用函數,包括以下:
>>> import functools >>> functools.__all__ Out[3]: ['update_wrapper','wraps','WRAPPER_ASSIGNMENTS','WRAPPER_UPDATES','total_ordering','cmp_to_key','lru_cache','reduce','partial','partialmethod','singledispatch']? reduce被網友笑成為被放逐到了functools模塊,因為python語言創始人Guido不喜歡函數式編程,于是把reduce移出了builtin模塊,而map函數則在社區的強烈要求下得以在builtin立足。另外,lambda、filter也是python函數式編程中常用的builtin函數,這四個函數我在python函數詳解曾介紹過,這里不再贅述;我曾在python裝飾器詳細剖析中簡單剖析過update_wrapper、wraps、WRAPPER_ASSIGNMENTS、WRAPPER_UPDATES的源碼,這里也不再贅述。
partial & partialmethod
partial,俗稱偏函數,在python函數詳解中曾介紹過,其作用是為函數某些參數設置默認值,簡化函數signature,其實現大致等同于下面的代碼,由此我們可以partial object有三個屬性,分別是partial_obj.func、partial_obj.args、partial_obj.keywords,而并沒有自動創建其他一般函數擁有的屬性,如__name__、__doc__。
def partial(func, *args, **keywords):def newfunc(*fargs, **fkeywords):newkeywords = keywords.copy()newkeywords.update(fkeywords)return func(*args, *fargs, **newkeywords)newfunc.func = funcnewfunc.args = argsnewfunc.keywords = keywordsreturn newfunc比如,我們可以使用偏函數創建一個以2為base的int函數basetwo:
>>> from functools import partial >>> basetwo = partial(int, base=2) >>> basetwo.__doc__ = 'Convert base 2 string to an int.' >>> basetwo('10010') 18partialmethod類(python3.7.2)是用描述器實現的,(關于描述器請參考我之前的博客python描述器深度解析),該描述器的行為類似于partial,只是它被設計為用作方法定義而不是可直接調用的。
舉例:
>>> class Cell(object): ... def __init__(self): ... self._alive = False ... @property ... def alive(self): ... return self._alive ... def set_state(self, state): ... self._alive = bool(state) ... set_alive = partialmethod(set_state, True) ... set_dead = partialmethod(set_state, False) ... >>> c = Cell() >>> c.alive False >>> c.set_alive() >>> c.alive Truecmp_to_key
? 將comparison function(比較函數)轉化為 key function。python中的key function包括sorted(), min(), max(), heapq.nlargest(), itertools.groupby()等等,它們的共同點是至少接受一個序列和一個key function,key function為序列元素生成一個可用作排序的值;而comparison function接受兩個參數,并比較這兩個參數,根據他們的大小關系返回負值、零或正值。
sorted(iterable, key=cmp_to_key(locale.strcoll))@total_ordering
? 在Python面向對象、魔法方法曾說到python有一類對象比較的魔法方法,實現這些方法可以對對象進行比較大小。包括__gt__(),__lt__(),__eq__(),__ne__(),__ge__()。根據數學推理,只要我定義了__gt__(),__lt__(),__ne__(),__ge__()中的一個以及定義了__eq__(),其比較大小的所有規則應該是確定的,可是我還是要一一實現剩余三個方法,好累啊。在python3.2新增的total_ordering裝飾器則是用來簡化該問題,如下官方例子:
@total_ordering class Student:def _is_valid_operand(self, other):return (hasattr(other, "lastname") andhasattr(other, "firstname"))def __eq__(self, other):if not self._is_valid_operand(other):return NotImplementedreturn ((self.lastname.lower(), self.firstname.lower()) ==(other.lastname.lower(), other.firstname.lower()))def __lt__(self, other):if not self._is_valid_operand(other):return NotImplementedreturn ((self.lastname.lower(), self.firstname.lower()) <(other.lastname.lower(), other.firstname.lower()))官方文檔特別提示,這可能帶來執行效率的低下,如果性能分析發現這會是瓶頸時,不妨自己手動實現吧,哈哈!
@lru_cache
? 學過操作系統的同學對LRU應該不陌生,就是其中一種經典的頁面置換算法,另外還有FIFO、LFU等等。 該函數主要是用來做緩存,把相對耗時的函數結果進行保存,避免傳入相同的參數重復計算。同時,緩存并不會無限增長,不用的緩存會被釋放。
import functools@functools.lru_cache(maxsize=20, typed=False) def add(x, y):print(f'[Log]-invoke function now, with params x={x}, y={y}')return x + yprint(add(5, 6)) print(add(4, 7)) print(add(5, 6)) print(add(5, 6.0))[Log]-invoke function now, with params x=5, y=6 11 [Log]-invoke function now, with params x=4, y=7 11 11 [Log]-invoke function now, with params x=5, y=6.0 11.0? 上面的例子可以看到,兩次調用add(5, 6)實際只發生了一次調用,因為結果已經緩存,無需再次計算。但是add(5, 6.0)卻再次調用了,因為我們指定了參數typed=False。也可以看看官方文檔給的斐波那契函數的高效實現,常規遞歸方式會導致很多重復計算,效率低下,緩存已計算可大大提高地柜效率。
@lru_cache(maxsize=None) def fib(n):if n < 2:return nreturn fib(n-1) + fib(n-2)>>> [fib(n) for n in range(16)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]>>> fib.cache_info() CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)@singledispatch
? 這是PEP443新提出的一個單參數泛型函數裝飾器,只作用于函數的第一個參數,舉例:
>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)? 要將重載的實現添加到函數中,可以使用泛型函數的register()屬性。它是一個裝飾。對于帶有類型注釋的函數,裝飾器將自動推斷第一個參數的類型:
>>> @fun.register ... def _(arg: int, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> @fun.register ... def _(arg: list, verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)? 對于不使用類型注釋的代碼,可以將類型參數顯式傳遞給裝飾器本身:
>>> @fun.register(complex) ... def _(arg, verbose=False): ... if verbose: ... print("Better than complicated.", end=" ") ... print(arg.real, arg.imag) ...? 為了能夠register lambda匿名函數和預先存在的函數,可以采用如下方式定義:
>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)? 調用時,泛型函數根據第一個參數的類型分派到不同的函數,如果傳入了一個沒有注冊的類型,則調用被@singledispatch裝飾的那個函數:
>>> fun("Hello, world.") Hello, world. >>> fun("test.", verbose=True) Let me just say, test. >>> fun(42, verbose=True) Strength in numbers, eh? 42 >>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True) Enumerate this: 0 spam 1 spam 2 eggs 3 spam >>> fun(None) Nothing. >>> fun(1.23) 0.615? 可以通過下面的方式check,給定類型會調用哪個函數:
>>> fun.dispatch(float) <function fun_num at 0x1035a2840> >>> fun.dispatch(dict) # note: default implementation <function fun at 0x103fe0000>? 可以通過只讀屬性registry查看已經實現的函數:
>>> fun.registry.keys() dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,<class 'decimal.Decimal'>, <class 'list'>,<class 'float'>]) >>> fun.registry[float] <function fun_num at 0x1035a2840> >>> fun.registry[object] <function fun at 0x103fe0000>附錄
可變對象 & 不可變對象
? 一般在函數傳參的時候我們才需要考慮一個對象是可變(mutable)還是不可變(immutable)對象,因為這往往會決定,我們的函數有沒有副作用(side effects)。我們先來舉個例子,該例中lin和a指向同一個object,因為參數傳入參數a是一個可變對象,因此在函數中對它的修改是in-place的,即a和lin同時被修改了(但是要理解到,實際只發生了一次修改,因為lin和a指向同一個object)。
# 當參數是可變對象時 >>> def list_add3(lin):lin += [3]return lin>>> a = [1, 2, 3] >>> b = list_add3(a) >>> b [1, 2, 3, 3] >>> a [1, 2, 3, 3]? 但是,在函數中修改不可變對象時,是在內存中開辟一片新空間保存修改后的結果,即a指向原來的object,tin則指向修改后的object。
>>> def tuple_add3(tin):tin += (3,)return tin>>> a = (1, 2, 3) >>> b = tuple_add3(a) >>> b (1, 2, 3, 3) >>> a (1, 2, 3)? 在python中呢,不可變對象包括integers, floats, complex, strings, bytes, tuples, ranges 和 frozensets;可變對象包括lists, dictionaries, bytearrays 和 sets。
閉包
? 在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。
? 我們知道,python函數是第一類對象,它可以作為參數值傳遞給函數,也可以作為函數的返回值返回。在上面attrgetter()函數的實現中g就是一個閉包,它包含了外層函數的變量attr或items。
def attrgetter(*items): # enclosing functionif any(not isinstance(item, str) for item in items):raise TypeError('attribute name must be a string')if len(items) == 1:attr = items[0]def g(obj): # nested function or enclosed functionreturn resolve_attr(obj, attr)else:def g(obj):return tuple(resolve_attr(obj, attr) for attr in items)return g>>> f = attrgetter('name') >>> f.__closure__ Out[19]: (<cell at 0x11a131bb8: str object at 0x10d201538>,) >>> f.__closure__[0] Out[20]: <cell at 0x11a131bb8: str object at 0x10d201538> >>> f.__closure__[0].cell_contents Out[21]: 'name'關于python函數和第一類對象更多的理解,可以參考我之前的博客python函數詳解和python meataclass詳解;關于閉包更通俗的講解可以參考python closures
reference
- Functional Programming Modules
- Functional Programming HOWTO
- Best Practices for Using Functional Programming in Python
- 廖雪峰函數式
- python函數詳解
總結
以上是生活随笔為你收集整理的python函数式编程之functools、itertools、operator详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python hashlib 哈希算法
- 下一篇: Python collection模块