Website-Suche

Wie man den Python-Debugger verwendet


Einführung

In der Softwareentwicklung ist Debugging der Prozess, nach Problemen zu suchen und diese zu beheben, die verhindern, dass die Software korrekt ausgeführt wird.

Der Python-Debugger bietet eine Debugging-Umgebung für Python-Programme. Er unterstützt das Setzen von bedingten Haltepunkten, das Schritt-für-Schritt-Durchgehen des Quellcodes, die Stack-Inspektion und mehr.

Voraussetzungen

Sie sollten Python 3 installiert und eine Programmierumgebung auf Ihrem Computer oder Server eingerichtet haben. Wenn Sie keine Programmierumgebung eingerichtet haben, können Sie auf die Installations- und Einrichtungsanleitungen für eine lokale Programmierumgebung oder für eine Programmierumgebung auf Ihrem Server verweisen, die für Ihr Betriebssystem (Ubuntu, CentOS, Debian usw.) geeignet ist.

Interaktiv mit dem Python-Debugger arbeiten

Der Python-Debugger ist Teil der standardmäßigen Python-Distribution und wird als Modul namens pdb bereitgestellt. Der Debugger ist auch erweiterbar und wird als Klasse Pdb definiert. Sie können die offizielle Dokumentation von pdb lesen, um mehr zu erfahren.

Info: Um dem Beispielcode in diesem Tutorial zu folgen, öffnen Sie eine Python-interaktive Shell auf Ihrem lokalen System, indem Sie den Befehl python3 ausführen. Dann können Sie die Beispiele kopieren, einfügen oder bearbeiten, indem Sie sie nach der >>> Eingabeaufforderung hinzufügen.

Wir werden beginnen, indem wir mit einem kurzen Programm arbeiten, das zwei globale Variablen, eine Funktion, die eine geschachtelte Schleife erzeugt, und die Konstruktion if __name__ == '__main__': hat, die die Funktion nested_loop() aufruft.

num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']


def nested_loop():
    for number in num_list:
        print(number)
        for letter in alpha_list:
            print(letter)

if __name__ == '__main__':
    nested_loop()

Wir können dieses Programm jetzt mit dem Python-Debugger durch den folgenden Befehl ausführen:

  1. python -m pdb looping.py

Die -m Befehlszeilenoption importiert ein beliebiges Python-Modul für Sie und führt es als Skript aus. In diesem Fall importieren und führen wir das pdb Modul aus, das wir wie oben gezeigt in den Befehl übergeben.

Beim Ausführen dieses Befehls erhalten Sie folgende Ausgabe:

> /Users/sammy/looping.py(1)<module>()
-> num_list = [500, 600, 700]
(Pdb) 

In der Ausgabe enthält die erste Zeile den aktuellen Modulnamen (wie mit <module> angegeben) mit einem Verzeichnis-Pfad und die folgende Zeilennummer (in diesem Fall ist es 1, aber wenn es einen Kommentar oder eine andere nicht ausführbare Zeile gibt, könnte es eine höhere Zahl sein). Die zweite Zeile zeigt die aktuelle Zeile des Quellcodes, die hier ausgeführt wird, da pdb eine interaktive Konsole zum Debuggen bereitstellt. Sie können den Befehl help verwenden, um die Befehle zu lernen, und help command, um mehr über einen bestimmten Befehl zu erfahren. Beachten Sie, dass die pdb Konsole anders ist als die interaktive Python-Shell.

Der Python-Debugger wird automatisch neu gestartet, wenn er das Ende Ihres Programms erreicht. Immer wenn Sie die pdb-Konsole verlassen möchten, geben Sie den Befehl quit oder exit ein. Wenn Sie ein Programm an einer beliebigen Stelle innerhalb des Programms explizit neu starten möchten, können Sie dies mit dem Befehl run tun.

Den Debugger verwenden, um durch ein Programm zu navigieren

