Python functools 模組

Jason990420發表於2020-05-23

Python functools 模組

雖然《 Python語言參考》描述了Python語言的確切語法和語義,但該庫參考手冊描述了隨Python分發的標準庫。它還描述了Python發行版中通常包含的一些可選元件。Python的標準庫非常廣泛,可提供各種功能。該庫包含內建模組(用C編寫),這些模組提供對系統功能的訪問。

本文將提到有關 functools 模組的內容, 只提到個人認為比較好用的部份 .

@cached_property 轉換函式為屬性值 (python 3.8)

有些類的屬性值為常數, 我們可以一開始就設定, 但有些卻是隨時在變化, 如果我們隨時都要去更新, 在程式碼編寫上很不好處理, 因此, 可以在需要的時候, 以函式來取得結果. 更簡單的方式是呼叫屬性時會自動呼叫函式, 但是程式碼要如何來作到呢? 相信很多人都不清楚要怎麼作到.

下面這個範例, 可以看出來, 只要內容一有更動, 就必須呼叫 get_pets, get_legs 去更新 pets 及 legs 屬性, 相當麻煩.

class Animal():

    def __init__(self, cats=0, dogs=0, fishes=0):
        self.cats = cats
        self.dogs = dogs
        self.fishes = fishes
        self.get_pets()
        self.get_legs()

    def get_pets(self):
        self.pets = self.cats + self.dogs + self.fishes

    def get_legs(self):
        self.legs = (self.cats + self.dogs)*3

    def set_pets(self, animal, number):
        self.__setattr__(animal, number)
        self.get_pets()
        self.get_legs()

一個好的方式就是使用@cached_property,這樣函式就變成屬性,程式碼又變得簡單, 重要是隻有在用到時才會呼叫該函式。

from functools import cached_property

class Animal():

    def __init__(self, cats=0, dogs=0, fishes=0):
        self.cats = cats
        self.dogs = dogs
        self.fishes = fishes

    @cached_property
    def pets(self):
        return self.cats + self.dogs + self.fishes

    @cached_property
    def legs(self):
        return (self.cats + self.dogs)*4

    def set_pets(self, animal, number):
        self.__setattr__(animal, number)

@lru_cache(maxsize=128) 快取函式的呼叫結果

有時候我們常常會重複呼叫相同引數的函式, 如果每次都要重新處理一次, 速度上就會變得很慢; 因此這裡將呼叫過的函式結果存在快取中, 下次如果再次呼叫, 直接從快取中取出結果就可以了, 這樣會使得速度變得很快.

下面的範例是計算所有N!, N:0~1000的結果, 計算方式採用遞迴, 也就是N! = N * (N-1)!, f1 和f2 兩個函式內容完全一樣, 只不過f2採用了@lru_cache(maxsize=1200), f1 用了約0.2秒, f2 卻只用了約0.001秒, 速度差了200倍啊 !

from functools import lru_cache
from datetime import datetime
import sys

sys.setrecursionlimit(2000)

def f1(n):
    if n < 2:
        return 1
    return n*f1(n-1)

@lru_cache(maxsize=1200)
def f2(n):
    if n < 2:
        return 1
    return n*f2(n-1)

now = datetime.now()
for i in range(1001):
    factorial = f1(i)
print(datetime.now()-now)
# 0:00:00.202456

now = datetime.now()
for i in range(1001):
    factorial = f2(i)
print(datetime.now()-now)
# 0:00:00.000997

f2.cache_info()  # 快取訊息
f2.cache_clear() # 清除快取

partial(func, /, *args, **keywords) 定義一個新的子函式

通常我們建立一個新的函式, 可能帶有一些引數, 當我們常會用到該函式, 而某些引數值是固定的, 用起來程式碼還是那麼長, 而且不容易與原函式區隔; 如果另外要定義新的函式也挺麻煩的. 這裡可以提供一個簡單的方式來使用.

partial 中的 func 為你想簡化的函式, 後面給上指定的引數值, 返回一新的函式.

from functools import partial

int2 = partial(int, base=2)
int8 = partial(int, base=8)
int16 = partial(int, base=16)

int2('101')  # int('101', base= 2) => 5
int8('101')  # int('101', base= 8) => 65
int16('101') # int('101', base=16) => 257

partialmethod(func, /, args, *keywords) 建立一個類的新函式

這個方法類似 partial, 但用在類的函式定義

from functools import partialmethod

class Animal():
    def __init__(self, dogs=0, cats=0):
        self.dogs = dogs
        self.cats = cats

    def set(self, kind, number):
        self.__setattr__(kind, number)

    set_dogs = partialmethod(set, "dogs")
    set_cats = partialmethod(set, "cats")

reduce(function, iterable[, initializer]) 累積處理函式

這個函式本來是python 的內建函式, 後來移到內建模組functools 中, 基本演算法就是取前兩個元素運算, 其結果再與下一個元素再運算, 一直到沒有元素. 比如累加, 累乘, 等等…

from functools import reduce

reduce(lambda x, y:x+y, range(1, 11)) # 1+2+...+10
reduce(lambda x, y:x*y, range(1, 11)) # 1*2*...*10 (10!)

singledispatch 建立一個可修改的通用函式

建立一個可根據第一個引數類別, 分開不同函式處理的通用函式, 內容有點復新, 不好三言兩語就說清楚, 所以只提供以下範例.

from functools import singledispatch

@singledispatch
def print_value(arg):
    print(f'argument value is {arg}')

@print_value.register(int)
@print_value.register(float)
def _(arg:int):
    print(f'half value of {arg} is {arg/2}')

@print_value.register
def _(arg:complex):
    print(f'complex value is {arg}')

def nothing(arg):
    print('argument is None')
print_value.register(type(None), nothing)

>>> print_value(10)
half value of 10 is 5.0
>>> print_value(12.5)
half value of 12.5 is 6.25
>>> print_value(3-2j)
complex value is (3-2j)
>>> print("Hello")
Hello
>>> print_value("Hello")
argument value is Hello
>>> print_value(None)
argument is None

>>> print_value.dispatch(float) # 引數為 float 時所呼叫的函式
<function _ at 0x0000000001756700>

>>> print_value.registry # 可以字典的形式來使用
mappingproxy({<class 'complex'>: <function _ at 0x00000000017561F0>,
              <class 'float'>: <function _ at 0x0000000001756700>,
              <class 'int'>: <function _ at 0x0000000001756700>,
              <class 'NoneType'>: <function nothing at 0x0000000001756670>,
              <class 'object'>: <function print_value at 0x0000000001746B80>})
本作品採用《CC 協議》,轉載必須註明作者和本文連結

Jason Yang

相關文章