Functools-Pythonの高階関数の力

ソースノード: 1865357

この記事は、の一部として公開されました データサイエンスブログソン

概要

Python 標準ライブラリには、コードをよりクリーンかつシンプルに保つのに役立つ多くの優れたモジュールが含まれています。 間違いなくそのうちのXNUMXつ

キャッシング

モジュール functools の最も単純でありながら強力な関数のいくつかから始めましょう。 キャッシュ関数 (およびデコレーター) から始めましょう – lru_cache,cache & cached_property。 そのうちの最初のものは – lru_cache 関数の実行の最後の結果のキャッシュを提供します。つまり、関数の作業の結果を記憶します。

from functools import lru_cache
import requests @lru_cache(maxsize=32)
def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://reddit.com/", "https://google.com/", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
print(get_with_cache.cache_parameters())
# {'maxsize': 32, 'typed': False}

この例では、デコレータを使用して GET リクエストを作成し、その結果 (最大 32 個の結果) をキャッシュします。 @lru_cache。 キャッシュが実際に機能するかどうかを確認するには、次のメソッドを使用して関数キャッシュ情報を確認できます。 cache_infoこれは、キャッシュのヒット数とヒット数を示します。 デコレータはメソッドも提供します clear_cache& cache_parametersキャッシュされた結果とテストパラメータをそれぞれキャンセルします。

より詳細なキャッシュが必要な場合は、オプションの引数 typed=true を含めることができます。これにより、さまざまな種類の引数を個別にキャッシュできるようになります。

functools でキャッシュするためのもう XNUMX つのデコレーターは、単純に呼ばれる関数です。 cache。 シンプルなラッピングです lru_cacheこれは引数 max_size を省略して値を減らしますが、古い値は削除されません。

キャッシュに使用できるもう XNUMX つのデコレータは次のとおりです。 cached_property。 名前が示すように、クラス属性の結果をキャッシュするために使用されます。 このメカニズムは、計算にコストがかかるプロパティがあるが、それは変わらない場合に非常に役立ちます。

from functools import cached_property class Page: @cached_property def render(self, value): # Do something with supplied value... # Long computation that renders HTML page... return html

この簡単な例はそれを示しています。 キャッシュされたプロパティは、たとえば、ユーザーに何度も表示する必要があるレンダリングされた HTML ページをキャッシュするために使用できます。 特定のデータベース クエリや長い数学計算でも同じことができます。

もう一つの美しさ cached_propertyそれは検索時にのみ実行されるため、属性の値を変更できるということです。 属性を変更した後、以前にキャッシュされた値は変更されず、代わりに新しい値が計算されてキャッシュされます。 キャッシュをクリアすることもできます。必要なのは属性を削除することだけです。

このセクションの終わりに、上記のすべてのデコレータに関する注意を述べておきます。関数に副作用がある場合、または呼び出されるたびに変更可能なオブジェクトを作成する場合は、これらのデコレータを使用しないでください。これらは明らかにキャッシュしたい関数ではないためです。 。

比較と注文

おそらく、Python で次のような比較演算子を実装できることはすでにご存じでしょう。 <, >=or ==、と lt, gtor eq。 ただし、それぞれのことを理解すると非常にイライラすることがあります。 eq, lt, le, gtor ge。 幸いなことに、functools にはデコレータがあります @total_orderingこれはこれに役立ちます。実装する必要があるのは eq残りのメソッドの XNUMX つと残りのデコレータは自動的に生成されます。

from functools import total_ordering @total_ordering
class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value Number(3))
# True
print(Number(1) = Number(15))
# True
print(Number(10) <= Number(2))
# False

このようにして、eq と手動の lt しかないにもかかわらず、すべての拡張比較演算を実装できます。 最も明白な利点は、これらの追加の魔法のメソッドをすべて記述する必要がないという利便性ですが、おそらくコードの量を減らし、コードの読みやすさを向上させることの方が重要です。

過負荷

おそらく誰もが、Python にはオーバーロードがないことを教えられてきたと思いますが、実際には、functools の XNUMX つの関数、つまり単一ディスパッチおよび/または単一ディスパッチ メソッドを使用してオーバーロードを実装する簡単な方法があります。 これらの関数は、Python などの動的に型指定されたプログラミング言語が実行時に型を区別できるようにする、いわゆるマルチ ディスパッチ アルゴリズムの実装に役立ちます。

一部

私たちは皆、さまざまな外部ライブラリやフレームワークを使用していますが、その多くは、非同期操作やイベントのリッスンなど、コールバックを渡す必要がある関数やインターフェイスを提供しています。 これは新しいことではありませんが、コールバックとともにいくつかの引数も渡す必要がある場合はどうなるでしょうか。 ここで便利な関数ツールが役立ちます.partial. に使える partial簡素化された関数シグネチャを持つ新しいオブジェクトを作成することにより、関数の引数の一部 (またはすべて) を固定します。 混乱した? いくつかの実際的な例を見てみましょう。

def output_result(result, log=None): if log is not None: log.debug(f"Result is: {result}") def concat(a, b): return a + b import logging
from multiprocessing import Pool
from functools import partial logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("default") p = Pool()
p.apply_async(concat, ("Hello ", "World"), callback=partial(output_result, log=logger))
p.close()
p.join()

上記のコードはその使用方法を示しています partial関数を渡すには ( output_result) と引数 ( log=logger) をコールバックとして使用します。 この場合、関数の結果を非同期的に計算する multiprocessing.apply_async を使用します ( concat) コールバックの結果を返します。 しかし、 apply_async常に結果を最初の引数として渡します。log=logger の場合のように、追加の引数を含めたい場合は、partial を使用する必要があります。

