関数型プログラミングに関する別の記事ではありません

ここ数年、関数型プログラミングの人気が高まっています。 これは、もちろん、人々が古い言語とOOPを放棄し、Haskell、LispまたはErlangに大規模に切り替えることを意味するものではありません。 いや 機能的パラダイムは、マルチパラダイム言語の抜け穴を介してコードを貫通し、前述の言語は、直接使用されるよりもこの攻撃のフラグとして機能することが多くあります。

私は同じ流れを続け、記事の第2部ではライブラリにPythonで機能的なトリックをいくつか追加する方法を紹介しましたが、ライブラリの焦点は関数型プログラミングではなく実用性にあることに気付きました。 これに集中し、ファンシーユーティリティのいくつかの例を紹介します。

funcyの開発は、データや、あまり一般的ではない関数を操作するための多数のユーティリティをまとめる試みから始まったため、私の例のほとんどはそれに焦点を合わせます。 いくつかの(または多くの)例は些細なように見えるかもしれませんが、そのような単純な関数がどれだけ時間を節約でき、コードをどれだけ表現力豊かにできるかは驚くべきことです。

私は、Pythonの練習で遭遇するいくつかの典型的なタスクを調べ、それらの単純さにもかかわらず、絶え間ない質問を提起します。 行きましょう。

簡単なデータ操作


1.リストのリストを結合します。 伝統的に、私はこのようにしました:

from operator import concat reduce(concat, list_of_lists) #  : sum(list_of_lists, []) #  : from itertools import chain list(chain.from_iterable(list_of_lists)) 

それらはすべて悪くありませんが、追加のジェスチャー:インポートと追加の呼び出し、または制限を課す必要があります:リストを含むリストとタプルを含むタプルのみを組み合わせることができます。これは、どのタイプが来るかを事前に知る必要があるためです。 面白いことに、これは次のように行われます。

 from funcy import cat cat(list_of_lists) 

cat()は、リスト、タプル、反復子、および実際に反復されたすべてのリストを1つのリストに結合します。 関数呼び出しの結果リストを結合する必要がある場合、次のようにmapcat()使用できます。

 from funcy import mapcat mapcat(str.splitlines, bunch_of_texts) 

テキスト内のすべての行を1つのフラットリストに並べ替えます。 icat()imapcat()両方の関数の遅延バージョンがあります。

2.複数の辞書を追加します。 Pythonで辞書を組み合わせるには、いくつかの厄介な方法があります。

 d1.update(d2) #  d1 dict(d1, **d2) #   > 2  d = d1.copy() d.update(d2) 

なぜ折りたたむことができないのかといつも疑問に思っていました。 しかし、我々は持っているものを持っています。 いずれにせよ、ファンシーでこれは簡単に行われます:

 from funcy import merge, join merge(d1, d2) merge(d1, d2, d3) join(sequence_of_dicts) 

しかし、 merge()join()は、辞書だけでなく、ほとんどすべてのコレクション(辞書、順序付き辞書、セット、リスト、タプル、反復子、さらには文字列)でも機能します。

3.正規表現を使用して部分文字列をキャプチャします。 通常、これは次のように行われます。

 m = re.search(some_re, s) if m: actual_match = m.group() #  m.group(i),  m.groups() ... 

ファンシーでは、これは次のようになります。

 from funcy import re_find actual_match = re_find(some_re, s) 

これで十分な印象が得られない場合は、次をご覧ください。

 from funcy import re_finder, re_all, partial, mapcat #      map(re_finder('\d+'), words) #  ini  (re_finder()      > 1 ) dict(imap(re_finder('(\w+)=(\w+)'), ini.splitlines())) #     (    )      mapcat(partial(re_all, r'\d+'), bunch_of_strings) 

輸入と実用性に関する余談


お気づきかもしれませんが、サブパッケージを使用せずに、関数から関数を直接インポートします。 私がそのようなインターフェースに決めた理由は実用性です。 ライブラリのすべてのユーザーにfuncy.collsまたはfuncy.seqsからwalk()をインポートする場所を覚えておくように要求するのはかなり退屈です。さらに、各ファイルの先頭に複数行のインポートがあり、私がいなければスタッフがいます。

このソリューションのもう1つの利点は、次のことを簡単に記述できることです。

 from funcy import * 

また、機能の魅力と便利さをすべてお楽しみください。ファイルの先頭に戻って、さらに多くのことをする必要はありません。 さて、あなたはすべての良い嘘がどこにあるか知っているので、私はもはやファンシーからの輸入を明示的に示すことはありません。 続けましょう。

より機能的なもの


