拦截函数调用的Pythonic方式是什么?

4

为了测试查询环境的事物(例如os.getenvsys.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,如果允许具有多个参数的函数,则语法会变得有些笨拙。我想在astcode/exec/eval之间,我可以将要覆盖的函数作为参数扩展它,但不够简洁。此外,我将走上格林斯潘第十条路。有更好的方法吗?

2个回答

4

您可以轻松地将os.getenv本身作为第一个参数传递,然后在上下文管理器中分析它,比使用astcode等更简单:

>>> 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__):
   ...

当然,如果你希望传递特定的异常而不是将其转移到原始函数,或者快捷地处理一些常见情况,提供一个虚假可调用对象会很繁琐等等,可能需要更多的复杂性。但是这样一个通用的伪造器没有理由比你特定的伪造器复杂。


所以这就是Python时间机器。没错,我脑海中缺失的部分是如何从函数本身而不是其字符串名称(例如'os.getenv')中提取模块和名称。谢谢! - Wang
当时我并没有理解这一点,但重新导入模块实际上非常重要,不是吗?比如说,如果你只是在 locals()/globals() 中查找它,那么当模块以不同的名称导入(例如 import csv as bob)时,它就会出错。 - Wang
@Wang,original.__module__不会受到as子句的影响——试试看! - Alex Martelli
没错,这就是我的意思。original.__module__ 总是使用标准名称,而 sys.modules 总是将标准名称映射到模块。但是 locals()/globals() 可能会在 as 子句名称下列出它。 - Wang
@Wang,没错——sys.modules就是你的好朋友,就是在这个意义上。 - Alex Martelli

1

为什么不编写自己的(伪造的)sysos等模块?

import fakeSys as sys

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接