Effiziente Möglichkeit, um zu bestimmen, ob eine bestimmte Funktion auf dem Stapel in Python

StackOverflow https://stackoverflow.com/questions/1403471

  •  05-07-2019
  •  | 
  •  

Frage

Für das Debuggen, ist es oft sinnvoll, zu sagen, ob eine bestimmte Funktion höher auf dem Call-Stack ist. Zum Beispiel wollen wir oft nur Debug-Code auszuführen, wenn eine bestimmte Funktion uns genannt.

Eine Lösung ist, alle der Stapeleinträge höher bis zu prüfen, aber es ist dies in einer Funktion, die in dem Stapel tief und wiederholt aufgerufen, dies zu übermäßigem Aufwand führt. Die Frage ist, ein Verfahren zu finden, die Sie uns, wenn eine bestimmte Funktion bestimmen kann, ist höher auf dem Call-Stack in eine Weise, die einigermaßen effizient ist.

ähnliche

War es hilfreich?

Lösung

Es sei denn, die Funktion, die Sie streben tut etwas ganz Besonderes markieren „eine Instanz von mir ist aktiv auf dem Stapel“ (IOW: wenn die Funktion unberührte und unantastbar ist und nicht möglicherweise kann sich bewusst dieser eigentümlichen Notwendigkeit gemacht werden von Ihnen), gibt es keine denkbare Alternative Frame für Frame auf den Stapel zu Fuß, bis Sie treffen entweder die obere (und die Funktion ist nicht vorhanden) oder ein Stapelrahmen für Ihre Funktion von Interesse. Da mehrere Kommentare auf die Frage angeben, dann ist es sehr zweifelhaft, ob es sich lohnt, strebt diese zu optimieren. Aber unter der Annahme, für die Zwecke der Beweisführung, dass es betrug lohnt sich ...:

Bearbeiten :. Die ursprüngliche Antwort (vom OP) hatte viele Mängel, aber einige haben seit behoben worden, so dass ich die Bearbeitung der aktuellen Situation zu reflektieren und warum bestimmte Aspekte sind wichtig

Zunächst einmal ist es wichtig, try / except oder with, in dem Dekorateur, so dass jede Ausfahrt aus einer Funktion überwacht zu verwenden ist richtig berücksichtigt wird, nicht nur Normale (wie die ursprüngliche Version des eigenen Antwort des OP tat).

Zweitens sollte jeder Dekorateur es sicherzustellen, hält die __name__ gezierte Funktion und __doc__ intakt - das ist, was functools.wraps ist für (es andere Wege gibt, aber wraps macht es am einfachsten)

.

Drittens ebenso entscheidend wie der erste Punkt, ein set, die die Datenstruktur ursprünglich vom OP gewählt wurde, ist die falsche Wahl: eine Funktion auf dem Stapel mehrmals (direkte oder indirekte Rekursion) sein kann. Wir brauchen eindeutig ein „Multi-Set“ (auch als „bag“ genannt), eine Reihe artige Struktur, die Spur von „wie oft“ jedes Element hält vorhanden ist. In Python ist die natürliche Implementierung eines multiset als dict Kartierschlüssel zu zählt, die wiederum die meist handlichen als collections.defaultdict(int) umgesetzt werden.

Viertens sollte ein allgemeiner Ansatz THREAD (wenn das leicht erreicht werden kann, zumindest ;-). Glücklicherweise macht threading.local trivial es, wenn anwendbar -. Und hier sollte es sicher sein (jeder Stapel von Anrufen einen eigenen Thread mit)

