** kwargsでタイプミスを見つける

私が積極的に参加しているプロジェクトが成長するにつれて、右の写真のように、関数の引数の名前に同様のタイプミスが見られるようになりました。 デバッグで特に高価なのは、無効な基本クラスパラメータが長い継承チェーンで渡された場合、またはまったく渡されなかった場合のクラスコンストラクターでのこのようなエラーです。 ** kwargsの代わりにnamedtupleのような特別なユーザー構造へのインターフェイスを再描画すると、いくつかの問題がありました。





最終的に私が思いついた解決策は、考えられるすべてのケースを100%保護することはできませんが、必要な80%(私のプロジェクトでは100%)で完全に機能します。 つまり、ソース(バイト)関数コードを分析し、検出された「実際の」名前と外部から送信された距離との距離のマトリックスを構築し、指定された基準に従って警告を出力します。 ソース



TDD



そのため、最初にタスクを確実に設定します。 次の例は、5つの「疑わしい」警告を出力するはずです。



def foo(arg1, arg2=1, **kwargs): kwa1 = kwargs["foo"] kwa2 = kwargs.get("bar", 200) kwa3 = kwargs.get("baz") or 3000 return arg1 + arg2 + kwa1 + kwa2 + kwa3 res = foo(0, arg3=100, foo=10, fo=2, bard=3, bas=4, last=5)
      
      





  1. 代わりにarg2がarg3を渡しました
  2. barまたはbazの代わりにbasを渡しました
  3. 代わりに、barはbardを渡しました
  4. fooに加えてfoを渡しました
  5. 最後に一般的に不必要


同様に、クラスと継承の例では、同じ警告に加えてもう1つ(booが渡された沼地の代わりに)あるはずです。



 class Foo(object): def __init__(self, arg1, arg2=1, **kwargs): self.kwa0 = arg2 self.kwa1 = kwargs["foo"] self.kwa2 = kwargs.get("bar", 200) self.kwa3 = kwargs.get("baz") or 3000 class Bar(Foo): def __init__(self, arg1, arg2=1, **kwargs): super(Bar, self).__init__(arg1, arg2, **kwargs) self.kwa4 = kwargs.get("boo") bar = Bar(0, arg3=100, foo=10, fo=2, bard=3, bas=4, last=5, bog=6)
      
      





問題解決計画





get_kwarg_names(コードから名前を抽出)



コードのフラップがあるはずですが、私はむしろそれへのリンクを提供したいと思います。 関数は、 ...見つかった名前付き引数を多数返す必要がある入力関数受け取る入力関数を受け取ります。 私は特に解説ではないので、要点を簡単に説明します。

最初に行うことは、関数に** kwargsがまったくないかどうかを調べることです。 そうでない場合は、voidを返します。 次に、「二重星」の名前を明確にします。これは、** kwargsが一般に受け入れられている合意であり、それ以上のものではないためです。 さらに、バージョンポータブルコードでよくあるように、ロジックは分岐しますが、通常は2と3の分岐にではなく、<3.4 and> =に分岐します。 実際には、(完全なリファクタリングとともに)健全な分解サポートが3.4で登場しました。 その前に、奇妙なことに、サードパーティのモジュールがなければ、pythonoutバイトコードを標準出力(sic!)でしか印刷できませんでした。 dis.get_instructions()関数は、分析されたオブジェクトのすべてのバイトコード命令のインスタンスジェネレーターを返します。 一般に、私が理解しているように、バイトコードの唯一の信頼できる記述はオペコードリーダーです。もちろん、オペコードへの特定の命令の展開は実験的に決定する必要があったため、悲しいことです。

var = kwargs ["key"]とkwargs.get( "key" [、default])の2つのパターンを一致させます。



 >>> from dis import dis >>> def foo(**kwargs): return kwargs["key"] >>> dis(foo) 2 0 LOAD_FAST 0 (kwargs) 3 LOAD_CONST 1 ('key') 6 BINARY_SUBSCR 7 RETURN_VALUE >>> def foo(**kwargs): return kwargs.get("key", 0) >>> dis(foo) 2 0 LOAD_FAST 0 (kwargs) 3 LOAD_ATTR 0 (get) 6 LOAD_CONST 1 ('key') 9 LOAD_CONST 2 (0) 12 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 15 RETURN_VALUE
      
      