Wenn Sie mit Programmen im Python-Debugger arbeiten, werden Sie wahrscheinlich die Befehle list, step und next verwenden, um durch Ihren Code zu navigieren. Wir werden diese Befehle in diesem Abschnitt durchgehen.

Innerhalb der Shell können wir den Befehl list eingeben, um den Kontext der aktuellen Zeile zu erhalten. Von der ersten Zeile des Programms looping.py, die wir oben angezeigt haben — num_list=[500, 600, 700] — wird das wie folgt aussehen:

(Pdb) list
  1  ->	num_list = [500, 600, 700]
  2  	alpha_list = ['x', 'y', 'z']
  3  	
  4  	
  5  	def nested_loop():
  6  	    for number in num_list:
  7  	        print(number)
  8  	        for letter in alpha_list:
  9  	            print(letter)
 10  	
 11  	if __name__ == '__main__':
(Pdb) 

Die aktuelle Zeile wird mit den Zeichen -> angezeigt, was in unserem Fall die erste Zeile der Programmdatei ist.

Da dies ein relativ kurzes Programm ist, erhalten wir fast das gesamte Programm mit dem Befehl list zurück. Ohne Argumente liefert der Befehl list 11 Zeilen um die aktuelle Zeile herum, aber Sie können auch angeben, welche Zeilen einbezogen werden sollen, so:

(Pdb) list 3, 7
  3  	
  4  	
  5  	def nested_loop():
  6  	    for number in num_list:
  7  	        print(number)
(Pdb) 

Hier haben wir angefordert, dass die Zeilen 3-7 mit dem Befehl list 3, 7 angezeigt werden.

Um das Programm Zeile für Zeile zu durchlaufen, können wir step oder next verwenden:

(Pdb) step
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb) 
(Pdb) next
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb) 

Der Unterschied zwischen step und next besteht darin, dass step innerhalb einer aufgerufenen Funktion stoppt, während next aufgerufene Funktionen ausführt, um nur an der nächsten Zeile der aktuellen Funktion zu stoppen. Wir können diesen Unterschied sehen, wenn wir mit der Funktion arbeiten.

Der step-Befehl wird durch die Schleifen iterieren, sobald er die Ausführung der Funktion erreicht, und genau zeigen, was die Schleife tut. Zuerst wird eine Zahl mit print(number) ausgegeben, dann wird durchgegangen, um die Buchstaben mit print(letter) auszugeben, und anschließend wird zur Zahl zurückgekehrt, usw:

(Pdb) step
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) step
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) step
--Call--
> /Users/sammy/looping.py(5)nested_loop()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(6)nested_loop()
-> for number in num_list:
(Pdb) step
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) step
500
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
x
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
y
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb)

Der next-Befehl hingegen wird die gesamte Funktion ausführen, ohne den Schritt-für-Schritt-Prozess anzuzeigen. Lassen Sie uns die aktuelle Sitzung mit dem exit-Befehl beenden und dann den Debugger erneut starten:

  1. python -m pdb looping.py

Jetzt können wir mit dem next-Befehl arbeiten:

(Pdb) next
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) next
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) next
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) next
500
x
y
z
600
x
y
z
700
x
y
z
--Return--
> /Users/sammy/looping.py(12)<module>()->None
-> nested_loop()
(Pdb)  

Während Sie Ihren Code durchgehen, möchten Sie möglicherweise den Wert einer Variablen überprüfen, was Sie mit dem pp-Befehl tun können, der den Wert des Ausdrucks unter Verwendung des pprint-Moduls schön ausgibt:

(Pdb) pp num_list
[500, 600, 700]
(Pdb) 

Die meisten Befehle in pdb haben kürzere Aliase. Für step ist diese Kurzform s, und für next ist es n. Der Befehl help listet die verfügbaren Aliase auf. Sie können auch den letzten Befehl, den Sie eingegeben haben, aufrufen, indem Sie die ENTER-Taste an der Eingabeaufforderung drücken.