re_finder()およびpartial() - re_finder()関数の使用例がすでにいくつかあります。 re_finder()関数自体がmap()などでの使いやすさのために作成された部分的なre_find()アプリケーションであることを追加する価値があります。 そして当然、 filter()を使用すると、 re_tester()を使用すると便利です。

 #      is_private = re_tester('^_') filter(is_private, dir(some_obj)) 

素晴らしい、 is_private()などのいくつかの述語を設定し、それらによってオブジェクトの属性をフィルターできます。

 is_special = re_tester('^__.+__$') is_const = re_tester('^[A-Z_]+$') filter(...) 

しかし、述語の組み合わせを含むパブリック属性またはプライベート定数のリストを取得したい場合はどうでしょうか? 簡単:

 is_public = complement(is_private) is_private_const = all_fn(is_private, is_const) either_const_or_public = any_fn(is_const, is_public) 

便宜上、 filter()を補完する関数もあります:

 remove(is_private, ...) #  ,  filter(is_public) 

誰もが機能的な欲求を抑えてくれたらいいのにと思いますので、今度は抽象的なものに移りましょう。

コレクションを操作する


ここで説明したより多くのシーケンスを処理するユーティリティに加えて、funcyはコレクションの処理にも役立ちます。 基本は、関数walk()およびselect() 。これらはmap()およびfilter()に似ていmap()が、処理中のコレクションのタイプを保持します。

 walk(inc, {1, 2, 3}) # -> {2, 3, 4} walk(inc, (1, 2, 3)) # -> (2, 3, 4) #        - swap = lambda (k, v): (v, k) walk(swap, {1: 10, 2: 20}) # -> {10: 1, 20: 2} select(even, {1, 2, 3, 10, 20}) # -> {2, 10, 20} select(lambda (k, v): k == v, {1: 1, 2: 3}) # -> {1: 1} 

この関数のペアは、辞書を操作するためのセットによってサポートされています: walk_keys(), walk_values(), select_keys(), select_values()

 #       select_keys(is_public, instance.__dict__) #      select_values(bool, some_dict) 

このシリーズの最後の例では、いくつかの新しい関数を一度に使用しますsilent() -ラップされた関数によってスローされるすべての例外を抑制し、 None返します。 compact() -コレクションからNone値を削除します。 walk_values() -渡された辞書の値をバイパスし、渡された関数によって変換された値で新しい辞書を構築します。 一般に、次の行はクエリパラメーターから整数パラメーターのディクショナリを選択します。

 compact(walk_values(silent(int), request_dict)) 

データ操作


ああ! 最も興味深い部分にたどり着きました。 正直に言うと、私は上記のことをしました。 次に、分割してグループ化します。

 #   URL   absolute, relative = split(re_tester(r'^http://'), urls) #     group_by(lambda post: post.category, posts) 

ネストされた構造にフラットデータを収集します。

 #       dict(partition(2, flat_list_of_pairs)) #     {id: (name, password) for id, name, password in partition(3, users)} # ,     assert all(prev + 1 == next for prev, next in partition(2, 1, versions)): #    for chunk in chunks(CHUNK_SIZE, lots_of_data): process(chunk) 

さらに、ヒープへのいくつかの例:

 #     for line, prev in with_prev(text.splitlines()): if not prev: print ' ', print line #     1611  where(plays, author="Shakespeare", year=1611) # => [{"title": "Cymbeline", "author": "Shakespeare", "year": 1611}, # {"title": "The Tempest", "author": "Shakespeare", "year": 1611}] 


単なる図書館ではない


おそらく、ClojureやUnderscore.jsの使い慣れた関数に出会った人もいるでしょう(ちなみに、シェークスピアの例は後者のドキュメントから露骨に切り取られています)。 同時に、ライブラリの一貫性を維持し、どこでも実用性を犠牲にしないために、Pythonスタイルに従うことを試みました。したがって、すべての機能がプロトタイプに完全に対応するわけではなく、むしろお互いと標準ライブラリに対応します。

そしてもう一つ考えました。 私たちはプログラミング言語の言語を呼び出すことに慣れていますが、構文構造と標準関数がこれらの言語の言葉であることはめったにわかりません。 関数を定義することで独自の単語を追加できますが、通常、そのような単語はあまりにも具体的であるため、日常の言語辞書には収まりません。 対照的に、funcyのユーティリティは、幅広いアプリケーションに合わせて調整されているため、このライブラリは、アンダースコアまたはjQuery(JavaScript拡張)と同様に、Python拡張として解釈できます。 ですから、語彙を補充したい人は誰でも歓迎します。

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


All Articles