9

パート1

モックしたい一連のクラスがあるセットアップがあります。これを行いたい場合は、mockキーワード引数をコンストラクターに渡し、__new__これをインターセプトして、代わりにモックされたバージョンのそのオブジェクト。

次のようになります ( @mgilsons の提案後にキーワード検索を編集しました):

class RealObject(object):
    def __new__(cls, *args, **kwargs):
        if kwargs.pop('mock', None):
            return MockRealObject()
        return super(RealObect, cls).__new__(cls, *args, **kwargs)

    def __init__(self, whatever = None):
        '''
        Constructor
        '''
        #stuff happens

次に、コンストラクターを次のように呼び出します。

ro = RealObject(mock = bool)

ここでの問題は、次の場合に次のエラーが発生することboolですFalse

TypeError: __init__() got an unexpected keyword argument 'mock'

mockこれは、キーワード引数として追加すると機能します__init__が、これを回避できるかどうかを尋ねています。mockからをポップすることさえありkwargs dictます。

これデザインに関する質問です。これを行うより良い方法はありますか?(もちろん!)ファクトリーやスーパークラスなどを使わずに、この方法でやってみたかったのです。それでも、別のキーワードを使用する必要がありますか? __call__?

jsbuenoの回答に基づくパート2

__new__そこで、メタクラスと関数を別のモジュールに抽出したいと考えました。これは私がしました:

class Mockable(object):

    def __new__(cls, *args, **kwargs):

        if kwargs.pop('mock', None):
            mock_cls = eval('{0}{1}'.format('Mock',cls.__name__))
            return super(mock_cls, mock_cls).__new__(mock_cls)

        return super(cls, cls).__new__(cls,*args, **kwargs)


class MockableMetaclass(type):

    def __call__(self, *args, **kwargs):
        obj = self.__new__(self, *args, **kwargs)
        if "mock" in kwargs:
            del kwargs["mock"]

        obj.__init__(*args, **kwargs)

        return obj

そして、別のモジュールでクラスRealObjectMockRealObject. 私は今2つの問題を抱えています:

  1. MockableMetaclassとがクラスMockableと同じモジュールにない場合、 はif I provide を発生させます。RealObjectevalNameErrormock = True
  2. コードがmock = False無限の再帰に入り、印象的なRuntimeError: maximum recursion depth exceeded while calling a Python objec. RealObjectこれは、のスーパークラスobjectが ではなく になったためだと思いMockableます。

これらの問題を解決するにはどうすればよいですか? 私のアプローチは間違っていますか?代わりにMockableデコレーターとして持つべきですか?私はそれを試しましたが__new__、インスタンスが読み取り専用であるため、機能していないようです。

4

2 に答える 2

12

これはのための仕事です__metaclass__ :-)

Python の新しいスタイルのオブジェクトをインスタンス化するときに と の両方を呼び出すコードは、クラス メタクラスの__new__メソッドにあります。(またはそれと意味的に同等)。__init____call__

言い換えれば - あなたがするとき:

RealObject()- 実際に呼び出されるのはRealObject.__class__.__call__メソッドです。明示的なメタクラスを宣言しない場合、メタクラスはtypeであるため、type.__call__呼び出されるのは です。

メタクラスの処理に関するほとんどのレシピは、__new__メソッドのサブクラス化 (クラス作成時のアクションの自動化) を処理します。しかしオーバーライド__call__すると、代わりにクラスがインスタンス化されたときにアクションを実行できます。

この場合、必要なのは、「mock」キーワード パラメータがある場合は、呼び出す前に削除することだけです__init__

class MetaMock(type):
    def __call__(cls, *args, **kw):
       obj = cls.__new__(cls, *args, **kw)
       if "mock" in kw:
           del kw["mock"]
       obj.__init__(*args, **kw)
       return obj

class RealObject(object):
    __metaclass__ = MetaMock
    ...
于 2013-02-07T16:58:10.770 に答える
1

__new__メソッドへのコンストラクター呼び出しに常に引数を渡すため、サブクラスは非常に重要__init__です。クラス デコレータを介してサブクラスを mixin として追加すると、サブクラスのmock引数をインターセプトでき__init__ます。

def mock_with(mock_cls):
    class MockMixin(object):
        def __new__(cls, *args, **kwargs):
            if kwargs.pop('mock'):
                return mock_cls()
            return super(MockMixin, cls).__new__(cls, *args, **kwargs)
        def __init__(self, *args, **kwargs):
            kwargs.pop('mock')
            super(MockMixin, self).__init__(*args, **kwargs)
    def decorator(real_cls):
        return type(real_cls.__name__, (MockMixin, real_cls), {})
    return decorator

class MockRealObject(object):
    pass

@mock_with(MockRealObject)
class RealObject(object):
    def __init__(self, whatever=None):
        pass

r = RealObject(mock=False)
assert isinstance(r, RealObject)
m = RealObject(mock=True)
assert isinstance(m, MockRealObject)

別の方法は、サブクラス__new__メソッドが返すことRealObject(cls, *args, **kwargs)です。その場合、返されたオブジェクトはサブクラスのインスタンスではないためです。ただし、その場合、isinstanceチェックは失敗します。

于 2013-02-07T16:54:54.323 に答える