4 Stimmen

aspektorientierte Techniken in Python?

Ich habe also ein interessantes Problem in Python, das möglicherweise mit aspektorientierten Techniken gelöst werden könnte. Hier ist die Situation:

  1. Ich habe eine Reihe von Modulen, von denen jedes eine Reihe von Funktionen hat.
  2. Ich habe eine ausführbare Datei, die eine Reihe von Funktionen in diesen Modulen aufruft.
  3. Wenn die ausführbare Datei eine dieser Funktionen aufruft, möchte ich eine Protokollanweisung mit den Details des Funktionsaufrufs (Name und Parameter) erstellen
  4. Wenn eines der Module eine Funktion eines anderen Moduls aufruft, möchte ich NICHT, dass eine Protokollanweisung erzeugt wird.

Gibt es eine bequeme Möglichkeit, dies in Python zu tun, ohne Logging-Anweisungen in jeder der Funktionen der Module zu stecken?

0 Stimmen

"Akzeptieren" orientierte Programmierung ;-)

5voto

unutbu Punkte 769083

Das Verhalten der Funktionen in der ausführbaren Datei kann mit einem Dekorator geändert werden:

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

Da Sie nur die Definition der Funktionen im Namensraum der ausführbaren Datei ändern, bleiben die Funktionen der Module unangetastet. Wenn die Funktion eines Moduls die Funktion eines anderen Moduls aufruft, wird sie nicht durch Trace gekennzeichnet und es wird keine Protokollanweisung erzeugt.

Wenn Sie Funktionsaufrufe nur protokollieren möchten, wenn sie direkt von main() kommen, dann könnten Sie einen Trace-Dekorator wie diesen verwenden:

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

Beachten Sie, dass mit der obigen Kurve

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

würde die foo(1,2,'Hi') y bar(x=3) Anrufe, aber no foo(3,4) da diese Anruf nicht kommt direkt von main. Sie kommt jedoch indirekt von main, denn main ruft baz . Wenn Sie die Daten protokollieren möchten foo(3,4) anrufen, dann sollten Sie Schleife durch den gesamten Stapel:

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 Stimmen

Ok, das ist ziemlich genau das, was ich brauche. Ich möchte jedoch nur die Protokollmeldung auf der Grundlage von Informationen über den Aufrufer drucken. Wie erhalte ich Informationen über den Aufrufer innerhalb der tracewrapper()-Implementierung?

0 Stimmen

Ich möchte den Modul- und Funktionsnamen des Aufrufers wissen; ich möchte die Informationen nur protokollieren, wenn der Aufruf von Modul 'test', Funktion 'main' kommt.

0voto

gnud Punkte 75549

Die einfachste und erste Lösung, die mir einfällt, ist die Verwendung eines Proxy-Moduls.

#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 Stimmen

Dies würde ein Proxy für jedes aufgerufene Modul erfordern, das eine Definition für jede aufgerufene Funktion enthält. Dies ist möglicherweise nicht sehr gut skalierbar.

0 Stimmen

Das ist sicherlich machbar, aber... Ich habe Dutzende von diesen Modulen, und jedes von ihnen hat vielleicht bis zu 100 Funktionen. Das sind also verdammt viele Proxy-Funktionen, die ich schreiben muss (!).

0voto

David Berger Punkte 11589

Ich weiß nicht, wie man das generell macht, aber was mir einfällt, ist, die ausführbare Datei ein wenig zu hacken. Etwas wie:

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

So etwas kann eine Menge Ärger mit sich bringen, und die obigen Angaben sind wahrscheinlich nicht ohne einige Änderungen verwendbar. Aber vielleicht ist es eine Idee.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X