Functools-Python中高階函數的力量

這是我參與8月更文挑戰的第10天,活動詳情查看:8月更文挑戰html

Python標準庫包括許多強大的模塊,可讓你的代碼既乾淨又高效,今天咱們介紹的是functools模塊,這個模塊提供了許多有用的高階函數,咱們能夠利用這些高階函數來實現緩存功能、重載、建立裝飾器等,會然咱們的代碼更加高效,好了,讓咱們來看一下,functools模塊都給咱們提供了什麼呢。python

緩存

functools模塊中提供了一個簡單卻又強大的緩存函數(裝飾器)lru_cache()lru_cache()能夠直接以語法糖@的形式做用於咱們的函數至上,爲咱們的函數提供緩存的功能,遵循最近最少使用的原則,將函數執行結果緩存,方便下次函數執行前查詢,若是函數的參數比較固定,邏輯比較固定,那麼將很是必要,能夠有效的減小函數的執行時間。緩存

import functools
import time


@functools.lru_cache(maxsize=32)
def add(a, b):
    print("sleep 2s")
    time.sleep(2)
    return a+b


print("第1次調用:", add(4, 5))  # 第一次調用,無緩存,執行原函數,結果加入緩存
print("第2次調用:", add(4, 5))  # 第二次調用,參數與第一次相同,匹配到緩存,直接返回緩存,不執行原函數
print("第3次調用:", add(5, 5))  # 第三次調用,參數與以前不一樣,無緩存,執行原函數,結果加入緩存
print(add.cache_info())  # 檢查函數的緩存信息,它顯示緩存命中和未命中的數量
複製代碼

執行結果爲:markdown

sleep 2s
第1次調用: 9
第2次調用: 9
sleep 2s
第3次調用: 10
CacheInfo(hits=1, misses=2, maxsize=32, currsize=2)

Process finished with exit code 0
複製代碼

在這個例子中,咱們使用@lru_cache裝飾器處理add()函數並緩存執行的結果(能夠緩存32個結果)。爲了查看緩存是否真的有效,咱們使用了cache_info()方法檢查函數的緩存信息,它顯示緩存命中和未命中的數量,經過結果咱們能夠看出,在第一次調用的時候並沒有緩存,add()函數執行結果存入緩存,第二次的時候命中緩存,第三次的時候沒有命中緩存。app

若是你想要更細粒度的緩存,那麼你也能夠包含可選的typed=true參數,這使得不一樣類型的參數被分別緩存。 例如,add(4.0, 5.0)add(4, 5)將被視爲不一樣的調用。函數

其實在Python3.8的版本以後,還有另外一個能夠用於緩存的裝飾器叫作cached_property,這個函數用於緩存類屬性的結果,若是你的類的屬性是而又不可變的,可使用這個。oop

比較

您可能已經知道,可使用__lt__()__gt__()__eq__()在Python中實現比較操做符,如<>==。可是實現__lt__()__gt__()__eq__()__le__()__ge__()多是至關繁瑣的,恰好functools模塊包含total_ordering'裝飾器,它能夠幫助咱們作到這一點。
咱們只須要實現__eq__(),和__lt__()__gt__()__le__()__ge__()中的任一個方法,其餘的它會幫咱們自動提供:post

from functools import total_ordering


@total_ordering
class MyNumber:
    def __init__(self, value):
        self.value = value

    def __gt__(self, other):
        return self.value > other.value

    def __eq__(self, other):
        return self.value == other.value

print(MyNumber(5) > MyNumber(3))
# True
print(MyNumber(1) < MyNumber(5))
# True
print(MyNumber(5) >= MyNumber(5))
# True
print(MyNumber(5) <= MyNumber(2))
# False
複製代碼

很明顯,它能夠幫助咱們減小代碼和提升可讀性。ui

partial函數(偏函數)

這裏的偏函數和數學意義上的偏函數不是一個概念,functools模塊中的partial函數的做用是,把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會比調用原來的函數更簡單。理解起來是否是有些困惑,讓咱們來看一些實際的例子。 在Python中,int()函數能夠把指定數字字符串轉換爲整數,默認是作十進制的轉換:url

print(int(1234))
# 1234
複製代碼

int()函數還提供了一個base參數,默認值爲10。若是傳入base參數,就能夠作N進制的轉換,即,默認數字字符串的進制是N進制,要轉化爲10進制,例如:

print(int("1234", base=8))
# 默認1234位8進制的數字,轉化爲10進制:668
print(int("1234", base=16))
# 默認1234位16進制的數字,轉化爲10進制:4660
複製代碼

假設要轉換大量的8進制字符串,每次都傳入int(x, base=8)顯得過於麻煩,因而,咱們想到,能夠定義一個int8()的函數,默認把base=8傳進去:

def int8(x, base=8):
    return int(x, base)

print(int8("1234"))
複製代碼

這樣,就很是方便了。

其實functools.partial的功能就是可以建立一個偏函數,不須要咱們本身定義int8(),能夠直接使用下面的代碼建立一個新的函數int8

int2 = functools.partial(int, base=8)
print(int2('1234'))
複製代碼

因此簡單來講,functools.partial的做用就是,把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,使得咱們調用這個新函數會更簡單。

重載

咱們都知道嚴格意義上來講,在Python中函數重載是不可能實現的,但實際上有一個簡單的方法來實現和函數重載類似的功能,那就是使用functools模塊中singledispatch函數裝飾器。下面來看個例子:

@singledispatch
def show(obj):
    print(obj, type(obj), "obj")


@show.register(str)
def _(text):
    print(text, type(text), "str")


@show.register(int)
def _(n):
    print(n, type(n), "int")

    
show(1234)
show("abcd")
show({"abcd": 1234})
複製代碼

結果爲:

1234 <class 'int'> int
abcd <class 'str'> str
{'abcd': 1234} <class 'dict'> obj

Process finished with exit code 0
複製代碼

可見爲show()函數傳遞不一樣的類型參數,就調用不一樣的函數,表現不一樣的行爲,實際上singledispatch實現的是單泛函數,給函數加上.register方法,該方法支持綁定一個變量類型和一個函數。而後它返回一個被重載了的函數。當輸入值的類型是經過.register綁定的類型時,就調用同時綁定的函數。

wraps

這一部分在我以前的文章有提到,感性趣的能夠看一下:內置裝飾器@functools.wrap

總結

functools模塊提供了許多有用的功能和裝飾器,能夠幫助咱們構建更好的代碼,我這裏提到的只是部份內容。感興趣的小夥伴能夠查看官方文檔functools — Higher-order functions and operations on callable objects

最後,感謝女友在工做和生活中的包容、理解與支持 !

相關文章
相關標籤/搜索