- 悪いユーザーエクスペリエンス。 特別に設計されたオブジェクトを関数に渡す必要があります。 オプションの引数をどうするかは明確ではありません。
- 複雑な開発。 クラスを継承する場合、対応する引数構造を継承する必要があります。 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)
- 代わりにarg2がarg3を渡しました
- barまたはbazの代わりにbasを渡しました
- 代わりに、barはbardを渡しました
- fooに加えてfoを渡しました
- 最後に一般的に不必要
同様に、クラスと継承の例では、同じ警告に加えてもう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)
問題解決計画
- 関数を使用した最初の例では、スマートデコレーターを作成し、2つ目のクラスでは、メタクラスを作成します。 内部の複雑なロジックをすべて共有する必要があり、本質的に違いはありません。 したがって、最初に内部マイクロAPIを作成し、その上に
ユーザースペースユーザーAPIを作成します。 デコレータはdetect_misprintsという名前で、メタクラスはKeywordArgsMisprintsDetector(重いJava / C#legacy、ええ)と呼ばれます。 - ソリューションのアイデアは、バイトコードの分析と距離行列の発見にありました。 これらは独立したステップであるため、マイクロAPIは対応する2つの関数で構成されます。 それらをget_kwarg_namesおよびcheck_misprintsと呼びました。
- コードを分析するには、標準のinspectとdisを使用して、線間の距離を計算します-pyxDamerauLevenshtein 。 プロジェクトの要件は、PyPyと同様に2つおよび3つとの互換性でした。 ご覧のとおり、
ラズベリーは依存関係を損なわないため、これらの要件と互換性があります。
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にアップロードする必要があるかどうか、あなたの意見を知りたいです。