Pythonic способ перехватить вызовы функции? - PullRequest
4 голосов
/ 01 августа 2010

Для тестирования вещей, которые запрашивают среду (например, os.getenv, sys.version и т. Д.), Зачастую удобнее делать ложные запросы, чем фактически подделывать среду. Вот менеджер контекста, который делает это для одного os.getenv вызова за раз:

from __future__ import with_statement
from contextlib import contextmanager
import os

@contextmanager
def fake_env(**fakes):
    '''fakes is a dict mapping variables to their values. In the
    fake_env context, os.getenv calls try to return out of the fakes
    dict whenever possible before querying the actual environment.
    '''

    global os
    original = os.getenv

    def dummy(var):
        try: return fakes[var]
        except KeyError: return original(var)

    os.getenv = dummy
    yield
    os.getenv = original

if __name__ == '__main__':

    print os.getenv('HOME') 
    with fake_env(HOME='here'):
        print os.getenv('HOME') 
    print os.getenv('HOME') 

Но это работает только для os.getenv, и синтаксис становится немного неуклюжим, если я разрешу функции с несколькими аргументами. Я предполагаю, что между ast и code / exec / eval я мог бы расширить его, чтобы использовать функцию для переопределения в качестве параметра, но не чисто. Кроме того, я бы тогда был на пути к десятому Гринспуна. Есть ли лучший способ?

Ответы [ 2 ]

4 голосов
/ 01 августа 2010

Вы можете легко просто передать os.getenv в качестве первого аргумента, а затем проанализировать его в менеджере контекста намного проще, чем ast, code и т. Д. И т. Д.

>>> os.getenv.__name__
'getenv'
>>> os.getenv.__module__
'os'

После этого для использования в достаточно общих целях вы можете получить возвращаемый объект результата или сопоставление аргументов (возможно, их кортежей) с результатами.Диспетчер контекста faker также может опционально принять вызываемый элемент, который будет использоваться для подделки.

Например, с максимальной простотой:

import sys

def faker(original, fakefun):

    original = os.getenv
    themod = sys.modules[original.__module__]
    thename = original.__name__

    def dummy(*a, **k):
        try: return fakefun(*a, **k)
        except BaseException: return original(*a, **k)

    setattr(themod, thename, dummy)
    yield
    setattr(themod, thename, original)

Ваш конкретный пример может стать:

with faker(os.getenv, dict(HOME='here').__getitem__):
   ...

Конечно, немного больше сложностей может быть оправдано, если, например, вы хотите распространять определенные исключения, а не выполнять указание на исходную функцию, или сокращать некоторые распространенные случаи, когда предоставление фальшивого вызываемого вызова затруднительно, и так далее.Но нет никакой причины, по которой такой универсальный мошенник должен быть намного более сложным, чем ваш конкретный.

1 голос
/ 01 августа 2010

Почему бы не написать свой (поддельный) sys, os и т. Д. модули?

import fakeSys as sys
...