Pytestでfixtureを定義するとき、デコレータfixture
は括弧と引数無しでも、(scope="session")
といった括弧と引数がある使い方でもどちらでもサポートしています。次のコードはPytestのドキュメントにある両方の使い方の例です。
引用元: Pytestのドキュメント
括弧と引数無しの使い方
@pytest.fixture
def smtp():
return smtplib.SMTP("smtp.gmail.com")
括弧と引数有りの使い方
@pytest.fixture(scope="module")
def smtp():
return smtplib.SMTP("smtp.gmail.com")
どうやってこのようなデコレータを記述しているのか、調べてみました。
正確に記すと、前者の括弧と引数無しの用法でのfixture
はデコレータと呼び、後者の括弧と引数有りの用法ではfixture
はデコレータを作成するデコメーカー(decomaker
)というものです。(PEP 318 -- Decorators for Functions and Methods)
Pytestのfixtureのソースでは、fixtureは次のように実装されていました。
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
""" (return a) decorator to mark a fixture factory function.
This decorator ....(略)
"""
if callable(scope) and params is None and autouse == False:
# direct decoration
return FixtureFunctionMarker(
"function", params, autouse, name=name)(scope)
if params is not None and not isinstance(params, (list, tuple)):
params = list(params)
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
括弧と引数無しのデコレータとしてfixture
が用いられた場合、fixture
の引数scope
には、デコレータに続く関数定義で定義された関数オブジェクト(上記の例ではsmtp
)が渡されます。
if callable(...
の行で、scope
がcallable
であるかどうか(と同時に後に続く引数がデフォルト値であるか)を調べ、そうであった場合、デコメーカーFixtureFunctionMaker
のインスタンスを作成し、さらにその()
にscope
を渡しています。
一方、括弧と引数有りでfixtureが使用された場合、デコレータ(デコメーカーのインスタンス)自体を返しています。Pythonは、返されたデコレータにsmtp
を渡して返り値を名前smtp
にアサインします。
最初引数scope
を全く別の目的に使用している点が気持ち悪いですが、一つの関数に集約できてユーザーにとっては使いやすいデコレータのインターフェースができます。