как автоматически обернуть функции из определенного файла

Общеизвестно, что есть много способов получить имя функции, используя стандартную библиотеку 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
--------------------------------------------------------------------------------

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

Просто для протокола: я не прошу никакого профилировщика. Я хотел бы знать, возможно ли это сделать и как.


person BPL    schedule 14.09.2016    source источник


Ответы (2)


Решением может быть использование 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')
person Nicolas Heimann    schedule 14.09.2016
comment
Спасибо за ответ! Я добавил окончательное решение. В любом случае я подтвердил ваш ответ, потому что вы были первыми и подали мне идею ;-). Кроме того, я не считаю справедливым самоголосование. - person BPL; 14.09.2016

Вот решение, которое я искал:

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)
person BPL    schedule 14.09.2016