Fünfter, ein interessantes Thema, das in einigen Kommentaren angeschnitten wurde (zu bemerken, wie schlecht der angebotenen Dekorateure in einigen Antworten mit anderen Dekorateure spielen: die Überwachung Dekorateur erscheint der letzte (äußerste) ein, da sonst die Kontrolle Pausen zu müssen sein. Dies ergibt sich aus der natürlichen, aber unglückliche Wahl der Verwendung der Funktion Objekt selbst als Schlüssel in die Überwachung dict.

Ich schlage vor, diese von Schlüssel durch eine andere Wahl zu lösen: Stellen Sie den Dekorateur einen nehmen (string, sagt) identifier Argument, das (in jedem gegebenen Thread) und verwenden Sie die Kennung als Schlüssel in die Überwachung dict eindeutig sein muss. Der Code, den Stapel Überprüfung muss natürlich der Kennung bewusst sein, und es verwenden, auch.

Zur Zeit dekorieren kann der Dekorateur für die Einzigartigkeit Eigenschaft überprüfen (durch einen separaten Satz verwenden). Die Kennung kann auf die Funktionsnamen auf Standard gelassen werden (so ist es nur explizit die Flexibilität gleichnamige Funktionen der Überwachung im gleichen Namensraum zu halten erforderlich); die Einzigartigkeit Eigenschaft explizit verzichtet werden kann, wenn mehrere überwachte Funktionen werden als „die gleichen“ für Überwachungszwecke (dies der Fall sein kann, wenn eine bestimmte def Anweisung mehrmals in leicht unterschiedlichen Kontexten werden soll ausgeführt mehrere Funktionsobjekte machen, dass die Programmierer wollen „die gleiche Funktion“ zu Kontrollzwecken) zu berücksichtigen. Schließlich sollte es möglich sein, auf das „Funktionsobjekt als Kennung“ optional zurückkehrt für die seltenen Fälle, in denen weitere Dekoration bekannt ist unmöglich zu sein (da in diesen Fällen kann es die angenehmste Art seine Eindeutigkeit zu gewährleisten).

Also, diese vielen Überlegungen der Zusammenstellung, könnten wir (einschließlich einer threadlocal_var Nutzenfunktion haben, die wahrscheinlich schon in einem t sein wirdoolbox Modul natürlich ;-) etwas wie das folgende ...:

import collections
import functools
import threading

threadlocal = threading.local()

def threadlocal_var(varname, factory, *a, **k):
  v = getattr(threadlocal, varname, None)
  if v is None:
    v = factory(*a, **k)
    setattr(threadlocal, varname, v)
  return v

def monitoring(identifier=None, unique=True, use_function=False):
  def inner(f):
    assert (not use_function) or (identifier is None)
    if identifier is None:
      if use_function:
        identifier = f
      else:
        identifier = f.__name__
    if unique:
      monitored = threadlocal_var('uniques', set)
      if identifier in monitored:
        raise ValueError('Duplicate monitoring identifier %r' % identifier)
      monitored.add(identifier)
    counts = threadlocal_var('counts', collections.defaultdict, int)
    @functools.wraps(f)
    def wrapper(*a, **k):
      counts[identifier] += 1
      try:
        return f(*a, **k)
      finally:
        counts[identifier] -= 1
    return wrapper
  return inner

Ich habe diesen Code nicht getestet, so dass es einige Tippfehler oder ähnliches enthalten könnte, aber ich biete es, weil ich hoffe, dass es mit den wichtigsten technischen Punkten alles, was ich oben erläuterte abdeckt.

Ist es das alles wert? Wahrscheinlich nicht, wie zuvor erläutert. Aber ich denke, nach dem Vorbild von „wenn es überhaupt wert ist zu tun, dann lohnt es sich richtig machen“, -).

Andere Tipps

Ich weiß nicht wirklich wie dieser Ansatz, aber hier ist eine feste-up-Version von dem, was du getan hast:

from collections import defaultdict
import threading
functions_on_stack = threading.local()

def record_function_on_stack(f):
    def wrapped(*args, **kwargs):
        if not getattr(functions_on_stack, "stacks", None):
            functions_on_stack.stacks = defaultdict(int)
        functions_on_stack.stacks[wrapped] += 1

        try:
            result = f(*args, **kwargs)
        finally:
            functions_on_stack.stacks[wrapped] -= 1
            if functions_on_stack.stacks[wrapped] == 0:
                del functions_on_stack.stacks[wrapped]
        return result

    wrapped.orig_func = f
    return wrapped

def function_is_on_stack(f):
    return f in functions_on_stack.stacks

def nested():
    if function_is_on_stack(test):
        print "nested"

@record_function_on_stack
def test():
    nested()

test()

Diese Griffe Rekursion, Gewindeschneiden und Ausnahmen.

Ich mag es nicht, diesen Ansatz aus zwei Gründen:

  • Es funktioniert nicht, wenn die Funktion weiter eingerichtet: Das muss der letzte Dekorateur sein
  • .
  • Wenn Sie diese verwenden für das Debuggen, es bedeutet, dass Sie Code an zwei Stellen bearbeiten haben, es zu benutzen; man den Dekorateur hinzuzufügen, und man es zu benutzen. Es ist viel bequemer, einfach den Stapel zu untersuchen, so dass Sie nur Code im Code zu bearbeiten haben Sie debuggen.

Ein besserer Ansatz wäre es, den Stapel direkt zu untersuchen (möglicherweise als native Erweiterung für Geschwindigkeit), und, wenn möglich, einen Weg finden, um die Ergebnisse für die gesamte Lebensdauer des Stapelrahmens cachen. (Ich bin nicht sicher, ob das möglich ist, ohne dass der Python Kern zu modifizieren, though.)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top