аспектно-ориентированные приемы в питоне? - PullRequest
4 голосов
/ 12 ноября 2009

Итак, у меня есть интересная проблема в Python, которая может быть решена с помощью аспектно-ориентированных методов. Вот ситуация:

  1. У меня есть несколько модулей, каждый из которых имеет несколько функций.
  2. У меня есть исполняемый файл, который вызывает некоторый набор функций в этих модулях.
  3. Когда исполняемый файл вызывает одну из этих функций, я хочу сгенерировать оператор журнала с подробностями вызова функции (имя и параметры)
  4. Когда один из модулей вызывает функцию одного из других модулей, я НЕ хочу, чтобы генерировался какой-либо оператор журнала.

Есть ли какой-нибудь удобный способ сделать это в Python, не вставляя операторы регистрации внутри каждой функции модуля?

Ответы [ 3 ]

5 голосов
/ 12 ноября 2009

Изменить поведение функций в исполняемом файле можно с помощью декоратора:

#!/usr/bin/env python
from module1 import foo
from module2 import bar

def trace(f):
    def tracewrapper(*arg, **kw):
        arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
        print "%s(%s)" % (f.__name__, arg_str)
        return f(*arg, **kw)
    return tracewrapper

verbose_functions=[foo,bar]  # add whatever functions you want logged here
for func in verbose_functions:
    globals()[func.func_name]=trace(func)

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

Если вы хотите регистрировать вызовы функций только тогда, когда они приходят непосредственно из main (), тогда вы могли бы использовать декоратор следов, как это:

import traceback
def trace(f,filename,funcname):
    def tracewrapper(*arg, **kw):
        stacks=traceback.extract_stack()
        (s_filename,s_lineno,s_funcname,s_text)=stacks[-2]
        # Alternatively, you can search the entire call stack
        # for (s_filename,s_lineno,s_funcname,s_text) in stacks:
        if s_filename.endswith(filename) and s_funcname==funcname: 
            arg_str=','.join(['%r'%a for a in arg]+
                             ['%s=%s'%(key,kw[key]) for key in kw])
            print "%s(%s)" % (f.__name__, arg_str)                
        return f(*arg, **kw)
    return tracewrapper
verbose_functions=[foo,bar]  # add whatever functions you want logged here
for func in verbose_functions:
    # You can pass the module's filename and the function name here
    globals()[func.func_name]=trace(func,'test.py','main')

Обратите внимание, что с приведенным выше следом

def baz():
    foo(3,4)
def main():
    foo(1,2,'Hi')
    bar(x=3)
    baz()

будет регистрировать вызовы foo(1,2,'Hi') и bar(x=3), но не foo(3,4), так как это звонок не приходит напрямую с основного. Однако это косвенно происходит от main, поскольку main вызывает baz. Если вы хотите записать вызов foo(3,4), то вы хотите цикл по всему стеку:

import traceback
def trace(f,filename,funcname):
    def tracewrapper(*arg, **kw):
        stacks=traceback.extract_stack()        
        for (s_filename,s_lineno,s_funcname,s_text) in stacks:
            if s_filename.endswith(filename) and s_funcname==funcname: 
                arg_str=','.join(['%r'%a for a in arg]+
                                 ['%s=%s'%(key,kw[key]) for key in kw])
                print "%s(%s)" % (f.__name__, arg_str)                
        return f(*arg, **kw)
    return tracewrapper
0 голосов
/ 12 ноября 2009

Я не знаю правильный способ сделать это в общем случае, но что приходит на ум, это просто немного взломать исполняемый файл. Что-то вроде:

class DebugCallWrapper(object):
    def __init__(self, callable):
        self.callable = callable

    def __call__(*args,**kwargs):
        log.debug(str(callable) + str(args) + str(kwargs))
        callable(*args,**kwargs)

class Top(object):
    def __getattribute__(self, name):
        real_object = globals()[name]
        if callable(real_object):
            return DebugCallWrapper(real_object)
        else:
            return real_object

top = Top()

import foo
#instead of foo.bar()
top.foo.bar()

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

0 голосов
/ 12 ноября 2009

Самое простое и первое решение, которое приходит на ум, это использование прокси-модуля.

#fooproxy.py
import foo
import logger #not implemented here - use your imagination :)

def bar(baz):
    logger.method("foo.bar")
    return foo.bar(baz)


#foo.py
def bar(baz):
    print "The real McCoy"

#main.py
import fooproxy as foo
foo.bar()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...