Python-Debugging mit Pdb

Python-Debugging mit Pdb

Das Debuggen von Anwendungen kann manchmal eine unerwünschte Aktivität sein. Sie arbeiten gerade in einer Zeitkrise und möchten nur, dass es funktioniert. In anderen Fällen lernen Sie jedoch möglicherweise eine neue Sprachfunktion oder experimentieren mit einem neuen Ansatz und möchten besser verstehen, wie etwas funktioniert.

Unabhängig von der Situation ist das Debuggen von Code eine Notwendigkeit. Daher ist es eine gute Idee, mit der Arbeit in einem Debugger vertraut zu sein. In diesem Tutorial zeige ich Ihnen die Grundlagen der Verwendung von pdb, dem interaktiven Quellcode-Debugger von Python.

Ich werde Sie durch einige gängige Verwendungen von PDF führen. Möglicherweise möchten Sie dieses Tutorial als Lesezeichen speichern, damit Sie es später schnell nachschlagen können. pdb und andere Debugger sind unverzichtbare Werkzeuge. Wenn Sie einen Debugger benötigen, gibt es keinen Ersatz. Du brauchst es wirklich.

Am Ende dieses Tutorials erfahren Sie, wie Sie mit dem Debugger den Status einer Variablen in Ihrer Anwendung anzeigen. Sie können den Ausführungsfluss Ihrer Anwendung auch jederzeit stoppen und fortsetzen, sodass Sie genau sehen können, wie sich jede Codezeile auf ihren internen Status auswirkt.

Dies ist ideal, um schwer zu findende Fehler aufzuspüren, und ermöglicht es Ihnen, fehlerhaften Code schneller und zuverlässiger zu beheben. Manchmal kann das Durchlaufen von Code in pdb und das Sehen, wie sich Werte ändern, ein echter Augenöffner sein und zu „Aha“ -Momenten führen, zusammen mit gelegentlichen „Gesichtspalmen“.

pdb ist Teil der Standardbibliothek von Python, daher immer verfügbar und verfügbar. Dies kann lebensrettend sein, wenn Sie Code in einer Umgebung debuggen müssen, in der Sie keinen Zugriff auf den Ihnen vertrauten GUI-Debugger haben.

Der Beispielcode in diesem Tutorial verwendet Python 3.6. Den Quellcode für diese Beispiele finden Sie unterGitHub.

Am Ende dieses Tutorials finden Sie eine Kurzreferenz fürEssential pdb Commands.

Es gibt auch eine druckbare pdb-Befehlsreferenz, die Sie beim Debuggen als Spickzettel verwenden können:

Free Bonus:Click here to get a printable "pdb Command Reference" (PDF), auf die Sie beim Debuggen auf Ihrem Schreibtisch zugreifen können.

Erste Schritte: Drucken des Werts einer Variablen

In diesem ersten Beispiel wird die Verwendung von pdb in seiner einfachsten Form betrachtet: Überprüfen des Werts einer Variablen.

Geben Sie den folgenden Code an der Stelle ein, an der Sie in den Debugger einbrechen möchten:

import pdb; pdb.set_trace()

Wenn die obige Zeile ausgeführt wird, stoppt Python und wartet darauf, dass Sie ihm mitteilen, was als Nächstes zu tun ist. Sie sehen eine Eingabeaufforderung von(Pdb). Dies bedeutet, dass Sie jetzt im interaktiven Debugger angehalten sind und einen Befehl eingeben können.

Ab Python 3.7there’s another way to enter the debugger. PEP 553 beschreibt die integrierte Funktionbreakpoint(), die das Aufrufen des Debuggers einfach und konsistent macht:

breakpoint()

Standardmäßig importiertbreakpoint()pdb und ruftpdb.set_trace() auf, wie oben gezeigt. Die Verwendung vonbreakpoint() ist jedoch flexibler und ermöglicht es Ihnen, das Debugging-Verhalten über die API und die Verwendung der UmgebungsvariablenPYTHONBREAKPOINT zu steuern. Wenn Sie beispielsweisePYTHONBREAKPOINT=0 in Ihrer Umgebung festlegen, werdenbreakpoint() vollständig deaktiviert, wodurch das Debuggen deaktiviert wird. Wenn Sie Python 3.7 oder höher verwenden, empfehlen wir Ihnen,breakpoint() anstelle vonpdb.set_trace() zu verwenden.

Sie können auch in den Debugger einbrechen, ohne die Quelle zu ändern undpdb.set_trace() oderbreakpoint() zu verwenden, indem Sie Python direkt über die Befehlszeile ausführen und die Option-m pdb übergeben. Wenn Ihre Anwendung Befehlszeilenargumente akzeptiert, übergeben Sie diese wie gewohnt nach dem Dateinamen. Zum Beispiel:

$ python3 -m pdb app.py arg1 arg2

