웹사이트 검색

Python 디버거를 사용하는 방법


소개

소프트웨어 개발에서 디버깅은 소프트웨어가 올바르게 실행되지 않도록 하는 문제를 찾아서 해결하는 프로세스입니다.

Python 디버거는 Python 프로그램을 위한 디버깅 환경을 제공합니다. 조건부 중단점 설정, 한 번에 한 줄씩 소스 코드 단계별 실행, 스택 검사 등을 지원합니다.

전제 조건

컴퓨터나 서버에 Python 3이 설치되어 있고 프로그래밍 환경이 설정되어 있어야 합니다. 프로그래밍 환경이 설정되지 않은 경우 운영 체제(Ubuntu, CentOS, Debian 등)에 적합한 서버의 프로그래밍 환경에 대한 설치 및 설정 가이드를 참조할 수 있습니다.

Python 디버거와 대화식으로 작업

Python 디버거는 pdb라는 모듈로 표준 Python 배포판의 일부로 제공됩니다. 디버거도 확장 가능하며 Pdb 클래스로 정의됩니다. 자세한 내용은 pdb의 공식 문서를 참조하세요.

정보: 이 자습서의 예제 코드를 따라하려면 python3 명령을 실행하여 로컬 시스템에서 Python 대화형 셸을 엽니다. 그런 다음 >>> 프롬프트 뒤에 추가하여 예제를 복사, 붙여넣기 또는 편집할 수 있습니다.

두 개의 전역 루프가 있는 짧은 프로그램과 nested_loop() 함수를 호출하는 if __name__ == __main__: 구조로 작업하는 것으로 시작하겠습니다.

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

이제 다음 명령을 사용하여 Python 디버거를 통해 이 프로그램을 실행할 수 있습니다.

  1. python -m pdb looping.py

-m 명령줄 플래그는 Python 모듈을 가져와서 스크립트로 실행합니다. 이 경우 위에 표시된 대로 명령에 전달하는 pdb 모듈을 가져와서 실행합니다.

이 명령을 실행하면 다음과 같은 결과가 표시됩니다.

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

출력에서 첫 번째 줄에는 디렉터리 경로와 함께 현재 모듈 이름(<module>로 표시됨)과 뒤따르는 인쇄된 줄 번호(이 경우 1이지만 주석이나 기타 실행 불가능한 줄이 있는 경우 더 높은 숫자가 될 수 있습니다.) 두 번째 줄은 여기서 실행되는 소스 코드의 현재 줄을 보여줍니다. pdb는 디버깅을 위한 대화형 콘솔을 제공하기 때문입니다. help 명령을 사용하여 명령을 배우고 help 명령을 사용하여 특정 명령에 대해 자세히 알아볼 수 있습니다. pdb 콘솔은 Python 대화형 셸과 다릅니다.

Python 디버거는 프로그램 끝에 도달하면 자동으로 다시 시작됩니다. pdb 콘솔을 나가고 싶을 때마다 quit 또는 exit 명령을 입력하십시오. 프로그램 내의 어느 위치에서든 프로그램을 명시적으로 다시 시작하려면 run 명령을 사용하면 됩니다.

디버거를 사용하여 프로그램 이동

Python 디버거에서 프로그램으로 작업할 때 list, stepnext 명령을 사용하여 코드를 이동할 수 있습니다. 이 섹션에서 이러한 명령을 살펴보겠습니다.

셸 내에서 list 명령을 입력하여 현재 줄 주변의 컨텍스트를 얻을 수 있습니다. 위에서 표시한 프로그램 looping.py의 첫 번째 줄( num_list = [500, 600, 700])에서 다음과 같이 표시됩니다.

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

현재 줄은 -> 문자로 표시되며 이 경우 프로그램 파일의 첫 번째 줄입니다.

이것은 상대적으로 짧은 프로그램이므로 list 명령으로 거의 모든 프로그램을 다시 받습니다. 인수를 제공하지 않고 list 명령은 현재 줄 주위에 11줄을 제공하지만 다음과 같이 포함할 줄을 지정할 수도 있습니다.

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

여기에서 list 3, 7 명령을 사용하여 3-7행을 표시하도록 요청했습니다.

프로그램을 한 줄씩 이동하려면 step 또는 next를 사용할 수 있습니다.

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

