「シンプルな」Pythonプログラミング


functools (これは、私にとって不必要なあらゆる種類のダンプです:-)。
-グイド・ファン・ロッサム

AFに関する記事のように思えるかもしれませんが、パラダイムについては説明しません。 コードの再利用と簡素化についてです-コードを書きすぎていることを証明しようとするので、テストが複雑で難しくなりますが、最も重要なのは、読み取りと変更に時間がかかることです。


この記事では、 ファンシーライブラリの例や概念を取り上げています。 第一に、それはクールで、第二に、すぐに使用を開始できます。 はい、FPが必要です。


FPについて簡単に説明します



AFには次の手法もあります。



これをすべて知っている場合は、例に直接進んでください。


純粋な機能


純粋な関数はパラメーターのみに依存し、結果のみを返します。 同じ引数で複数回呼び出される次の関数は、異なるオブジェクトになります(この場合は同じオブジェクトですが)。


労働力の値を持つ要素のリストを返すフィルター関数を作成します。


pred = bool result = [] def filter_bool(seq): for x in seq: if pred(x): result.append(x) return result 

きれいにしましょう:


 pred = bool def filter_bool(seq): result = [] for x in seq: if pred(x): result.append(x) return result 

これで、彼女のラードを1回連続で呼び出すことができ、結果は同じになります。


高階関数


これらは 、他の関数を引数として受け取るか、結果として別の関数を返す関数です。


 def my_filter(pred, seq): result = [] for x in seq: if pred(x): result.append(x) return result 

関数の名前を変更する必要がありました。これは、今でははるかに便利だからです。


 above_zero = my_filter(bool, seq) only_odd = my_filter(is_odd, seq) only_even = my_filter(is_even, seq) 

1つの関数が既に多くのことを行っていることに注意してください。 実際、彼女は怠け者である必要があります。


 def my_filter(pred, seq): for x in seq: if pred(x): yield x 

コードを削除したことに気づきましたか? これはほんの始まりに過ぎず、まもなく休日にのみ関数を作成します。 ここを見てください:


 my_filter = filter 

pythonのビルトイン機能は、一生涯ほぼ十分であり、それらを正しく構成する必要があります。


部分適用


これは、関数の引数の一部を修正するプロセスであり、アリティの低い別の関数を作成します。 私たちのものに翻訳されているのはfunctools.partialです。


 filter_bool = partial(filter, bool) filter_odd = partial(filter, is_odd) filter_even = partial(filter, is_even) 

これがすべてFPの基本であることは理解していますが、新しいものは何も書いていないことに注意してください。既製の関数を作成し、他の関数を作成しました。 新しい機能の基本は、非常に小さく、シンプルで、簡単にテストできる機能です。これらを安全に使用して、より複雑な機能を作成できます。


構成