Es sind viele pdb-Befehle verfügbar. Am Ende dieses Tutorials befindet sich eine Liste vonEssential pdb Commands. Verwenden Sie zunächst den Befehlp, um den Wert einer Variablen zu drucken. Geben Sie an der Eingabeaufforderung(Pdb)p variable_name ein, um den Wert zu drucken.

Schauen wir uns das Beispiel an. Hier ist die Quelle vonexample1.py:

#!/usr/bin/env python3

filename = __file__
import pdb; pdb.set_trace()
print(f'path = {filename}')

Wenn Sie dies von Ihrer Shell aus ausführen, sollten Sie die folgende Ausgabe erhalten:

$ ./example1.py
> /code/example1.py(5)()
-> print(f'path = {filename}')
(Pdb)

Wenn Sie Probleme haben, die Beispiele oder Ihren eigenen Code über die Befehlszeile auszuführen, lesen SieHow Do I Make My Own Command-Line Commands Using Python?. Wenn Sie unter Windows arbeiten, überprüfen SiePython Windows FAQ.

Geben Sie nunp filename ein. Das solltest du sehen:

(Pdb) p filename
'./example1.py'
(Pdb)

Da Sie sich in einer Shell befinden und eine CLI (Befehlszeilenschnittstelle) verwenden, achten Sie auf die Zeichen und die Formatierung. Sie geben Ihnen den Kontext, den Sie benötigen:

  • > beginnt die erste Zeile und teilt Ihnen mit, in welcher Quelldatei Sie sich befinden. Nach dem Dateinamen steht die aktuelle Zeilennummer in Klammern. Weiter ist der Name der Funktion. In diesem Beispiel sehen wir<module>(), da wir nicht innerhalb einer Funktion und auf Modulebene angehalten werden.

  • -> startet die 2. Zeile und ist die aktuelle Quellzeile, in der Python angehalten wird. Diese Zeile wurde noch nicht ausgeführt. In diesem Beispiel ist dies die Zeile5 inexample1.py aus der obigen Zeile>.

  • (Pdb) ist die Eingabeaufforderung von pdb. Es wartet auf einen Befehl.

Verwenden Sie den Befehlq, um das Debuggen zu beenden und zu beenden.

Ausdrücke drucken

Wenn Sie den Druckbefehlp verwenden, übergeben Sie einen Ausdruck, der von Python ausgewertet werden soll. Wenn Sie einen Variablennamen übergeben, gibt pdb seinen aktuellen Wert aus. Sie können jedoch noch viel mehr tun, um den Status Ihrer laufenden Anwendung zu untersuchen.

In diesem Beispiel wird die Funktionget_path() aufgerufen. Um zu überprüfen, was in dieser Funktion passiert, habe ichpdb.set_trace() aufgerufen, um die Ausführung kurz vor der Rückkehr anzuhalten:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    import pdb; pdb.set_trace()
    return head


filename = __file__
print(f'path = {get_path(filename)}')

Wenn Sie dies von Ihrer Shell aus ausführen, sollten Sie die Ausgabe erhalten:

$ ./example2.py
> /code/example2.py(10)get_path()
-> return head
(Pdb)

Wo sind wir?

  • >: Wir befinden uns in der Quelldateiexample2.py in Zeile10 in der Funktionget_path(). Dies ist der Referenzrahmen, den der Befehlpverwendet, um Variablennamen aufzulösen, d. H. den aktuellen Umfang oder Kontext.

  • ->: Die Ausführung wurde beireturn head angehalten. Diese Zeile wurde noch nicht ausgeführt. Dies ist die Zeile10 inexample2.py in der Funktionget_path() aus der obigen Zeile>.

Drucken wir einige Ausdrücke aus, um den aktuellen Status der Anwendung anzuzeigen. Ich benutze zunächst den Befehlll (Longlist), um die Quelle der Funktion aufzulisten:

(Pdb) ll
  6     def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         import pdb; pdb.set_trace()
 10  ->     return head
(Pdb) p filename
'./example2.py'
(Pdb) p head, tail
('.', 'example2.py')
(Pdb) p 'filename: ' + filename
'filename: ./example2.py'
(Pdb) p get_path

(Pdb) p getattr(get_path, '__doc__')
"Return file's path or empty string if no path."
(Pdb) p [os.path.split(p)[1] for p in os.path.sys.path]
['pdb-basics', 'python36.zip', 'python3.6', 'lib-dynload', 'site-packages']
(Pdb)

Sie können jeden gültigen Python-Ausdruck zur Auswertung anp übergeben.

Dies ist besonders hilfreich, wenn Sie debuggen und zur Laufzeit eine alternative Implementierung direkt in der Anwendung testen möchten.

Sie können auch den Befehlpp (Pretty-Print) verwenden, um Ausdrücke hübsch zu drucken. Dies ist hilfreich, wenn Sie eine Variable oder einen Ausdruck mit einer großen Ausgabemenge drucken möchten, z. Listen und Wörterbücher. Beim hübschen Drucken bleiben Objekte in einer einzelnen Zeile, wenn dies möglich ist, oder werden in mehrere Zeilen aufgeteilt, wenn sie nicht in die zulässige Breite passen.