Haltepunkte

In der Regel werden Sie mit größeren Programmen als dem oben genannten arbeiten, daher werden Sie wahrscheinlich bestimmte Funktionen oder Zeilen betrachten wollen, anstatt ein ganzes Programm durchzugehen. Indem Sie den break-Befehl verwenden, um Haltepunkte zu setzen, führen Sie das Programm bis zum angegebenen Haltepunkt aus.

Wenn Sie einen Haltepunkt einfügen, weist der Debugger ihm eine Nummer zu. Die den Haltepunkten zugewiesenen Nummern sind aufeinanderfolgende ganze Zahlen, die mit der Nummer 1 beginnen, auf die Sie sich beziehen können, wenn Sie mit Haltepunkten arbeiten.

Haltepunkte können an bestimmten Zeilennummern gesetzt werden, indem die Syntax von <program_file>:<line_number> wie im Folgenden gezeigt befolgt wird:

(Pdb) break looping.py:5
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb)

Geben Sie clear ein und drücken Sie dann y, um alle aktuellen Haltepunkte zu entfernen. Sie können dann einen Haltepunkt dort setzen, wo eine Funktion definiert ist:

(Pdb) break looping.nested_loop
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb) 

Um die aktuellen Haltepunkte zu entfernen, geben Sie clear ein und drücken dann y. Sie können auch eine Bedingung festlegen:

(Pdb) break looping.py:7, number > 500
Breakpoint 1 at /Users/sammy/looping.py:7
(Pdb)     

Jetzt, wenn wir den Befehl continue ausgeben, wird das Programm anhalten, wenn die number x als größer als 500 ausgewertet wird (d. h. wenn sie in der zweiten Iteration der äußeren Schleife auf 600 gesetzt wird):

(Pdb) continue
500
x
y
z
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) 

Um eine Liste der Breakpoints anzuzeigen, die derzeit zum Ausführen festgelegt sind, verwenden Sie den Befehl break ohne Argumente. Sie erhalten Informationen über die Besonderheiten des/der von Ihnen gesetzten Breakpoints:

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/sammy/looping.py:7
	stop only if number > 500
	breakpoint already hit 2 times
(Pdb) 

Wir können auch einen Haltepunkt mit dem Befehl disable und der Nummer des Haltepunkts deaktivieren. In dieser Sitzung fügen wir einen weiteren Haltepunkt hinzu und deaktivieren dann den ersten:

(Pdb) break looping.py:11
Breakpoint 2 at /Users/sammy/looping.py:11
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /Users/sammy/looping.py:7
	stop only if number > 500
	breakpoint already hit 2 times
2   breakpoint   keep yes   at /Users/sammy/looping.py:11
(Pdb) 

Um einen Haltepunkt zu aktivieren, verwenden Sie den Befehl enable, und um einen Haltepunkt vollständig zu entfernen, verwenden Sie den Befehl clear:

(Pdb) enable 1
Enabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) clear 2
Deleted breakpoint 2 at /Users/sammy/looping.py:11
(Pdb) 

Breakpoints in pdb geben Ihnen viel Kontrolle. Zu den zusätzlichen Funktionen gehört das Ignorieren von Breakpoints während der aktuellen Iteration des Programms mit dem Befehl ignore (wie in ignore 1), das Auslösen von Aktionen an einem Breakpoint mit dem Befehl commands (wie in command 1) und das Erstellen temporärer Breakpoints, die beim ersten Erreichen des Punktes durch die Programmausführung automatisch gelöscht werden mit dem Befehl tbreak (zum Beispiel für einen temporären Breakpoint in Zeile 3 könnten Sie tbreak 3 eingeben).

Integration von pdb in Programme

Sie können eine Debugging-Sitzung auslösen, indem Sie das pdb-Modul importieren und die pdb-Funktion pdb.set_trace() oberhalb der Zeile hinzufügen, an der die Sitzung beginnen soll.