Pythonには、そのようなシンプルでクールで必要なものはありません。 あなたはそれを自分で書くことができますが、私は正気の実装が欲しいです:(


 def compose(*fns): init, *rest = reversed(fns) return lambda *a, **kw: reduce(lambda a, b: b(a), rest, init(*a, **kw)) 

これであらゆることを実行できます(実行は右から左に進みます)。


 mapv = compose(list, map) filterv = compose(list, filter) 

これらは、Pythonの2番目のバージョンからのmapfilter以前のバージョンです。 遅延mapが必要な場合、 mapvを呼び出すことができます。 または、昔ながらの方法でもう少しコードを記述します。 毎回。


compose関数とpartial関数は、既製のテスト済み関数を再利用できるという点で優れています。 しかし、最も重要なことは、このアプローチの利点を理解していれば、時間の経過とともにすぐにそれらを作曲の準備ができるようになることです。


これは非常に重要なポイントです-関数は1つの簡単な問題を解決する必要があります:




タスク:シーケンスからNoneをドロップします。
昔ながらの決定(ほとんどの場合、関数として記述されていません):


 no_none = (x for x in seq if x is not None) 

注:式に含まれる変数名に関係なく。 あまり重要ではないので、ほとんどのプログラマーは気にしないように愚かにx書いています。 誰もがこの無意味なコードを何度も書きます。 各検閲時間: forinif 、および何度もxスコープを補正する必要があり、独自の構文があるため。 ループの各反復に対して変数に値を割り当てます。 そして、それが割り当てられ、条件がチェックされます。


このボイラープレートを作成し、このボイラープレートのテストを作成するたびに。 なんで?


書き直しましょう:


 from operator import is_ from itertools import filterfalse from functools import partial is_none = partial(is_, None) filter_none = partial(filterfalse, is_none) #  no_none = filter_none(seq) #  all_none = compose(all, partial(map, is_none)) 

それだけです 余分なコードはありません。 このコード( no_none = filter_none(seq) )は非常に単純なので、私はこれを読むことを楽しんでいます。 この関数が機能する方法は、プロジェクトで常に1回だけ読み取る必要があります。 補償は、その機能を正確に理解するために毎回読む必要があります。 まあ、または関数に入れて、違いはありませんが、テストを忘れないでください。


例2


かなり一般的なタスクは、辞書の配列からキー値を取得することです。


 names = (x['name'] for x in users) 

ちなみに、それは非常に迅速に動作しますが、再び不必要なゴミの束を書きました。 さらに速く動作するように書き直します。


 from operator import itemgetter def pluck(key, seq): return map(itemgetter(key), seq) #  names = pluck('name', users) 

そして、これをどのくらいの頻度で行いますか?


 get_names = partial(pluck, 'name') get_ages = partial(pluck, 'age') #  get_names_ages = partial(pluck, ('name', 'age')) users_by_age = compose(dict, get_names_ages) ages = users_by_ages(users) # {x['name']: x['age'] for x in users} 

そして、オブジェクトがあれば? Pf、これをパラメーター化:


 from operator import itemgetter, attrgetter def plucker(getter, key, seq): return map(getter(key), seq) pluck = partial(plucker, itemgetter) apluck = partial(plucker, attrgetter) #  names = pluck('name', users) # (x['name'] for x in users) object_names = apluck('name', users) # (x.name for x in users) #      object_data = apluck(('name', 'age', 'gender'), users) # ((x.name, x.age, x.gender) for x in users) 

例3


シンプルなジェネレーターを想像してください:


 def dumb_gen(seq): result = [] for x in seq: #  - c result.append(x) return result 

ボイラープレートはたくさんあります。空のリストを作成してから、ループを作成し、リストに要素を追加して、それを渡します。 私は文字通り関数の本体全体をリストしたようです:(


正しい解決策はfilter(pred, seq)またはmap(func, seq)を使用filter(pred, seq)ことですが、時にはもっと複雑なものを作成する必要があります。 ジェネレーターを作成する必要があります。 そして、結果がリストまたはタプラの形で常に必要な場合はどうなりますか? はい、簡単です:


 @post_processing(list) def dumb_gen(seq): for x in seq: ... yield x 

これはパラメトリックデコレータで、次のように機能します。


 result = post_processing(list)(dumb_gen)(seq) 

すなわち 最初の呼び出しの結果は、関数を引数として受け取り、別の関数を返す新しい関数になります。 それよりも難しい音:


 def post_processing(post): return lambda func: compose(post, func) 

既存のcomposeを使用したことに注意してください。 結果は誰も書いていない新しい機能です。
そして今、詩:


 post_list = post_processing(list) post_tuple = post_processing(tuple) post_set = post_processing(set) post_dict = post_processing(dict) join_comma = post_processing(', '.join) @post_list def dumb_gen(pred, seq): for x in seq: ... yield x 

1つの価格で多数の新機能! そして、定型文を削除すると、関数は小さくなり、ずっときれいになりました。


まとめ


鉄筋コンクリート機能(クリーン、高)を使用してデータを調べ、実装の容易さを維持し、テストの容易なプログラムの安定性を確保します。



ツールキットを作成するとすぐに、問題の一部を解決できるものがあることを知って、新しいコードが作成されます。 したがって、ソフトウェアはより小さくて簡単になります。


どこから始めますか?



クレジット


私の場合、AFの使用はclojureの知識から始まりました-このことは完全に脳を回します。YouTubeで少なくともvidos 見ることを強くお勧めします


Clojureは、通常のことなしに、簡単に書かなければならないように何らかの形で配置されています。変数なしで、お気に入りのスタイルの「小説」なしで、最初にヒーローの性格を明らかにし、次に彼の心の問題に取り組みます。 clojureでは、考えなければなりません%)基本的なデータ型と「構文の欠如」 (c)のみを持っています。 そして、この「単純な」概念は、Pythonに移植できることがわかりました。


UPD


読者は、私が継続的なFPで書いているという印象を持っているようです。 すべての人を安心させたい:私は、 すでに書いたコード書かれいる場所でのみ機能的なアプローチを使用します。 私の意見では、「働く」トリックを毎回繰り返すことは愚かで無意味なので、そのようなピースを関数に変換して再利用します。 実例はコメントにあります



Source: https://habr.com/ru/post/J346272/


All Articles