Code durchgehen

Es gibt zwei Befehle, mit denen Sie beim Debuggen den Code schrittweise durchlaufen können:

Befehl Beschreibung

n (weiter)

Setzen Sie die Ausführung fort, bis die nächste Zeile in der aktuellen Funktion erreicht ist oder zurückkehrt.

s (Schritt)

Führen Sie die aktuelle Zeile aus und stoppen Sie bei der ersten möglichen Gelegenheit (entweder in einer aufgerufenen Funktion oder in der aktuellen Funktion).

Es gibt einen dritten Befehl mit dem Namenunt (bis). Es bezieht sich aufn (weiter). Wir werden es uns später in diesem Tutorial im AbschnittContinuing Execution ansehen.

Der Unterschied zwischenn (weiter) unds (Schritt) besteht darin, wo pdb stoppt.

Verwenden Sien (next), um die Ausführung bis zur nächsten Zeile fortzusetzen und innerhalb der aktuellen Funktion zu bleiben, d. H. nicht in einer fremden Funktion anhalten, wenn eine aufgerufen wird. Stellen Sie sich als Nächstes „vor Ort bleiben“ oder „übersteigen“ vor.

Verwenden Sies (Schritt), um die aktuelle Zeile auszuführen und in einer Fremdfunktion anzuhalten, wenn eine aufgerufen wird. Stellen Sie sich Schritt als „Schritt in“ vor. Wenn die Ausführung in einer anderen Funktion gestoppt wird, gibts--Call-- aus.

Sowohln als auchs stoppen die Ausführung, wenn das Ende der aktuellen Funktion erreicht ist, und drucken--Return-- zusammen mit dem Rückgabewert am Ende der nächsten Zeile nach->.

Schauen wir uns ein Beispiel mit beiden Befehlen an. Hier ist die Quelle vonexample3.py:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    return head


filename = __file__
import pdb; pdb.set_trace()
filename_path = get_path(filename)
print(f'path = {filename_path}')

Wenn Sie dies von Ihrer Shell aus ausführen undn eingeben, sollten Sie die folgende Ausgabe erhalten:

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) n
> /code/example3.py(15)()
-> print(f'path = {filename_path}')
(Pdb)

Mitn (next) haben wir in Zeile15, der nächsten Zeile, angehalten. Wir sind in<module>() „lokal geblieben“ und haben den Anruf aufget_path() „übersprungen“. Die Funktion ist<module>(), da wir uns derzeit auf Modulebene befinden und nicht in einer anderen Funktion angehalten sind.

Versuchen wirs:

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb)

Mits (Schritt) haben wir in Zeile6 in der Funktionget_path() angehalten, da sie in Zeile14 aufgerufen wurde. Beachten Sie die Zeile--Call-- nach dem Befehls.

Praktischerweise merkt sich pdb Ihren letzten Befehl. Wenn Sie viel Code durchlaufen, können Sie einfach[.kbd .key-enter]#Enter # drücken, um den letzten Befehl zu wiederholen.

Unten finden Sie ein Beispiel für die Verwendung vons undn, um den Code zu durchlaufen. Ich gebe zunächsts ein, weil ich in die Funktionget_path() „eintreten“ und anhalten möchte. Dann gebe ich einmaln ein, um lokal zu bleiben oder andere Funktionsaufrufe zu überspringen, und drücke einfach[.kbd .key-enter]#Enter #, um den Befehln zu wiederholen, bis ich zur letzten Quellzeile komme.

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) n
> /code/example3.py(8)get_path()
-> head, tail = os.path.split(filename)
(Pdb)
> /code/example3.py(9)get_path()
-> return head
(Pdb)
--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb)
> /code/example3.py(15)()
-> print(f'path = {filename_path}')
(Pdb)
path = .
--Return--
> /code/example3.py(15)()->None
-> print(f'path = {filename_path}')
(Pdb)

Beachten Sie die Zeilen--Call-- und--Return--. Mit dieser PDF-Datei erfahren Sie, warum die Ausführung gestoppt wurde. n (next) unds (step) werden gestoppt, bevor eine Funktion zurückkehrt. Deshalb sehen Sie oben die Zeilen von--Return--.

Beachten Sie auch->'.' am Ende der Zeile nach den ersten--Return-- oben:

--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb)

Wenn pdb am Ende einer Funktion stoppt, bevor sie zurückgegeben wird, wird auch der Rückgabewert für Sie gedruckt. In diesem Beispiel sind es'.'.

Quellcode auflisten

Vergessen Sie nicht den Befehlll (Longlist: Listen Sie den gesamten Quellcode für die aktuelle Funktion oder den aktuellen Frame auf). Dies ist sehr hilfreich, wenn Sie unbekannten Code durchgehen oder nur die gesamte Funktion für den Kontext anzeigen möchten.

