專欄地址:每週一個 Python 模組
functools 是 Python 中很簡單但也很重要的模組,主要是一些 Python 高階函式相關的函式。 該模組的內容並不多,看 官方文件 也就知道了。
說到高階函式,這是函數語言程式設計正規化中很重要的一個概念,簡單地說, 就是一個可以接受函式作為引數或者以函式作為返回值的函式,因為 Python 中函式是一類物件, 因此很容易支援這樣的函式式特性。
functools 模組中函式只有 cmp_to_key
、partial
、reduce
、total_ordering
、update_wrapper
、wraps
、lru_cache
這幾個:
被髮配邊疆的 reduce
這個 functools.reduce
就是 Python 2 內建庫中的 reduce
,它之所以出現在這裡就是因為 Guido 的獨裁,他並不喜歡函數語言程式設計中的“map-reduce”概念,因此打算將 map
和 reduce
兩個函式移出內建函式庫,最後在社群的強烈反對中將 map
函式保留在了內建庫中, 但是 Python 3 內建的 map
函式返回的是一個迭代器物件,而 Python 2 中會 eagerly 生成一個 list,使用時要多加註意。
該函式的作用是將一個序列歸納為一個輸出,其原型如下:
reduce(function, sequence, startValue)
複製程式碼
使用示例:
>>> def foo(x, y):
... return x + y
...
>>> l = range(1, 10)
>>> reduce(foo, l)
45
>>> reduce(foo, l, 10)
55
複製程式碼
偏函式 partial
和 partialmethod
用於建立一個偏函式,它用一些預設引數包裝一個可呼叫物件,返回結果是可呼叫物件,並且可以像原始物件一樣對待,這樣可以簡化函式呼叫。
一個簡單的使用示例:
from functools import partial
def add(x, y):
return x + y
add_y = partial(add, 3) # add_y 是一個新的函式
add_y(4) # 7
複製程式碼
一個很實用的例子:
def json_serial_fallback(obj):
"""
JSON serializer for objects not serializable by default json code
"""
if isinstance(obj, (datetime.datetime, datetime.date)):
return str(obj)
if isinstance(obj, bytes):
return obj.decode("utf-8")
raise TypeError ("%s is not JSON serializable" % obj)
json_dumps = partial(json.dumps, default=json_serial_fallback)
複製程式碼
可以在 json_serial_fallback
函式中新增型別判斷來指定如何 json 序列化一個 Python 物件
partialmethod
是 Python 3.4 中新引入的裝飾器,作用基本類似於 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 # True
複製程式碼
在 Python 2 中使用 partialmethod 可以這樣定義:
# Code from https://gist.github.com/carymrobbins/8940382
from functools import partial
class partialmethod(partial):
def __get__(self, instance, owner):
if instance is None:
return self
return partial(self.func, instance,
*(self.args or ()), **(self.keywords or {}))
複製程式碼
裝飾器相關
說到“接受函式為引數,以函式為返回值”,在 Python 中最常用的當屬裝飾器了。 functools 庫中裝飾器相關的函式是 update_wrapper
、wraps
,還搭配 WRAPPER_ASSIGNMENTS
和 WRAPPER_UPDATES
兩個常量使用,作用就是消除 Python 裝飾器的一些負面作用。
wraps
例:
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator
def add(x, y):
return x + y
add # <function __main__.wrapper>
複製程式碼
可以看到被裝飾的函式的名稱,也就是函式的 __name__
屬性變成了 wrapper
, 這就是裝飾器帶來的副作用,實際上add
函式整個變成了 decorator(add)
,而 wraps
裝飾器能消除這些副作用:
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator
def add(x, y):
return x + y
add # <function __main__.add>
複製程式碼
更正的屬性定義在 WRAPPER_ASSIGNMENTS
中:
>>> functools.WRAPPER_ASSIGNMENTS
('__module__', '__name__', '__doc__')
>>> functools.WRAPPER_UPDATES
('__dict__',)
複製程式碼
update_wrapper
update_wrapper
的作用與 wraps
類似,不過功能更加強大,換句話說,wraps
其實是 update_wrapper
的特殊化,實際上 wraps(wrapped)
相當於 partial(update_wrapper, wrapped=wrapped, **kwargs)
。
因此,上面的程式碼可以用 update_wrapper
重寫如下:
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return update_wrapper(wrapper, func)
複製程式碼
用於比較的 cmp_to_key
和 total_ordering
cmp_to_key
在 list.sort
和 內建函式 sorted
中都有一個 key 引數,這個引數用來指定取元素的什麼值進行比較,例如按字串元素的長度進行比較:
>>> x = ['hello','abc','iplaypython.com']
>>> x.sort(key=len)
>>> x
['abc', 'hello', 'iplaypython.com']
複製程式碼
也就是說排序時會先對每個元素呼叫 key 所指定的函式,然後再排序。同時,sorted
和 list.sort
還提供了 cmp 引數來指定如何比較兩個元素,但是在 Python 3 中該引數被去掉了。
cmp_to_key
是 Python 2.7 中新增的函式,用於將比較函式轉換為 key 函式, 這樣就可以應用在接受 key 函式為引數的函式中。比如 sorted()
、min()
、 max()
、 heapq.nlargest()
、 itertools.groupby()
等。
sorted(range(5), key=cmp_to_key(lambda x, y: y-x)) # [4, 3, 2, 1, 0]
複製程式碼
total_ordering
total_ordering
同樣是 Python 2.7 中新增函式,用於簡化比較函式的寫法。如果你已經定義了__eq__
方法,以及 __lt__
、__le__
、__gt__
或者 __ge__()
其中之一, 即可自動生成其它比較方法。官方示例:
@total_ordering
class Student:
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
dir(Student) # ['__doc__', '__eq__', '__ge__', '__gt__', '__le__', '__lt__', '__module__']
複製程式碼
再看一個示例:
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
print dir(Student)
stu = Student("Huoty", "Kong")
stu2 = Student("Huoty", "Kong")
stu3 = Student("Qing", "Lu")
print stu == stu2
print stu > stu3
複製程式碼
輸出結果:
['__doc__', '__eq__', '__ge__', '__gt__', '__init__', '__le__', '__lt__', '__module__']
True
False
複製程式碼
用於快取的lru_cache
這個裝飾器是在 Python3 中新加的,在 Python2 中如果想要使用可以安裝第三方庫 functools32
。該裝飾器用於快取函式的呼叫結果,對於需要多次呼叫的函式,而且每次呼叫引數都相同,則可以用該裝飾器快取呼叫結果,從而加快程式執行。示例:
from functools import lru_cache
@lru_cache(None)
def add(x, y):
print("calculating: %s + %s" % (x, y))
return x + y
print(add(1, 2))
print(add(1, 2)) # 直接返回快取資訊
print(add(2, 3))
複製程式碼
輸出結果:
calculating: 1 + 2
3
3
calculating: 2 + 3
5
複製程式碼
由於該裝飾器會將不同的呼叫結果快取在記憶體中,因此需要注意記憶體佔用問題,避免佔用過多記憶體,從而影響系統效能。
相關文件:
blog.windrunner.me/python/func…