2009-11-12 3 views
4

J'ai donc un problème intéressant en Python, qui pourrait être résolu avec des techniques orientées aspect. Voici la situation:techniques orientées aspect en python?

  1. J'ai un tas de modules, dont chacun a un tas de fonctions.
  2. J'ai un exécutable qui appelle un ensemble de fonctions dans ces modules.
  3. Lorsque l'exécutable appelle une de ces fonctions, je veux une déclaration de journal généré avec les détails de l'appel de fonction (nom & paramètres)
  4. Lorsque l'un des modules appelle une des fonctions des autres modules, je ne Voulez-vous générer une instruction de journal?

Y a-t-il un moyen pratique de faire cela en Python, sans coller d'instructions de journalisation dans chacune des fonctions des modules?

+0

« Accepter » programmation orientée ;-) – jldupont

Répondre

0

La solution la plus simple et la première qui vient à l'esprit est l'utilisation d'un module proxy.

#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() 
+0

Cela nécessiterait une procuration pour chaque module appelé contenant une définition pour chaque fonction appelée. Peut ne pas très bien évoluer. –

+0

C'est certainement réalisable, mais ... J'ai des douzaines de ces modules, et chacun d'eux a peut-être jusqu'à 100 fonctions. C'est donc un tas de fonctions proxy à écrire (!). –

0

Je ne sais pas la bonne façon de le faire dans un cas général, mais ce qui vient à l'esprit est tout simplement l'exécutable piraté un peu. Quelque chose comme:

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() 

Ce genre de chose pourrait vous entrer dans beaucoup d'ennuis, et ce qui précède ne sera probablement pas utilisable sans quelques ajustements. Mais peut-être que c'est une idée.

5

Modifier le comportement des fonctions de l'exécutable peut être fait en utilisant un décorateur:

#!/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) 

Puisque vous ne modifiez la définition des fonctions dans l'espace de noms de l'exécutable, les fonctions des modules restent intacts. Lorsque la fonction d'un module appelle la fonction d'un autre module, elle n'est pas décomposée par trace et aucune instruction n'est générée.

Si vous souhaitez connecter des appels de fonction que quand ils viennent directement de main(), alors vous pouvez utiliser un décorateur de trace comme ceci:

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') 

Notez que la trace ci-dessus

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

se connecter les foo(1,2,'Hi') et bar(x=3) appels, mais pas foo(3,4) depuis cet appel ne vient pas directement de la main. Cependant, il vient indirectement de principal, puisque les appels principaux baz. Si vous souhaitez connecter l'appel foo(3,4), alors vous voulez boucle à travers l'ensemble de la pile:

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

Ok, c'est assez proche de ce dont j'ai besoin.Cependant, je veux seulement imprimer le message de journal basé sur des informations sur l'appelant. Comment puis-je obtenir des informations sur l'appelant dans l'implémentation tracewrapper()? –

+0

Quelles sont les informations sur l'appelant que vous cherchez? – unutbu

+0

Je voudrais connaître le nom du module et de la fonction de l'appelant; Je veux seulement enregistrer les informations si l'appel provient du module 'test', fonction 'main'. –