Hier ist ein Beispiel:

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) ll
  6  -> def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         return head
(Pdb)

Verwenden Sie den Befehll (Liste), um einen kürzeren Codeausschnitt anzuzeigen. Ohne Argumente werden 11 Zeilen um die aktuelle Zeile gedruckt oder die vorherige Auflistung fortgesetzt. Übergeben Sie das Argument., um immer 11 Zeilen um die aktuelle Zeile aufzulisten:l .

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) l
  9         return head
 10
 11
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) l
[EOF]
(Pdb) l .
  9         return head
 10
 11
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb)

Haltepunkte verwenden

Haltepunkte sind sehr praktisch und können Ihnen viel Zeit sparen. Erstellen Sie einfach einen Haltepunkt, an dem Sie nachforschen möchten, anstatt Dutzende von Zeilen zu durchlaufen, an denen Sie nicht interessiert sind. Optional können Sie pdb auch anweisen, nur dann zu brechen, wenn eine bestimmte Bedingung erfüllt ist.

Verwenden Sie den Befehlb (break), um einen Haltepunkt festzulegen. Sie können eine Zeilennummer oder einen Funktionsnamen angeben, bei dem die Ausführung gestoppt wird.

Die Syntax für break lautet:

b(reak) [ ([filename:]lineno | function) [, condition] ]

Wennfilename: nicht vor der Zeilennummerlineno angegeben wird, wird die aktuelle Quelldatei verwendet.

Beachten Sie das optionale 2. Argument fürb:condition. Das ist sehr mächtig. Stellen Sie sich eine Situation vor, in der Sie nur brechen wollten, wenn eine bestimmte Bedingung bestand. Wenn Sie einen Python-Ausdruck als zweites Argument übergeben, wird pdb unterbrochen, wenn der Ausdruck true ergibt. Wir werden dies in einem Beispiel unten tun.

In diesem Beispiel gibt es ein Dienstprogrammmodulutil.py. Setzen wir einen Haltepunkt, um die Ausführung in der Funktionget_path() zu stoppen.

Hier ist die Quelle für das Hauptskriptexample4.py:

#!/usr/bin/env python3

import util

filename = __file__
import pdb; pdb.set_trace()
filename_path = util.get_path(filename)
print(f'path = {filename_path}')

Hier ist die Quelle für das Dienstprogrammmodulutil.py:

def get_path(filename):
    """Return file's path or empty string if no path."""
    import os
    head, tail = os.path.split(filename)
    return head

Legen Sie zunächst einen Haltepunkt mit dem Quelldateinamen und der Zeilennummer fest:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util:5
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p filename, head, tail
('./example4.py', '.', 'example4.py')
(Pdb)

Der Befehlc (Fortsetzung) setzt die Ausführung fort, bis ein Haltepunkt gefunden wurde.

Als nächstes setzen wir einen Haltepunkt unter Verwendung des Funktionsnamens:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) p filename
'./example4.py'
(Pdb)

Geben Sieb ohne Argumente ein, um eine Liste aller Haltepunkte anzuzeigen:

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb)

Sie können Haltepunkte mit den Befehlendisable bpnumber undenable bpnumber deaktivieren und wieder aktivieren. bpnumber ist die Haltepunktnummer aus der ersten Spalte der HaltepunktlisteNum. Beachten Sie die Wertänderung der SpalteEnb:

(Pdb) disable 1
Disabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /code/util.py:1
(Pdb) enable 1
Enabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb)

Verwenden Sie zum Löschen eines Haltepunkts den Befehlcl (clear):

cl(ear) filename:lineno
cl(ear) [bpnumber [bpnumber...]]

Verwenden wir nun einen Python-Ausdruck, um einen Haltepunkt festzulegen. Stellen Sie sich eine Situation vor, in der Sie nur dann brechen wollten, wenn Ihre gestörte Funktion eine bestimmte Eingabe erhielt.

In diesem Beispielszenario schlägt die Funktionget_path()fehl, wenn sie einen relativen Pfad empfängt, d. H. Der Pfad der Datei beginnt nicht mit/. Ich werde einen Ausdruck erstellen, der in diesem Fall als wahr ausgewertet wird, und ihn als zweites Argument anb übergeben:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path, not filename.startswith('/')
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) a
filename = './example4.py'
(Pdb)

Nachdem Sie den obigen Haltepunkt erstellt undc eingegeben haben, um die Ausführung fortzusetzen, stoppt pdb, wenn der Ausdruck true ergibt. Der Befehla (args) gibt die Argumentliste der aktuellen Funktion aus.

Wenn Sie im obigen Beispiel den Haltepunkt mit einem Funktionsnamen anstelle einer Zeilennummer festlegen, sollten Sie beachten, dass der Ausdruck nur Funktionsargumente oder globale Variablen verwenden sollte, die zum Zeitpunkt der Eingabe der Funktion verfügbar sind. Andernfalls stoppt der Haltepunkt die Ausführung in der Funktion unabhängig vom Wert des Ausdrucks.

