Seite 1 von 1

Decorators und sys.execpthook

Verfasst: Freitag 14. Dezember 2012, 09:23
von template
Hallo zusammen,

ich würde gerne in einem Exception-Hook auf eine Komponente zugreifen, die ich zuvor mit einem Decorator an einer Funktion hinterlegt habe. Ich will auf diese Weise hinterlegen, welche Lokalen bei einer Exception ausgegeben werden sollen, ohne das dadurch ein Laufzeit-Overhead entsteht:

Code: Alles auswählen

#!/usr/bin/env python
import sys

class mydecor(object):
	def __init__(self, *args):
		self.args = args
	
	def __call__(self, func):
		func.__mydecor__ = self.args
		return func

@mydecor('a', 'b', 'c')
def TestFunc(a, b):
	c = a + b
	return c + d
	
def MyExceptionHandler(exc_type, exc_value, exc_traceback):
	print 'How to access __mydecor__ here, that is a property of the function object???'
	
sys.excepthook = MyExceptionHandler
	
TestFunc(1, 2)
Vielen dank im voraus für jeden Vorschlag
template

Re: Decorators und sys.execpthook

Verfasst: Freitag 14. Dezember 2012, 12:00
von BlackJack
@template: Mal ein bisschen rumgehackt:

Code: Alles auswählen

#!/usr/bin/env python
import sys
from inspect import getinnerframes


def test_decorator(*args):
    def inner(func):
        func.test = args
        return func
    return inner


def f():
    g()


@test_decorator('a')
def g():
    a = 42
    b = 23
    h()


def h():
    return 1 / 0


def except_hook(type_, value, traceback):
    for frame, filename, line_no, name, _, _ in getinnerframes(traceback):
        print '%s:%s:%s' % (filename, name, line_no)
        func = frame.f_locals.get(name)
        if func is None:
            func = frame.f_globals.get(name)
        for local_name in getattr(func, 'test', []):
            print '>', local_name, '=', repr(frame.f_locals[local_name])

sys.excepthook = except_hook


def main():
    f()


if __name__ == '__main__':
    main()
Bei so etwas muss man immer aufpassen, dass man nicht auf Implementierungsdetails baut.

Re: Decorators und sys.execpthook

Verfasst: Freitag 14. Dezember 2012, 12:17
von template
Vielen dank für die schnelle Antwort.

Das sieht schon ganz gut aus. Über den Funktions-Namen wollte ich auch schon gehen, der ist ja auch über das Code Objekt unter co_name verfügbar. Aber wie sieht das ganze dann bei Methoden oder sogar statischen Methoden aus? Da dürfte dass dann so ja nicht funktionieren oder?

Re: Decorators und sys.execpthook

Verfasst: Freitag 14. Dezember 2012, 12:25
von BlackJack
@template: Stimmt. Letztendlich wird das IMHO nicht funktionieren weil im Traceback Codeobjekte sind, und die haben keinen Verweis auf die Funktions-, Methoden-, oder sonstige Objekte von denen sie stammen. Brauchen sie ja auch nicht wirklich.

Re: Decorators und sys.execpthook

Verfasst: Freitag 14. Dezember 2012, 12:59
von template
Mein ursprünglicher Ansatz sah anders aus. Statt mit einem Decorator habe ich mit einer lokalen Variable gearbeitet.

Code: Alles auswählen

def g():
    __testvar__ = [ 'a' ]
    a = 42
    b = 23
    h()

statt

@test_decorator('a')
def g():
    a = 42
    b = 23
    h()
Aber hierbei wird ja dann bei jedem Funktionsaufruf ein zusätzliches Listenobjekt instanziiert und das hat mit gestört. Außerdem fand ich es mit den Dekoraten Syntaktisch schöner.

Schade :(

Würde es etwas bringen __testvar__ statt als Liste als Tuple zu initialisieren? Würde dann nicht mehr jedes mal ein neues Objekt entstehen? Aber ich würde an den lokalen frame ja doch immer die property __testvar__ anhängen.

Re: Decorators und sys.execpthook

Verfasst: Freitag 14. Dezember 2012, 17:33
von Sirius3
Hallo template,

tuple sind genauso Objekte, die jedesmal neu erstellt werden, mit dem Unterschied, dass sie deutlich
weniger Overhead haben.

In Abwandlung von blackjacks Version, hier mit einem dict, das direkt das Codeobjekt mit den
Variablennamen verknüpft (Codeobjekte sind leider/zumglück readonly)

Code: Alles auswählen

#!/usr/bin/env python
import sys
from inspect import getinnerframes

funcargs_map={}
def test_decorator(*args):
    def inner(func):
        funcargs_map[func.func_code] = args
        return func
    return inner


@test_decorator('a','b')
def test(a):
    b = 42
    assert a==b


def except_hook(type_, value, traceback):
    for frame, filename, line_no, name, _, _ in getinnerframes(traceback):
        print '%s:%s:%s' % (filename, name, line_no)
        for local_name in funcargs_map.get(frame.f_code,()):
            print '>', local_name, '=', repr(frame.f_locals[local_name])

sys.excepthook = except_hook


if __name__ == '__main__':
    test(43)
Grüße
Sirius

Re: Decorators und sys.execpthook

Verfasst: Freitag 14. Dezember 2012, 17:57
von BlackJack
@Sirius3: Hier würde ich auf jeden Fall nachforschen ob `func_code` ein von der Sprache garantiertes Objekt ist. Und wie sich das bei Methoden verhält, also ob gebundene Methoden das selbe Code-Objekt verwenden wie die dekorierte ungebundene Methode und ob das Verhalten dort in irgendeiner Weise garantiert wird.

Letztendlich würde ich von dem Vorhaben die Finger lassen.

Re: Decorators und sys.execpthook

Verfasst: Samstag 15. Dezember 2012, 00:35
von Sirius3
prinzipiell ist es schon ein lobenswertes Vorhaben, dem User sprechendere Fehlermeldungen
zu präsentieren als tracebacks.

@blackjack: Der Versuch über den Namen von lokalen oder globalen Variablen die aufgerufene
Funktion wiederzufinden, führt zu sehr schwerwiegenden Seiteneffekten.

Beispiele:

Code: Alles auswählen

class G():
    @test_decorator('a')
    def g(self): # inneres g
        a = 42
        b = 'ich wollte eigentlich a'
        assert a==b

@test_decorator('b')
def g(): #aeusseres g
    b = 4

def main():
    obj = G()
    obj.g()

Code: Alles auswählen

@test_decorator('a')
def g(a):
    b='ich wollte eigentlich wieder a'
    assert a==17

@test_decorator('b')
def neues_g(b):
    return b*2

altes_g=g
g=neues_g

def main():
    altes_g(3)
usw.

Dass die Traceback- bzw. Code-Klassen in anderen Implementierungen von Python
nicht vorhanden oder anders sind, steht ja schon in der Dokumentation. Ob dann
überhaupt der Zugriff auf lokale Variablen möglich ist, ist dann auch zweifelhaft.

Bei Klassen-Methoden kommt es halt auf die Reihenfolge von Dekoratoren an, also

Code: Alles auswählen

class G:
    @classmethod
    @test_decorator('a','b')
    def test(cls,a):
        b = 42
        assert a==b
oder man gibt dem Dekorator noch etwas mehr intelligenz mit, dass er auch mit
staticmethod- bzw. classmethod-Klassen umgehen kann.