ご覧のとおり、最初の場合はLOAD_FAST + LOAD_CONSTの組み合わせであり、2番目の場合はLOAD_FAST + LOAD_ATTR + LOAD_CONSTです。 「kwargs」の代わりに、指示の引数で、先頭にある「二重星」の名前を探す必要があります。 バイトコードの詳細な説明を知識のある人に送って、物事を成し遂げる、つまり先に進みます。

そして、正規表現に関するPythonの古いバージョンのforい回避策があります。 inspect.getsourcelines()を使用して、関数のソース行のリストを取得し、プリコンパイルされた各レギュラーについて取得します。 この方法は、バイトコード分析よりもさらに劣ります。たとえば、複数の行で構成される式やセミコロンでつながれた複数の式は、現在の形式では定義されていません。 まあ、それが彼と回避策があまり緊張しないようにする理由です...しかし、この部分は客観的に改善することができます、私は要求を引き出したいです:)



check_misprints(距離行列)



コード 入力では、前のステージの結果、渡された名前付き引数、不可解な許容値、および警告する関数を取得します。 渡された引数ごとに、各「実際の」編集距離を見つける必要があります。 これはバイトコードの分析で発見されました。 実際、完全に一致するものがすでに見つかっている場合は、マトリックス全体を全体として愚かに考慮する必要はありません。続行することはできません。 まあ、そしてもちろん、行列は対称であり、したがって、半分しか計算できません。 それでも何らかの方法で最適化できると思いますが、典型的なkwargsの数が30未満であれば、n 2が機能します。 Damerau-Levenshteinの距離を、著者によく知られ、人気があり、理解しやすいものとして計算します :)ハブについて、彼らは彼について、たとえばここに書きまし 。 Python用にいくつかのパッケージが作成されました。Cythonの移植性と最適な線形メモリ消費量のために、PyxDamerauLevenshteinを選択しました。

さらに、技術的な問題:たとえリモートでも類似の標準さえも引数に見つからなかった場合、そのカテゴリの無益さを宣言します。 許容範囲よりも短い距離で複数の一致がある場合-あいまいな疑いを宣言します。



detect_misprints



Classicデコレータ 、名前付き引数の「実際の」名前を事前計算し(トートロジーについてはごめんなさい)、呼び出しごとにcheck_misprintsを取得します。



KeywordArgsMisprintsDetector



メタクラスは、クラスのタイプを作成する瞬間(__init__、そのすべての人生で一度「実際の」名前とその名前を計算します)とクラスインスタンスを作成する瞬間(__call __、check_misprintsを取得します)をインターセプトします。 唯一のポイントは、クラスにmroクラスと基本クラスがあり、そのコンストラクターで** kwargsも使用できることです。 したがって、__ init __- eでは、すべての基本クラスを実行し、各引数の名前を共通セットに追加する必要があります。



使い方



上記のデコレータを関数またはクラスのメタクラスに追加するだけです。

 @detect_misprints def foo(**kwargs): ... @six.add_metaclass(KeywordArgsMisprintsDetector) class Foo(object): def __init__(self, **kwargs): ...
      
      





まとめ



** kwargs名のタイプミスに対処する1つの方法を検討しましたが、私の場合、彼はすべての問題を解決し、すべての要件を満たしていました。 まず、関数のバイトコードまたは古いバージョンのPythonのソースコードのみを分析し、関数で使用される名前とユーザーから渡される名前の間の距離のマトリックスを作成しました。 距離はDamerau-Levenshteinに従って計算され、最終的に2つのエラーの場合に警告を書きました-引数が「完全に残っている」場合と「実際の」もののように見える場合。

この記事のソースコードはGitHubに投稿されています 。 私は修正と改善に満足しています。 また、この作成をPyPiにアップロードする必要があるかどうか、あなたの意見を知りたいです。



All Articles