Wenn Sie die Verwendung eines Ausdrucks mit einem Variablennamen innerhalb einer Funktion unterbrechen müssen, d. H. Geben Sie als Zeilennamen, der nicht in der Argumentliste der Funktion enthalten ist, die Zeilennummer an:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util:5, not head.startswith('/')
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p head
'.'
(Pdb) a
filename = './example4.py'
(Pdb)

Sie können auch einen temporären Haltepunkt mit dem Befehltbreak festlegen. Es wird automatisch entfernt, wenn es zum ersten Mal getroffen wird. Es werden dieselben Argumente wieb verwendet.

Fortsetzung der Ausführung

Bisher haben wir uns mit dem Durchlaufen von Code mitn (next) unds (step) und der Verwendung von Haltepunkten mitb (break) undc (continue) befasst. .

Es gibt auch einen verwandten Befehl:unt (bis).

Verwenden Sieunt, um die Ausführung wiec fortzusetzen, aber halten Sie an der nächsten Zeile an, die größer als die aktuelle Zeile ist. Manchmal istunt bequemer und schneller zu verwenden und genau das, was Sie wollen. Ich werde dies anhand eines Beispiels unten demonstrieren.

Schauen wir uns zunächst die Syntax und Beschreibung fürunt an:

Befehl Syntax Beschreibung

unt

unt (il) [lineno]

Setzen Sie die Ausführung ohnelineno fort, bis die Zeile mit einer größeren Zahl als der aktuellen erreicht ist. Setzen Sie mitlineno die Ausführung fort, bis eine Zeile mit einer Zahl größer oder gleich dieser erreicht ist. Beenden Sie in beiden Fällen auch, wenn der aktuelle Frame zurückkehrt.

Abhängig davon, ob Sie das Zeilennummernargumentlineno übergeben oder nicht, kann sichunt auf zwei Arten verhalten:

  • Setzen Sie die Ausführung ohnelineno fort, bis die Zeile mit einer größeren Zahl als der aktuellen erreicht ist. Dies ist ähnlich zun (weiter). Es ist eine alternative Möglichkeit, Code auszuführen und zu "überspringen". Der Unterschied zwischenn undunt besteht darin, dassunt nur dann stoppt, wenn eine Linie mit einer Nummer erreicht wird, die größer als die aktuelle ist. n stoppt bei der nächsten logisch ausgeführten Zeile.

  • Setzen Sie mitlineno die Ausführung fort, bis eine Zeile mit einer Zahl größer oder gleich dieser erreicht ist. Dies ist wiec (weiter) mit einem Zeilennummernargument.

In beiden Fällen stopptunt, wenn der aktuelle Frame (Funktion) zurückkehrt, genau wien (next) unds (step).

Das primäre Verhalten beiunt ist, dass es stoppt, wenn eine Zeilennummergreater or equal zur aktuellen oder angegebenen Zeile erreicht wird.

Verwenden Sieunt, wenn Sie die Ausführung fortsetzen und weiter unten in der aktuellen Quelldatei anhalten möchten. Sie können es wie eine Mischung ausn (next) undb (break) behandeln, je nachdem, ob Sie ein Zeilennummernargument übergeben oder nicht.

Im folgenden Beispiel gibt es eine Funktion mit einer Schleife. Hier möchten Sie die Ausführung des Codes fortsetzen und nach der Schleife anhalten, ohne jede Iteration der Schleife zu durchlaufen oder einen Haltepunkt festzulegen:

Hier ist die Beispielquelle fürexample4unt.py:

#!/usr/bin/env python3

import os


def get_path(fname):
    """Return file's path or empty string if no path."""
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    for char in tail:
        pass  # Check filename char
    return head


filename = __file__
filename_path = get_path(filename)
print(f'path = {filename_path}')

Und die Konsolenausgabe mitunt:

$ ./example4unt.py
> /code/example4unt.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) unt
> /code/example4unt.py(10)get_path()
-> for char in tail:
(Pdb)
> /code/example4unt.py(11)get_path()
-> pass  # Check filename char
(Pdb)
> /code/example4unt.py(12)get_path()
-> return head
(Pdb) p char, tail
('y', 'example4unt.py')

Der Befehlll wurde zuerst verwendet, um die Quelle der Funktion zu drucken, gefolgt vonunt. pdb merkt sich den zuletzt eingegebenen Befehl, also habe ich nur[.kbd .key-enter]#Enter # gedrückt, um den Befehlunt zu wiederholen. Dies setzte die Ausführung durch den Code fort, bis eine Quellzeile erreicht wurde, die größer als die aktuelle Zeile war.