かなり高度な使用例を検討しましたが、より簡単な例としては、stdout ではなく stderr に書き込む関数の通常の作成が挙げられます。

import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")

この簡単なトリックを使用して、常に渡す新しい呼び出し可能な関数を作成しました。 file=sys.stderr名前付き引数として出力します。これにより、コードが簡素化され、名前付き引数の値を毎回指定する必要がなくなります。

最後に良い例を一つ。 使用できます partialあまり知られていない機能と組み合わせて iter呼び出し可能なオブジェクトを渡してイテレータを作成し、 sentinelin iterこれは次のように適用できます。

from functools import partial RECORD_SIZE = 64 # Read binary file...
with open("file.data", "rb") as file: records = iter(partial(file.read, RECORD_SIZE), b'') for r in records: # Do something with the record...

通常、ファイルを読み取るときは、複数の行を反復処理する必要がありますが、バイナリ・データの場合は、固定サイズのレコードを反復処理する必要がある場合があります。 これを行うには、次を使用して呼び出し可能なオブジェクトを作成します。 partial指定されたデータのチャンクを読み取り、それを渡します iterイテレータを作成します。 このイテレータは、ファイルの終わりに到達するまで読み取り関数を呼び出し、常に指定されたチャンク サイズのみを取得します ( RECORD_SIZE)。 最後に、ファイルの終わりに到達すると、値は sentinel(b」) が返され、反復が停止します。

デコレータ

前のセクションでいくつかのデコレータについて説明しましたが、さらに多くのデコレータを作成するデコレータについては説明しませんでした。 そのようなデコレータの XNUMX つが functool です。.wraps。 なぜそれが必要なのかを理解するために、例を見てみましょう。

def decorator(func): def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") greet("Martin")
# Before Calling greet
# Hello, Martin!
# After Calling greet

この例では、単純なデコレータを実装する方法を示します。 特定のタスクを実行する関数をラップします ( actual_func) を外部デコレータと結合すると、それ自体がデコレータとなり、たとえば次のような他の関数に適用できます。 greet。 関数を呼び出すと、 greet 両方のメッセージが出力されることがわかります。 actual_funcそしてそれ自体で。 大丈夫そうじゃないですか? しかし、これを実行するとどうなるでしょうか:

print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work

装飾された関数の名前とドキュメントを呼び出すと、それらがデコレーター関数の値に置き換えられていることがわかります。 デコレータを使用する場合、すべての関数名とドキュメントを書き換えることはできないため、これは問題です。 この問題はどうすれば解決できますか? もちろん、関数ツールを使用して.wraps:

from functools import wraps def decorator(func): @wraps(func) def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") print(greet.__name__)
# greet
print(greet.__doc__)
# Says hello to somebody

関数の唯一の目的 wraps上書きを防ぐために、名前、ドキュメント、引数リストなどをコピーします。 それを考えると wrapsこれはデコレータでもあり、actual_func に追加するだけで問題は解決します。

減らす

モジュール functools の最後にあるのは、このreduceです。 おそらく他の言語では次のように知られているかもしれません。 fold(ハスケル)。 この関数は反復可能を受け取り、そのすべての値を XNUMX つに折り畳みます (加算します)。 これには、次のような多くのアプリケーションがあります。

from functools import reduce
import operator def product(iterable): return reduce(operator.mul, iterable, 1) def factorial(n): return reduce(operator.mul, range(1, n)) def sum(numbers): # Use `sum` function from standard library instead return reduce(operator.add, numbers, 1) def reverse(iterable): return reduce(lambda x, y: y+x, iterable) print(product([1, 2, 3]))
# 6
print(factorial(5))
# 24
print(sum([2, 6, 8, 3]))
# 20
print(reverse("hello"))
# olleh

コードからわかるように、 reduce コードを XNUMX 行に簡素化または圧縮できます。そうでなければ、コードは非常に長くなります。 そうは言っても、コードを縮小してコードを「賢く」するためだけにこの関数を乱用するのは、通常は悪い考えです。コードはすぐに怖くて読めなくなるからです。 このため、私の意見では、使用は慎重に行うべきです。

そして、それを覚えていれば、 reduce 多くの場合、すべてを XNUMX 行に短縮できます。以下と完全に組み合わせることができます。 partial:

product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6

そして最後に、最終的な「折りたたまれた」結果以上のものが必要な場合は、次のように使用できます。 accumulate– 別の素晴らしいモジュールから itertools。 最大値を計算するには、次のように使用できます。

from itertools import accumulate data = [3, 4, 1, 3, 5, 6, 9, 0, 1] print(list(accumulate(data, max)))
# [3, 4, 4, 4, 5, 6, 9, 9, 9]

まとめ

functools を見てもわかるように、作業を楽にしてくれる便利な関数やデコレータがたくさんありますが、これは氷山の一角にすぎません。 最初に述べたように、Python 標準ライブラリには、より優れたコードを作成するのに役立つ関数が多数あります。そのため、ここで説明した関数に加えて、次のような他のモジュールにも注目してください。 operatoror itertool. ご質問がございましたら、私のコメント ボックスを押してください。 私はあなたの入力を解決するために最善を尽くし、あなたが望む出力を提供できることを願っています。 LinkedIn から私に連絡することもできます:-

https://www.linkedin.com/in/shivani-sharma-aba6141b6/

この記事に示されているメディアは、Analytics Vidhyaが所有しておらず、作成者の裁量で使用されています。

出典: https://www.analyticsvidhya.com/blog/2021/08/functools-the-power-of-higher-order-functions-in-python/

タイムスタンプ:

より多くの 分析Vidhya