如何自动包装某些文件中的函数

how to wrap automatically functions from certain file

众所周知,有很多方法可以使用 python 标准库获取函数名,这里有一个小例子:

import sys
import dis
import traceback


def get_name():
    stack = traceback.extract_stack()
    filename, codeline, funcName, text = stack[-2]
    return funcName


def foo1():
    print("Foo0 start")
    print("Inside-_getframe {0}".format(sys._getframe().f_code.co_name))
    print("Inside-traceback {0}".format(get_name()))
    print("Foo1 end")


def foo2():
    print("Foo2 start")
    print("Inside {0}".format(sys._getframe().f_code.co_name))
    print("Inside-traceback {0}".format(get_name()))
    print("Foo2 end")


def foo3():
    print("Foo3 start")
    print("Inside {0}".format(sys._getframe().f_code.co_name))
    print("Inside-traceback {0}".format(get_name()))
    print("Foo3 end")

for f in [foo1, foo2, foo3]:
    print("Outside: {0}".format(f.__name__))
    f()
    print('-' * 80)

您可以使用 traceback, sys._getframe, dis,也许还有更多选择...到目前为止一切顺利,python 进行这种自省真是太棒了。

现在,事情是这样的,我想知道如何自动包装函数(在文件级别)以打印其名称并在执行它们时测量执行时间。例如,像这样:

def foo1():
    print("Foo0 processing")


def foo2():
    print("Foo2 processing")


def foo3():
    print("Foo3 processing")

wrap_function_from_this_file()

for f in [foo1, foo2, foo3]:
    f()
    print('-' * 80)

会打印如下内容:

foo1 started
Foo1 processing
foo1 finished, elapsed time=1ms
--------------------------------------------------------------------------------
foo2 started
Foo2 processing
foo2 finished, elapsed time=2ms
--------------------------------------------------------------------------------
foo3 started
Foo3 processing
foo3 finished, elapsed time=3ms
--------------------------------------------------------------------------------

如您所见,我们的想法是不向文件的函数手动添加任何包装函数。 wrap_function_from_this_file 会自动反省执行的文件,它会修改一些包装它们的函数,在这种情况下,用一些打印其名称和执行时间的代码包装函数。

郑重声明,我不要求任何分析器。我想知道这是否可行以及如何实现。

一种解决方案是使用 globals() 获取有关当前定义的对象的信息。这是一个简单的包装函数,它将给定全局数据中的函数替换为它们的包装版本:

import types

def my_tiny_wrapper(glb):
    def wrp(f):
        # create a function which is not in 
        # local space of my_tiny_wrapper
        def _inner(*args, **kwargs):
            print('wrapped', f.__name__)
            return f(*args, **kwargs)
            print('end wrap', f.__name__)
        return _inner
    for f in [f for f in glb.values() if type(f) == types.FunctionType 
              and f.__name__ != 'my_tiny_wrapper']:
        print('WRAP FUNCTION', f.__name__) 
        glb[f.__name__] = wrp(f)

可以这样使用:

def peter(): pass
def pan(a): print('salat and onions')   
def g(a,b,c='A'): print(a,b,c)

# pass the current globals to the funcion
my_tiny_wrapper(globals())
g(4,b=2,c='D')  # test keyword arguments
peter()         # test no arguments
pan(4)          # single argument

生成以下结果:

~ % python derp.py
('WRAP FUNCTION', 'g')
('WRAP FUNCTION', 'pan')
('WRAP FUNCTION', 'peter')
('wrapped', 'g')
(4, 2, 'D')
('end wrap', 'g')
('wrapped', 'peter')
('end wrap', 'peter')
('wrapped', 'pan')
salat and onions
('end wrap', 'pan')

这是我一直在寻找的解决方案:

import inspect
import time
import random
import sys

random.seed(1)


def foo1():
    print("Foo0 processing")


def foo2():
    print("Foo2 processing")


def foo3():
    print("Foo3 processing")


def wrap_functions_from_this_file():
    def wrapper(f):
        def _inner(*args, **kwargs):
            start = time.time()
            print("{0} started".format(f.__name__))
            result = f(*args, **kwargs)
            time.sleep(random.random())
            end = time.time()
            print('{0} finished, elapsed time= {1:.4f}s'.format(
                f.__name__, end - start))

        return _inner

    for o in inspect.getmembers(sys.modules[__name__], inspect.isfunction):
        globals()[o[0]] = wrapper(o[1])


wrap_functions_from_this_file()
for f in [foo1, foo2, foo3]:
    f()
    print('-' * 80)