Beachten Sie in der Konsolenausgabe oben, dass pdb in den Zeilen10 und11 nur einmal gestoppt wurde. Daunt verwendet wurde, wurde die Ausführung nur in der ersten Iteration der Schleife gestoppt. Jede Iteration der Schleife wurde jedoch ausgeführt. Dies kann in der letzten Ausgabezeile überprüft werden. Der Wert'y' der Variablenchar entspricht dem letzten Zeichen intail Wert'example4unt.py'.

Ausdrücke anzeigen

Ähnlich wie beim Drucken von Ausdrücken mitp undpp können Sie mit dem Befehldisplay [expression] pdb anweisen, den Wert eines Ausdrucks automatisch anzuzeigen, wenn er sich ändert, wenn die Ausführung stoppt. Verwenden Sie den Befehlundisplay [expression], um einen Anzeigeausdruck zu löschen.

Hier ist die Syntax und Beschreibung für beide Befehle:

Befehl Syntax Beschreibung

display

Anzeige [Ausdruck]

Zeigen Sie den Wert vonexpression an, wenn er sich ändert, jedes Mal, wenn die Ausführung im aktuellen Frame stoppt. Listen Sie ohneexpression alle Anzeigeausdrücke für den aktuellen Frame auf.

undisplay

undisplay [Ausdruck]

Zeigen Sie im aktuellen Frame keineexpression mehr an. Löschen Sie ohneexpression alle Anzeigeausdrücke für den aktuellen Frame.

Unten sehen Sie ein Beispiel,example4display.py, das seine Verwendung mit einer Schleife demonstriert:

$ ./example4display.py
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb)
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'a'  [old: 'x']
(Pdb)
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'm'  [old: 'a']

In der obigen Ausgabe zeigte pdb automatisch den Wert der Variablenchar an, da sich der Wert jedes Mal geändert hatte, wenn der Haltepunkt erreicht wurde. Manchmal ist dies hilfreich und genau das, was Sie möchten, aber es gibt auch eine andere Möglichkeit,display zu verwenden.

Sie könnendisplay mehrmals eingeben, um eine Überwachungsliste mit Ausdrücken zu erstellen. Dies kann einfacher zu verwenden sein alsp. Nachdem Sie alle gewünschten Ausdrücke hinzugefügt haben, geben Sie einfachdisplay ein, um die aktuellen Werte anzuzeigen:

$ ./example4display.py
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) display fname
display fname: './example4display.py'
(Pdb) display head
display head: '.'
(Pdb) display tail
display tail: 'example4display.py'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) display
Currently displaying:
char: 'x'
fname: './example4display.py'
head: '.'
tail: 'example4display.py'

Python-Anrufer-ID

In diesem letzten Abschnitt werden wir auf dem aufbauen, was wir bisher gelernt haben, und mit einer schönen Auszahlung abschließen. Ich verwende den Namen "Anrufer-ID" in Bezug auf die Anruferidentifizierungsfunktion des Telefonsystems. Genau das zeigt dieses Beispiel, außer dass es auf Python angewendet wird.

Hier ist die Quelle für das Hauptskriptexample5.py:

#!/usr/bin/env python3

import fileutil


def get_file_info(full_fname):
    file_path = fileutil.get_path(full_fname)
    return file_path


filename = __file__
filename_path = get_file_info(filename)
print(f'path = {filename_path}')

Hier ist das Dienstprogrammmodulfileutil.py:

def get_path(fname):
    """Return file's path or empty string if no path."""
    import os
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    return head

Stellen Sie sich in diesem Szenario vor, es gibt eine große Codebasis mit einer Funktion in einem Dienstprogrammmodul,get_path(), die mit ungültiger Eingabe aufgerufen wird. Es wird jedoch von vielen Stellen in verschiedenen Paketen aufgerufen.

Wie finden Sie heraus, wer der Anrufer ist?

Verwenden Sie den Befehlw (where), um eine Stapelverfolgung mit dem neuesten Frame unten zu drucken:

$ ./example5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb)

Machen Sie sich keine Sorgen, wenn dies verwirrend aussieht oder Sie nicht sicher sind, was ein Stack-Trace oder Frame ist. Ich werde diese Begriffe unten erläutern. Es ist nicht so schwierig, wie es sich anhört.

Da sich das letzte Bild unten befindet, beginnen Sie dort und lesen Sie von unten nach oben. Schauen Sie sich die Zeilen an, die mit-> beginnen, aber überspringen Sie die erste Instanz, da dortpdb.set_trace() verwendet wurde, um pdb in die Funktionget_path() einzugeben. In diesem Beispiel lautet die Quellzeile, die die Funktionget_path() aufgerufen hat:

-> file_path = fileutil.get_path(full_fname)

Die Zeile über jedem-> enthält den Dateinamen, die Zeilennummer (in Klammern) und den Funktionsnamen, in dem sich die Quellzeile befindet. Der Anrufer ist also:

  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

