테스트와 디버깅

About_work·2023년 2월 25일
0

python 기초

목록 보기
24/56

75. 디버깅 출력에는 repr 문자열을 사용하라.

요약

  • 내장 파이썬 타입 값에 대해 print를 호출하면,
    • 해당 값을 사람이 읽을 수 있게 표현한 문자열을 얻는다.
    • 얻은 문자열에서는 타입 정보가 감춰져 있다.
  • 내장 파이썬 타입 값에 대해 repr을 호출하면,
    • 해당 값을 표현하는 출력 가능한 문자열을 얻는다.
    • repr로 얻은 문자열을 eval 내장 함수에 전달하면, 원래 값을 돌려받을 수 있다.
  • 형식화 문자열에서
    • %s: str과 마찬가지로 사람이 읽을 수 있는 문자열 생성
    • str.%r: repr과 마찬가지로 출력 가능한 문자열을 만들어 낸다.
  • f-문자열에서
    • !r 접미사를 붙이지 않고 텍스트 치환식을 사용하면, 사람이 읽을 수 있는 형태의 문자열이 만들어진다.
  • 직접 클래스의 __repr__ special method를 정의해서,
    • 인스턴스의 출력 가능한 표현을 원하는 대로 만들 수 있고,
    • 이를 통해 디버깅할 때 더 자세한 정보를 표시할 수 있다.

본문

my_value = 'foo 뭐시기'
print(str(my_value))
print('%s' % my_value)
print(f'{my_value}')
print(format(my_value))
print(my_value.__format__('s'))
print(my_value.__str__())
>>>
# 전부 똑같은 결과
foo 뭐시기
  • 하지만, 어떤 타입인지, 구체적인 구성은 어떤지 알 수 없다는 점이 단점이다.
  • 디버깅을 할 떄 원하는 문자열은, 거의 대부분 객체를 repr로 나타낸 버전이다.
  • repr
    • 객체의 printable representation을 반환
    • printable representation은 반드시 객체를 가장 명확하게 이해할 수 있는 문자열 표현이어야 한다.
    • ** repr을 호출하는 것은, % 연산자에 %r 형식화 문자열을 사용하는 것이나, f-문자열에 !r 타입 변환을 사용하는 것 과 같다.
print('%r' % 5) # 5
print('%r' % '5') # '5'

int_value = 5
str_value = 5
print(f'{int_value!r} != {str_value!r}')
  • 클래스는 사람이 읽을 수 있는 문자열 값(print의 결과)와 repr의 값이 같다.
  • 클래스에 __repr__을 쓰면 좋다.
class BetterClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'BetterClass({self.x!r}, {self.y!r})'
        
obj = BetterClass(2, '뭐시기')
print(obj)
>>> 
BetterClass(2, '뭐시기')
  • 클래스 정의를 마음대로 바꿀 수 없는 경우에는, __dict__ attribute를 통해 객체의 instance dictionary에 접근할 수 있다.
obj = OpaqueClass(4, 'baz')
print(obj.__dict__)
>>>
{'x':4, 'y':bar}

80. pdb를 사용해, 대화형으로 디버깅하라.

요약

  • 프로그램에서 관심이 있는 부분에 breakpoint 내장 함수 호출을 추가하면,
    • (프로그램을 실행하던 중에)그 위치에서 파이썬 대화형 디버거를 시작할 수 있다.
  • 파이썬 debugger prompt는 완전한 python shell이기 때문에,
    • 실행 중인 프로그램의 상태를 원하는 대로 관찰하거나 변경할 수 있다.
  • pdb shell 명령어를 사용하면,
    • 프로그램 실행을 정밀하게 제어할 수 있고,
    • 프로그램의 상태를 관찰하는 과정프로그램을 진행시키는 과정을 번갈아가며 수행할 수 있다.
  • 독립 실행한 파이썬 프로그램에서 예외가 발생한 경우 2가지 방법으로 디버깅할 수 있다.
    • pdb 모듈을 사용 (python -m pdb -c continue 프로그램 경로)
    • 대화형 파이썬 interpreter(import pdb; pdb.pm())를 사용

본문

  • 파이썬 내장 대화형 디버거
    • print 디버깅 & unittest로 한계가 있다면 사용해보라!
    • 프로그램의 상태를 들여다보고
    • local 변수를 출력하고,
    • 파이썬 프로그램을 한 번에 한 문장씩 실행할 수 있다.
  • breakpoint() == pdb.set_trace()
    • breakpoint 다음 줄로 넘어가기 전에, 터미널에서는 대화형 파이썬 셸이 시작된다.
# always_breakpoint.py
import math

def compute_rmse(observed, ideal):
    total_err_2 = 0
    count = 0
    for got, wanted in zip(observed, ideal):
        err_2 = (got - wanted) ** 2
        breakpoint()  # 여기서 디버거를 시작함
        total_err_2 += err_2
        count += 1

    mean_err = total_err_2 / count
    rmse = math.sqrt(mean_err)
    return rmse


result = compute_rmse(
    [1.8, 1.7, 3.2, 6],
    [2, 1.5, 3, 5])