stepnext의 차이점은 step은 호출된 함수 내에서 중지되는 반면 next는 호출된 함수를 실행하여 현재 함수의 다음 줄에서만 중지합니다. 함수로 작업할 때 이 차이를 볼 수 있습니다.

step 명령은 함수 실행에 도달하면 루프를 반복하여 루프가 수행하는 작업을 정확하게 보여줍니다. 먼저 print(number) 를 사용하여 숫자를 인쇄합니다. 그런 다음 print(letter)로 문자를 인쇄하고 숫자로 돌아가는 등의 작업을 수행합니다.

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

대신 next 명령은 단계별 프로세스를 표시하지 않고 전체 기능을 실행합니다. exit 명령으로 현재 세션을 종료한 다음 디버거를 다시 시작하겠습니다.

  1. python -m pdb looping.py

이제 next 명령으로 작업할 수 있습니다.

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

코드를 살펴보는 동안 변수에 전달된 값을 검사할 수 있습니다. pp 명령으로 수행할 수 있습니다. 이 작업은 pprint를 사용하여 표현식 값을 예쁘게 인쇄합니다. 모듈:

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

pdb에 있는 대부분의 명령은 별칭이 더 짧습니다. 단계의 경우 짧은 형식은 s이고 다음의 경우 n입니다. help 명령은 사용 가능한 별칭을 나열합니다. 프롬프트에서 ENTER 키를 눌러 호출한 마지막 명령을 호출할 수도 있습니다.

중단점

일반적으로 위의 예보다 더 큰 프로그램으로 작업할 것이므로 전체 프로그램을 살펴보는 것보다 특정 기능이나 라인을 살펴보고 싶을 것입니다. break 명령을 사용하여 중단점을 설정하면 지정된 중단점까지 프로그램이 실행됩니다.

중단점을 삽입하면 디버거가 중단점에 번호를 할당합니다. 중단점에 할당된 번호는 중단점 작업 시 참조할 수 있는 숫자 1로 시작하는 연속된 정수입니다.

다음과 같이 : 구문에 따라 특정 줄 번호에 중단점을 배치할 수 있습니다.

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

clear를 입력한 다음 y를 입력하여 현재 중단점을 모두 제거합니다. 그런 다음 함수가 정의된 곳에 중단점을 배치할 수 있습니다.

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

현재 중단점을 제거하려면 clear를 입력한 다음 y를 입력합니다. 조건을 설정할 수도 있습니다.

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

이제 continue 명령을 실행하면 number x가 500보다 큰 것으로 평가될 때(즉, 외부 루프의 두 번째 반복에서 600과 동일하게 설정됨):

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

현재 실행하도록 설정된 중단점 목록을 보려면 인수 없이 break 명령을 사용하십시오. 설정한 중단점의 특성에 대한 정보를 받게 됩니다.

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

disable 명령과 중단점 번호를 사용하여 중단점을 비활성화할 수도 있습니다. 이 세션에서는 다른 중단점을 추가한 다음 첫 번째 중단점을 비활성화합니다.

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

중단점을 활성화하려면 enable 명령을 사용하고 중단점을 완전히 제거하려면 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) 