Dies ist in diesem kleinen Beispiel zu Demonstrationszwecken keine Überraschung. Stellen Sie sich jedoch eine große Anwendung vor, in der Sie einen Haltepunkt mit einer Bedingung festgelegt haben, um festzustellen, woher ein schlechter Eingabewert stammt.

Jetzt wissen wir, wie wir den Anrufer finden.

Aber was ist mit diesem Stack-Trace- und Frame-Zeug?

Ein Stack-Trace ist nur eine Liste aller Frames, die Python erstellt hat, um Funktionsaufrufe zu verfolgen. Ein Frame ist eine Datenstruktur, die Python beim Aufruf einer Funktion erstellt und bei der Rückgabe löscht. Der Stapel ist einfach eine geordnete Liste von Frames oder Funktionsaufrufen zu jedem Zeitpunkt. Der (Funktionsaufruf-) Stapel wächst und schrumpft während der gesamten Lebensdauer einer Anwendung, wenn Funktionen aufgerufen werden und dann zurückkehren.

Beim Drucken wird diese geordnete Liste von Frames, der Stapel, als Stapelverfolgung bezeichnet. Sie können es jederzeit sehen, indem Sie den Befehlw eingeben, wie oben beschrieben, um den Anrufer zu finden.

Siehe diesecall stack article on Wikipedia für Details.

Um besser zu verstehen und mehr aus pdb herauszuholen, schauen wir uns die Hilfe fürw genauer an:

(Pdb) h w
w(here)
        Print a stack trace, with the most recent frame at the bottom.
        An arrow indicates the "current frame", which determines the
        context of most commands. 'bt' is an alias for this command.

Was bedeutet pdb unter "aktueller Frame"?

Stellen Sie sich den aktuellen Frame als die aktuelle Funktion vor, bei der pdb die Ausführung gestoppt hat. Mit anderen Worten, im aktuellen Frame wird Ihre Anwendung gerade angehalten und als Referenzrahmen für PDF-Befehle wiep (Drucken) verwendet.

p und andere Befehle verwenden bei Bedarf den aktuellen Frame für den Kontext. Im Fall vonp wird der aktuelle Frame zum Nachschlagen und Drucken von Variablenreferenzen verwendet.

Wenn pdb eine Stapelverfolgung druckt, zeigt ein Pfeil> den aktuellen Frame an.

Wie ist das nützlich?

Mit den beiden Befehlenu (aufwärts) undd (abwärts) können Sie den aktuellen Frame ändern. In Kombination mitp können Sie so Variablen und Status in Ihrer Anwendung an jedem Punkt entlang des Aufrufstapels in einem beliebigen Frame überprüfen.

Hier ist die Syntax und Beschreibung für beide Befehle:

Befehl Syntax Beschreibung

u

u (p) [Anzahl]

Verschieben Sie die Ebenen des aktuellen Framescount (Standard 1) in der Stapelverfolgung nach oben (zu einem älteren Frame).

d

d (eigene) [Anzahl]

Verschieben Sie die Ebenen des aktuellen Framescount (Standard 1) in der Stapelverfolgung nach unten (in einen neueren Frame).

Schauen wir uns ein Beispiel mit den Befehlenu undd an. In diesem Szenario möchten wir die Variablefull_fname untersuchen, die für die Funktionget_file_info() inexample5.py lokal ist. Dazu müssen wir den aktuellen Frame mit dem Befehlu um eine Ebene nach oben ändern:

$ ./example5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
(Pdb) p full_fname
'./example5.py'
(Pdb) d
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) p fname
'./example5.py'
(Pdb)

Der Aufruf vonpdb.set_trace() erfolgt infileutil.py in der Funktionget_path(), sodass der aktuelle Frame zunächst dort gesetzt wird. Sie können es in der ersten Ausgabezeile oben sehen:

> /code/fileutil.py(5)get_path()

Um auf die lokale Variablefull_fname in der Funktionget_file_info() inexample5.py zuzugreifen und diese zu drucken, wurde der Befehlu verwendet, um eine Ebene nach oben zu gelangen:

