Pythonの高度なソースコード分析:クラスメソッドを複数のメソッドに変換する方法
前の記事「Pythonでパラメーター化されたテストを実装する方法」"で、Pythonでパラメーター化されたテストを実装するいくつかのライブラリについて言及し、質問を残しました。
どのようにしてメソッドを複数のメソッドに変換し、各メソッドを対応するパラメーターにバインドしますか?
もう一度蒸留してみましょう。元の質問は次のとおりです。クラスでは、デコレータを使用してクラスメソッドを複数のクラスメソッドに変換する(または同様の効果を生み出す)方法を教えてください。
# 带有一个方法的测试类
class TestClass:
def test_func(self):
pass
# 使用装饰器,生成多个类方法
class TestClass:
def test_func1(self):
pass
def test_func2(self):
pass
def test_func3(self):
pass
Pythonのデコレータの本質は、デコレータを別のメソッドに移動し、デコレートされたメソッドを新しいメソッドに置き換えることです。パラメータ化を実装する過程で、私たちが導入したいくつかのライブラリが使用した/秘密兵器はどのような意味ですか?
1. ddtはどのようにパラメーター化を実現しますか?
最初に、前の記事のddtライブラリの書き込み方法を確認します。
import unittest
from ddt import ddt,data,unpack
@ddt
class MyTest(unittest.TestCase):
@data((3, 1), (-1, 0), (1.2, 1.0))
@unpack
def test(self, first, second):
pass
ddtは4つのデコレータを提供できます。1つは@ddtをクラスに追加し、3つは@ data、@ unpack、@ file_dataをクラスメソッドに追加します(上記には言及していません)。
最初に、クラスメソッドに追加された3つのデコレータの役割を確認します。
# ddt 版本(win):1.2.1
def data(*values):
global index_len
index_len = len(str(len(values)))
return idata(values)
def idata(iterable):
def wrapper(func):
setattr(func, DATA_ATTR, iterable)
return func
return wrapper
def unpack(func):
setattr(func, UNPACK_ATTR, True)
return func
def file_data(value):
def wrapper(func):
setattr(func, FILE_ATTR, value)
return func
return wrapper
それらの共通の役割は、クラスメソッドのsetattr()に属性を追加することです。これらの属性が使用されるのはいつですか?クラスに追加された@ddtデコレータのソースコードを見てみましょう。
forループの最初の層は、すべてのクラスメソッドをトラバースし、次にDATA_ATTR / FILE_ATTRに対応するif / elifの2つのブランチ、つまり対応するパラメーターの2つのソース:データ(@data)とファイル(@file_data)をトラバースします。
elifブランチにはファイルを解析するロジックがあり、それはデータ処理に似ているため、主に前のifブランチを調べてスキップします。この部分のロジックは非常に明確で、完了した主なタスクは次のとおりです。
- トラバーサルクラスメソッドのパラメーターキーと値のペア
- 元のメソッドとパラメーターのペアに基づいて新しいメソッド名を作成します
- 元のメソッドのドキュメント文字列を取得します
- タプルとリスト型のパラメーターをアンパックする
- テストクラスに新しいテストメソッドを追加し、パラメーターとドキュメント文字列をバインドします。
ソースコードを分析すると、@ data、@ unpack、@ file_dataの3つのデコレーターが主に属性を設定し、パラメーターを渡すことがわかります。@ ddtデコレーターはコア処理ロジックです。
(クラスとクラスメソッドに個別に追加された)デコレータを配布し、それらを組み合わせるこのスキームは、洗練されていません。一緒に使用できないのはなぜですか?後で、その隠された意味を分析します。最初に表を押して、他の実装がどのようなものかを確認しますか?
2.パラメータ化を実現するには?
最初に、前の記事のパラメーター化されたライブラリーの記述を確認します。
import unittest
from parameterized import parameterized
class MyTest(unittest.TestCase):
@parameterized.expand([(3,1), (-1,0), (1.5,1.0)])
def test_values(self, first, second):
self.assertTrue(first > second)
デコレータークラス@parameterizedを提供します。ソースコードは次のとおり(バージョン0.7.1)、主に初期検証とパラメーター分析を行うためのものであり、注意の焦点ではありません。スキップしてください。
ドキュメントのコメントに書かれている、このデコレータクラスのexpand()メソッドに主に焦点を当てています。
テストケースをパラメーター化する「力ずく」の方法。新しいテストケースを作成し、ラップされた関数が定義されている名前空間にそれらを挿入します。Noseテストジェネレーターが機能しない「UnitTest」のサブクラスでテストをパラメーター化するのに役立ちます。
2つの主要なアクションは、「新しいテストケースを作成する」と「名前空間に挿入する」です。
最初の点に関しては、ネーミングスタイルのいくつかの違い、およびあまり注意に値しないパラメーターの解析とバインディングの違いを除いて、それはddtに似ています。
最も異なるのは、新しいテスト方法を効果的にする方法です。
パラメータ化は、「注入」アプローチを使用します。
inspect
強力な標準ライブラリで、プログラムの呼び出しスタックに関する情報を取得するためにここで使用されます。コードの最初の3行の目的は、f_localsを取り出すことです。これは、「このフレームから見えるローカル名前空間」を意味します。f_localsは、クラスのローカル名前空間を指します。
ローカル名前空間について言えば、ローカル()について考えるかもしれませんが、以前の記事で「ローカル()とグローバル()の読み取りと書き込みの問題」について述べましたが、ローカル()は読み取りと書き込みができないため、この段落コードはf_localsを使用します。
3. pytestはどのようにパラメーター化を実現しますか?
最初に、規則に従って前の記事の記述を見てください。
import pytest
@pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)])
def test_values(first, second):
assert(first > second)
最初に「マーク」を参照してください。paratestrize、timeout、skipif、xfail、tryfirst、trylastなどの一部のタグはpytestに組み込まれており、ユーザー定義のタグもサポートしています。実行条件の設定、フィルターの実行のグループ化、元のテスト動作の変更が可能です。待って。
使用方法も非常に簡単ですが、ソースコードははるかに複雑になる可能性があります。ここではパラメータ化のみに焦点を当てています。最初にコードのコア部分を見てください。
着信パラメーターのペアに従って、元のテストメソッドの呼び出し情報をコピーし、呼び出されるリストに格納します。前に分析した2つのライブラリとは異なり、ここでは新しいテストメソッドを作成せず、既存のメソッドを再利用します。parametrize()が属するMetafuncクラスを検索すると、_callsリストが使用されている場所を追跡できます。
最後に、Functionクラスで実行されます。
興味深いのは、ここに数行の神のノートが表示されていることです...
pytestのソースコードを(表面的に)読み取ると、本当に問題が発生します...ただし、パラメーター化されている場合は、ジェネレーターのスキームを使用し、パラメーターをたどるとテストメソッドが1回呼び出されることがおおまかにわかります。以前のddtとパラメーター化では、すべてのパラメーターが一度に解析され、n個の新しいテストメソッドが生成されてから、スケジューリングのためにテストフレームワークに渡されます。
比較すると、最初の2つのライブラリのアイデアは非常に明確であり、それらのデザインは純粋にパラメーター化のためのものなので、pytestとは異なり、タグがなく、抽象的なデザインが多すぎるため、読みやすく理解しやすくなっています。最初の2つのライブラリは、Pythonの動的機能を利用して、クラス属性を設定するか、ローカル名前空間を注入します。pytestは、静的言語からアイデアを借りる不器用な方法のようです。
4.最後の要約
タイトルの質問に戻る「メソッドを複数のメソッドに変更するにはどうすればよいですか?」パラメータ化されたテストに加えて、他のどのようなシナリオがこの魅力を持つのでしょうか。議論するメッセージを残してようこそ。
この記事では、3つのテストライブラリのデコレータの実装を分析します。ソースコードを読むと、それぞれに独自の利点があることがわかります。この発見自体は非常に興味深いものです。デコレータを使用する場合、表面上はそれほど違いはありませんが、実際のカンフーの詳細はその下に隠されています。
ソースコード分析の重要性は、その理由を探ることです。この探求の旅で読者は何を得ることができますか?一緒に話しましょう!(追記: "Python cat"パブリックアカウントのバックグラウンドで "learning group"を送信して、グループのパスワードを取得してください。)