pdb의 중단점은 많은 제어 기능을 제공합니다. 일부 추가 기능에는 ignore 명령(ignore 1에서와 같이)을 사용하여 프로그램의 현재 반복 중에 중단점을 무시하고 commands 명령(command 1에서와 같이) 및 프로그램 실행이 처음으로 tbreak 명령으로 해당 지점에 도달하면 자동으로 지워지는 임시 중단점 생성(예: 예를 들어 tbreak 3를 입력할 수 있습니다.

프로그램에 pdb 통합

pdb 모듈을 가져오고 원하는 행 위에 pdb 함수 pdb.set_trace()를 추가하여 디버깅 세션을 트리거할 수 있습니다. 시작할 세션.

위의 샘플 프로그램에서 import 문과 디버거에 입력하려는 함수를 추가합니다. 이 예에서는 중첩 루프 앞에 추가해 보겠습니다.

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

디버거를 코드에 추가하면 특별한 방법으로 프로그램을 시작하거나 중단점을 설정하는 것을 기억할 필요가 없습니다.

pdb 모듈을 가져오고 pdb.set_trace() 함수를 실행하면 평소처럼 프로그램을 시작하고 실행을 통해 디버거를 실행할 수 있습니다.

프로그램 실행 흐름 수정

Python 디버거를 사용하면 jump 명령을 사용하여 런타임 시 프로그램 흐름을 변경할 수 있습니다. 이를 통해 앞으로 건너뛰어 일부 코드가 실행되지 않도록 하거나 뒤로 이동하여 코드를 다시 실행할 수 있습니다.

우리는 문자열 sammy = \sammy\에 포함된 문자 목록을 생성하는 작은 프로그램으로 작업할 것입니다.

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

if __name__ == "__main__":
    print_sammy()

평소와 같이 python letter_list.py 명령을 사용하여 프로그램을 실행하면 다음과 같은 결과가 표시됩니다.

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

Python 디버거를 사용하여 첫 번째 주기 후에 먼저 점프하여 실행을 변경하는 방법을 보여드리겠습니다. 이렇게 하면 for 루프가 중단된다는 것을 알 수 있습니다.

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

위의 디버깅 세션은 코드가 계속되는 것을 방지하기 위해 5행에서 중단한 다음 코드를 통해 계속됩니다(어떤 일이 일어나고 있는지 보여주기 위해 문자의 일부 값을 예쁘게 인쇄함과 함께). 다음으로 jump 명령을 사용하여 6행으로 건너뜁니다. 이 시점에서 변수 letter는 문자열 a와 동일하게 설정되지만 목록 sammy_list에 추가하는 코드를 건너뜁니다. 그런 다음 중단점을 비활성화하여 continue 명령으로 평소와 같이 실행을 진행하므로 asammy_list에 추가되지 않습니다.

다음으로 이 첫 번째 세션을 종료하고 디버거를 다시 시작하여 이미 실행된 명령문을 다시 실행하기 위해 프로그램 내에서 다시 이동할 수 있습니다. 이번에는 디버거에서 for 루프의 첫 번째 반복을 다시 실행합니다.

> /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']

위의 디버깅 세션에서 6행에 중단을 추가한 다음 계속한 후 5행으로 다시 이동했습니다. 우리는 s 문자열이 sammy_list 목록에 두 번 추가되었음을 보여주기 위해 예쁘게 인쇄했습니다. 그런 다음 6행에서 중단을 비활성화하고 프로그램을 계속 실행했습니다. 출력에는 sammy_list에 추가된 두 개의 s 값이 표시됩니다.

일부 점프는 특히 정의되지 않은 특정 흐름 제어 문으로 점프할 때 디버거에 의해 방지됩니다. 예를 들어 인수가 정의되기 전에 함수로 이동할 수 없으며 try:except 문의 중간으로 이동할 수 없습니다. 또한 finally 블록 밖으로 이동할 수 없습니다.

Python 디버거의 jump 문을 사용하면 프로그램을 디버깅하는 동안 실행 흐름을 변경하여 흐름 제어를 다른 목적으로 수정할 수 있는지 확인하거나 코드에서 발생하는 문제를 더 잘 이해할 수 있습니다.

일반적인 pdb 명령 표

다음은 Python 디버거로 작업하는 동안 염두에 두어야 할 짧은 형식과 함께 유용한 pdb 명령의 표입니다.

Command Short form What it does
args a Print the argument list of the current function
break b Creates a breakpoint (requires parameters) in the program execution
continue c or cont Continues program execution
help h Provides list of commands or help for a specified command
jump j Set the next line to be executed
list l Print the source code around the current line
next n Continue execution until the next line in the current function is reached or returns
step s Execute the current line, stopping at first possible occasion
pp pp Pretty-prints the value of the expression
quit or exit q Aborts the program
return r Continue execution until the current function returns

명령 및 디버거 작업에 대한 자세한 내용은 Python 디버거 설명서에서 읽을 수 있습니다.

결론

디버깅은 모든 소프트웨어 개발 프로젝트의 중요한 단계입니다. Python 디버거 pdb는 Python으로 작성된 모든 프로그램에서 사용할 수 있는 대화형 디버깅 환경을 구현합니다.

프로그램을 일시 중지하고, 변수가 어떤 값으로 설정되어 있는지 확인하고, 프로그램 실행을 별도의 단계별 방식으로 수행할 수 있는 기능을 통해 프로그램이 수행하는 작업을 보다 완벽하게 이해하고 존재하는 버그를 찾을 수 있습니다. 논리 또는 알려진 문제를 해결합니다.