(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

Beachten Sie in der Ausgabe vonu darüber, dass pdb den Pfeil> am Anfang der 1. Zeile druckte. Dies ist eine PDF-Datei, die Sie darüber informiert, dass der Frame geändert wurde und dieser Quellspeicherort jetzt der aktuelle Frame ist. Auf die Variablefull_fname kann jetzt zugegriffen werden. Es ist auch wichtig zu wissen, dass die Quellzeile, die mit-> in der 2. Zeile beginnt, ausgeführt wurde. Seit der Frame den Stapel nach oben verschoben wurde, wurdefileutil.get_path() aufgerufen. Mitu haben wir den Stapel (gewissermaßen in der Zeit zurück) auf die Funktionexample5.get_file_info() verschoben, bei derfileutil.get_path() aufgerufen wurde.

Wenn Sie mit dem Beispiel fortfahren, wurde nach dem Drucken vonfull_fname der aktuelle Frame mitd an seine ursprüngliche Position verschoben und die lokale Variablefname inget_path() gedruckt.

Wenn wir wollten, hätten wir mehrere Frames gleichzeitig verschieben können, indem wir das Argumentcountanu oderd übergeben hätten. Zum Beispiel hätten wir inexample5.py auf Modulebene wechseln können, indem wiru 2 eingegeben hätten:

$ ./example5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u 2
> /code/example5.py(12)()
-> filename_path = get_file_info(filename)
(Pdb) p filename
'./example5.py'
(Pdb)

Es ist leicht zu vergessen, wo Sie sich befinden, wenn Sie debuggen und an viele verschiedene Dinge denken. Denken Sie daran, dass Sie immer den treffend benannten Befehlw (where) verwenden können, um zu sehen, wo die Ausführung angehalten wird und wie der aktuelle Frame lautet.

Wichtige pdb-Befehle

Wenn Sie ein wenig Zeit mit pdb verbracht haben, werden Sie feststellen, dass ein wenig Wissen einen langen Weg zurücklegt. Mit dem Befehlh ist immer Hilfe verfügbar.

Geben Sie einfachh oderhelp <topic> ein, um eine Liste aller Befehle oder Hilfen für einen bestimmten Befehl oder ein bestimmtes Thema zu erhalten.

Zum schnellen Nachschlagen finden Sie hier eine Liste der wichtigsten Befehle:

Befehl Beschreibung

p

Drucken Sie den Wert eines Ausdrucks.

pp

Drucken Sie den Wert eines Ausdrucks hübsch aus.

n

Setzen Sie die Ausführung fort, bis die nächste Zeile in der aktuellen Funktion erreicht ist oder zurückkehrt.

s

Führen Sie die aktuelle Zeile aus und stoppen Sie bei der ersten möglichen Gelegenheit (entweder in einer aufgerufenen Funktion oder in der aktuellen Funktion).

c

Setzen Sie die Ausführung fort und stoppen Sie nur, wenn ein Haltepunkt festgestellt wird.

unt

Setzen Sie die Ausführung fort, bis die Zeile mit einer Nummer größer als der aktuellen erreicht ist. Setzen Sie die Ausführung mit einem Zeilennummernargument fort, bis eine Zeile mit einer Nummer größer oder gleich dieser erreicht ist.

l

Listen Sie den Quellcode für die aktuelle Datei auf. Listen Sie ohne Argumente 11 Zeilen um die aktuelle Zeile auf oder setzen Sie die vorherige Auflistung fort.

ll

Listen Sie den gesamten Quellcode für die aktuelle Funktion oder den aktuellen Frame auf.

b

Listen Sie ohne Argumente alle Pausen auf. Setzen Sie mit einem Zeilennummernargument einen Haltepunkt an dieser Zeile in der aktuellen Datei.

w

Drucken Sie eine Stapelverfolgung mit dem neuesten Frame unten. Ein Pfeil zeigt den aktuellen Frame an, der den Kontext der meisten Befehle bestimmt.

u

Verschieben Sie die aktuelle Frame-Anzahl (Standardeinstellung) in der Stapelverfolgung nach oben (auf einen älteren Frame).

d

Verschieben Sie die aktuelle Frame-Anzahl (Standardeinstellung) in der Stapelverfolgung nach unten (auf einen neueren Frame).

h

Eine Liste der verfügbaren Befehle anzeigen.

h

Hilfe für einen Befehl oder ein Thema anzeigen.

h pdb

Zeigen Sie die vollständige PDF-Dokumentation an.

q

Beenden Sie den Debugger und beenden Sie ihn.

Python-Debugging mit pdb: Fazit

In diesem Tutorial haben wir einige grundlegende und häufige Verwendungen von pdb behandelt:

  • Ausdrücke drucken

  • Durchlaufen des Codes mitn (weiter) unds (Schritt)

  • mit Haltepunkten

  • Fortsetzung der Ausführung mitunt (bis)

  • Ausdrücke anzeigen

  • den Aufrufer einer Funktion finden

Ich hoffe, es war hilfreich für Sie. Wenn Sie mehr erfahren möchten, lesen Sie:

  • Die vollständige Dokumentation von pdb an einer pdb-Eingabeaufforderung in Ihrer Nähe:(Pdb) h pdb

  • Pythons PDF-Dokumente

Der in den Beispielen verwendete Quellcode befindet sich auf den zugehörigenGitHub repository. Lesen Sie unbedingt unsere druckbare pdb-Befehlsreferenz, die Sie beim Debuggen als Spickzettel verwenden können:

Free Bonus:Click here to get a printable "pdb Command Reference" (PDF), auf die Sie beim Debuggen auf Ihrem Schreibtisch zugreifen können.

Wenn Sie einen GUI-basierten Python-Debugger ausprobieren möchten, lesen Sie unserePython IDEs and Editors Guide, um herauszufinden, welche Optionen für Sie am besten geeignet sind. Viel Spaß beim Pythoning!