In unserem Beispielprogramm oben fügen wir die import-Anweisung und die Funktion hinzu, in die wir in den Debugger eintreten möchten. Für unser Beispiel fügen wir es vor der geschachtelten Schleife hinzu.

Import pdb module
import pdb

num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']


def nested_loop():
    for number in num_list:
        print(number)

        # Trigger debugger at this line
        pdb.set_trace()
        for letter in alpha_list:
            print(letter)

if __name__ == '__main__':
    nested_loop()

Indem Sie den Debugger in Ihren Code einfügen, müssen Sie Ihr Programm nicht auf eine besondere Weise starten oder daran denken, Haltepunkte zu setzen.

Das Importieren des pdb-Moduls und das Ausführen der Funktion pdb.set_trace() ermöglicht es Ihnen, Ihr Programm wie gewohnt zu starten und den Debugger während der Ausführung durchzuführen.

Ändern des Programmablaufs

Der Python-Debugger ermöglicht es Ihnen, den Fluss Ihres Programms zur Laufzeit mit dem jump-Befehl zu ändern. Dies ermöglicht es Ihnen, vorwärts zu springen, um zu verhindern, dass ein bestimmter Code ausgeführt wird, oder zurückzugehen, um den Code erneut auszuführen.

Wir werden mit einem kleinen Programm arbeiten, das eine Liste der Buchstaben erstellt, die in der Zeichenkette sammy="sammy" enthalten sind:

def print_sammy():
    sammy_list = []
    sammy = "sammy"
    for letter in sammy:
        sammy_list.append(letter)
        print(sammy_list)

if __name__ == "__main__":
    print_sammy()

Wenn wir das Programm wie gewohnt mit dem Befehl python letter_list.py ausführen, erhalten wir die folgende Ausgabe:

['s']
['s', 'a']
['s', 'a', 'm']
['s', 'a', 'm', 'm']
['s', 'a', 'm', 'm', 'y']

Mit dem Python-Debugger wollen wir zeigen, wie wir die Ausführung ändern können, indem wir zunächst voraus springen nach dem ersten Zyklus. Wenn wir das tun, werden wir feststellen, dass es eine Unterbrechung der for-Schleife gibt:

  1. python -m pdb letter_list.py
> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
  1  ->	def print_sammy():
  2  	    sammy_list = []
  3  	    sammy = "sammy"
  4  	    for letter in sammy:
  5  	        sammy_list.append(letter)
  6  	        print(sammy_list)
  7  	
  8  	if __name__ == "__main__":
  9  	    print_sammy()
 10  	
 11  	
(Pdb) break 5
Breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) pp letter
's'
(Pdb) continue
['s']
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) jump 6
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
'a'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
['s']
['s', 'm']
['s', 'm', 'm']
['s', 'm', 'm', 'y']

Die obige Debugging-Sitzung setzt einen Haltepunkt in Zeile 5, um zu verhindern, dass der Code fortgesetzt wird, und fährt dann mit dem Code fort (nebst dem hübschen Drucken einiger Werte von letter, um zu zeigen, was passiert). Als Nächstes verwenden wir den Befehl jump, um zu Zeile 6 zu springen. An diesem Punkt ist die Variable letter auf den String 'a' gesetzt, aber wir überspringen den Code, der ihn zur Liste sammy_list hinzufügt. Dann deaktivieren wir den Haltepunkt, um die Ausführung wie gewohnt mit dem Befehl continue fortzusetzen, sodass 'a' niemals zu sammy_list hinzugefügt wird.

Dann können wir diese erste Sitzung beenden und den Debugger neu starten, um innerhalb des Programms zurückzuspringen und eine Anweisung, die bereits ausgeführt wurde, erneut auszuführen. Diesmal werden wir die erste Iteration der for-Schleife im Debugger erneut ausführen:

> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
  1  ->	def print_sammy():
  2  	    sammy_list = []
  3  	    sammy = "sammy"
  4  	    for letter in sammy:
  5  	        sammy_list.append(letter)
  6  	        print(sammy_list)
  7  	
  8  	if __name__ == "__main__":
  9  	    print_sammy()
 10  	
 11  	
(Pdb) break 6
Breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) jump 5
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
['s', 's']
['s', 's', 'a']
['s', 's', 'a', 'm']
['s', 's', 'a', 'm', 'm']
['s', 's', 'a', 'm', 'm', 'y']

In der oben genannten Debugging-Sitzung haben wir einen Haltepunkt bei Zeile 6 gesetzt und sind dann nach dem Fortfahren zurück zu Zeile 5 gesprungen. Wir haben währenddessen die Ausgaben ordentlich formatiert, um zu zeigen, dass der String 's' zweimal zur Liste sammy_list hinzugefügt wurde. Dann haben wir den Haltepunkt bei Zeile 6 deaktiviert und das Programm weiter ausgeführt. Die Ausgabe zeigt zwei Werte von 's', die zu sammy_list hinzugefügt wurden.

Einige Sprünge werden vom Debugger verhindert, insbesondere beim Ein- und Aussteigen in und aus bestimmten Kontrollflussanweisungen, die undefiniert sind. Zum Beispiel können Sie nicht in Funktionen springen, bevor die Argumente definiert sind, und Sie können nicht mitten in einer try:except-Anweisung springen. Außerdem können Sie nicht aus einem finally-Block springen.

Die jump-Anweisung mit dem Python-Debugger ermöglicht es Ihnen, den Ausführungsfluss während des Debuggens eines Programms zu ändern, um zu sehen, ob der Kontrollfluss für verschiedene Zwecke modifiziert werden kann oder um besser zu verstehen, welche Probleme in Ihrem Code auftreten.

Tabelle der gängigen pdb-Befehle

Hier ist eine Tabelle nützlicher pdb Befehle zusammen mit ihren Kurzformen, die man beim Arbeiten mit dem Python-Debugger im Hinterkopf behalten sollte.

BefehlKurzformWas es tut
args
a
Drucken Sie die Argumentliste der aktuellen Funktion
break
b
Setzt einen Haltepunkt (benötigt Parameter) in der Programmausführung.
continue
c or cont
Fährt mit der Programmausführung fort
help
h
Bietet eine Liste von Befehlen oder Hilfe für einen bestimmten Befehl
jump
j
Setze die nächste Zeile, die ausgeführt werden soll.
list
l
Drucken Sie den Quellcode um die aktuelle Zeile herum
next
n
Fahre mit der Ausführung fort, bis die nächste Zeile in der aktuellen Funktion erreicht ist oder zurückgegeben wird.
step
s
Führen Sie die aktuelle Zeile aus und halten Sie an der ersten möglichen Gelegenheit an.
pp
pp
Schön formatiert den Wert des Ausdrucks
quit or exit
q
Beendet das Programm
return
r
Setzen Sie die Ausführung fort, bis die aktuelle Funktion zurückkehrt.

Sie können mehr über die Befehle und die Arbeit mit dem Debugger in der Dokumentation des Python-Debuggers nachlesen.

Schlussfolgerung

Debugging ist ein wichtiger Schritt in jedem Softwareentwicklungsprojekt. Der Python-Debugger pdb implementiert eine interaktive Debugging-Umgebung, die Sie mit jedem Ihrer in Python geschriebenen Programme verwenden können.

Mit Funktionen, die es Ihnen ermöglichen, Ihr Programm anzuhalten, zu sehen, auf welchen Wert Ihre Variablen gesetzt sind, und die Programmausführung Schritt für Schritt durchzugehen, können Sie besser verstehen, was Ihr Programm tut, und Fehler in der Logik finden oder bekannte Probleme beheben.