print(result)
$ python3 always_breakpoint.py
> always_breakpoint.py(12)compute_rmse()
-> total_err_2 += err_2
(Pdb) wanted
>>> 5
(Pdb) got
>>> 7
  • pdb 사용법

    • p <이름>으로 지역 변수 이름을 입력하면, 변수에 저장된 값 출력
    • locals 호출하면, 모든 지역 변수 목록 볼 수 있음
    • help 내장 함수 호출 가능
    • 모듈을 import 하거나,
    • 새로운 객체를 만들거나,
    • 프로그램의 일부를 변경할 수도 있다.
    • 프로그램 관찰
      • where
        • 실행 중인 프로그램의 현재 위치 파악
      • up (u)
        • 현재 관찰 중인 함수를 호출한 쪽으로 이동해서, 해당 함수의 지역 변수를 관찰 가능
      • down (d)
        • 현재 관찰 중인 함수 한 수준 깊게 들어가서, 해당 함수의 지역 변수를 관찰 가능
    • 프로그램 실행 제어
      • step
        • 소스 코드 다음 줄에 함수를 호출하는 부분이 있다면, 해당 함수의 첫 줄에서 디버거로 제어가 돌아온다.
      • next
        • 소스 코드 다음 줄에 함수를 호출하는 부분이 있다면, 해당 함수에서 return된 다음에 제어가 돌아온다.
      • return
        • 현재 함수에서 return될 때까지 프로그램을 실행한후 제어가 돌아온다.
      • continue
        • breakpoint 호출이나 중단점까지 프로그램을 계속 실행한다.
      • quit
        • 디버그에서 나가면서 프로그램도 중단시킨다.
  • breakpoint 위치를 어디에 둘지 모르겠으면?

    • 사후 디버깅!
import math
def compute_rmse(observed, ideal):
    total_err_2 = 0
    count = 0
    for got, wanted in zip(observed, ideal):
        err_2 = (got - wanted) ** 2
        total_err_2 += err_2
        count += 1
    mean_err = total_err_2 / count
    rmse = math.sqrt(mean_err)
    return rmse

result = compute_rmse(
    [1.8, 1.7, 3.2, 7j], # 잘못된 입력
    [2, 1.5, 3, 5])
print(result)
  • pdb 모듈을 사용 (python -m pdb -c continue 프로그램 경로)
    • 프로그램에 문제가 생긴 후 -> 바로 대화형 디버거로 들어감
$ python3 -m pdb -c continue postmortem_breakpoint.py
  • 대화형 파이썬 interpreter(import pdb; pdb.pm())를 사용
...
ZeroDivisionError: ~~~
>>> import pdb; pdb.pm()
> my_module.py(13) compute_variance()
-> variance = err_2_sum / (len(data) - 1)
(Pdb) err_2_sum
0.0
(Pdb) len(data)
1

81. 프로그램이 메모리를 사용하는 방식과 메모리 누수를 이해하기 위해 tracemalloc을 사용하라.

요약

  • 파이썬 프로그램이 메모리를 사용하고 누수하는 양상을 이해하기는 어렵다.
  • gc 모듈은 어떤 객체가 존재하는지 이해할 때는 도움이 되지만,
    • 객체가 어떻게 할당됐는지 파악할 수 있는 정보는 제공하지 않는다.
  • tracemalloc 내장 모듈은
    • 프로그램이 메모리를 사용하는 이유를 알고 싶을 때 쓸 수 있는 강력한 도구다.

본문

  • 하지만 실전에서는 더 이상 사용하지 않는 reference를 유지하기 때문에, 언젠가 결국 메모리를 소진하게 되는 경우가 있다.
    • 메모리 사용의 디버깅이 필요하다.
  • tracemalloc
    • 객체 - 자신이 할당된 장소 - 사용한 메모리 양 연결
    • 메모리 사용의 이전과 이후 스냅샷을 만들어, 서로 비교하면 어떤 부분이 변경됐는지 알 수 있다.
# waste_memory.py
import os

class MyObject:
    def __init__(self):
        self.data = os.urandom(100)

def get_data():
    values = []
    for _ in range(100):
        obj = MyObject()
        values.append(obj)
    return values

def run():
    deep_values = []
    for _ in range(100):
        deep_values.append(get_data())
    return deep_values
# top_n.py
import tracemalloc

tracemalloc.start(10)                     # 스택 깊이 설정
time1 = tracemalloc.take_snapshot()       # 이전 스냅샷

import waste_memory

x = waste_memory.run() # Usage to debug
time2 = tracemalloc.take_snapshot()       # 이후 스냅샷

stats = time2.compare_to(time1, 'lineno') # 두 스냅샷을 비교
for stat in stats[:3]: # 메모리를 낭비하는 가장 큰 3가지 이유 찾기
    print(stat)
    
>>>
waste_memory.py:5: size=2392 KiB (+2392KiB), count=29994 (+29994), average= 82 B
waste_memory.py:10: ...
waste_memory.py:11: ...
  • 전체 코드에서, 특정 객체의 메모리 사용의 흐름을 추적 가능
    • tracemalloc 모듈은, 각 할당의 전체 stack trace를 출력한다.
      • tracemalloc.start 함수가 전달한 프레임 최대 개수만큼만 거슬러 올라가며 출력한다.
# with_trace.py
import tracemalloc

tracemalloc.start(10)
time1 = tracemalloc.take_snapshot()

import waste_memory

x = waste_memory.run()
time2 = tracemalloc.take_snapshot()

stats = time2.compare_to(time1, 'traceback')
top = stats[0]
print('가장 많이 사용하는 부분은:')
print('\n'.join(top.traceback.format()))

profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글