파이썬 코딩의 기술
Chapter 8. 제품화
54. 배포 환경을 구성하는 데는 모듈 스코프 코드를 고려하자
- 종종 프로그램을 여러 배포 환경에서 실행해야 하며, 각 환경마다 고유한 전제와 설정이 있다
- 모듈 스코프에서 일반 파이썬 문장을 사용하여 모듈 콘텐츠를 다르배포 환경에 맞출 수 있다
- 모듈 콘텐츠는 sys와 os 모듈을 이용한 호스트 조사 내역 같은 외부 조건의 결과물이 될 수 있다
프로그램의 일부를 오버라이드
# dev_main.py
TESTING = True
import db_connection
db = db_connection.Database()
# prod_main.py
TESTING = False
import db_connection
db = db_connection.Database()
# db_connection.py
import __main__
class TestingDatabase(object):
# ...
class RealDatabase(object):
# ...
if __main__.TESTING:
Database = TestingDatabase
else:
Database = RealDatabase
- 배포 환경이 복잡해지면 파이썬 상수에서 별도의 설정 파일로 옮기는 방안을 고려
- 내장 몯류 configparser와 같은 도구를 이용하면 제품 설정을 코드와 분리해서 관리
os.environ에 들어 있는 환경 변수를 기반으로 모듈 정의
# db_connection.py
import sys
class Win32Database(object):
# ...
class PosixDatabase(object):
# ...
if sys.platform.startswith('win32'):
Database = Win32Database
else:
Database = PosixDatabase
55. 디버깅 출력용으로는 repr 문자열을 사용하자
- 파이썬 내장 타입의 print를 호출하면 사람이 이해하기는 쉽지만 타입 정보는 숨은 문자열 버전의 값이 나온다
- 파이썬 내장 타입에 repr을 호출하면 출력할 수 있는 문자열 버전의 값이 나온다
- 이 repr 문자열을 eval 내장 함수에 전달하면 원래 값으로 되돌릴 수 있다
- 포맷 문자열에서 %s는 str처럼 사람이 이해하기 쉬운 문자열을 만들어내며, %r은 repr처럼 출력하기 쉬운 문자열을 만들어낸다
- __ repr __ 메서드를 정의하면 클래스의 출력 가능한 표현을 사용자화하고 더 자세한 디버깅 정보를 제공할 수 있다
- 객체의 __ dict __ 속성에 접근하면 객체의 내부를 볼 수 있다
print('foo bar')
print('$s' % 'foo bar')
print(5) # 5
print('5') # 5
a = '\x07'
print(repr(a)) # '\x07'
b = eval(repr(a))
assert a = b
print(repr(5)) # 5
print(repr('5')) # '5'
class OpaqueClass(object):
def __init__(self, x, y):
self.x = x
self.y = y
obj = OpaqueClass(1, 2)
print(obj)
class BetterClass(object):
def __init__(self, x, y):
# ...
def __repr__(self):
return 'BetterClass(%d, %d)' % (self.x, self.y)
obj = BetterClass(1, 2)
print(obj) # BetterClass(1, 2)
obj = OpaqueClass(4, 5)
print(obj.__dict__) # {'y': 5, 'x': 4}
56. unittest로 모든 것을 테스트하자
- 파이썬 프로그램을 신뢰할 수 있는 유일한 방법은 테스트를 작성하는 것이다
- 내장 모듈 unittest는 좋은 테스트를 작성하는 데 필요한 대부분의 기능을 제공한다
- TestCase를 상속하고 테스트하려는 동작별로 메서드 하나를 정의해서 테스트를 정의할 수 있다
- TestCase 클래스에 있는 테스트 메서드는 test라는 단어로 시작해야 한다
- 단위 테스트(고립된 기능용)와 통합 테스트(상호 작용하는 모듈용) 모두를 작성해야 한다
테스트 작성
# units.py
def to_str(data):
if isinstance(data, str):
return data
elif isinstance(data, bytes):
return data.decode('utf-8')
else:
raise TypeError('Must supply str or bytes, found: %r' % data)
# utils_test.py
from unittest import TestCase, main
from utils import to_str
class UtilsTestCase(TestCase):
def test_to_str_bytes(self):
self.assertEqual('hello', to_str(b'hello'))
def test_to_str_str(self):
self.assertEqual('hello', to_str('hello'))
def test_to_str_bad(self):
self.assertRaises(TypeError, to_str, object())
if __name__ == '__main__':
main()
- 테스트 메서드가 어떤 종류의 Exception을 일으키지 않고 실행된다면 테스트가 성공적으로 통과한 것이다
- 동등성을 검사하는 assertEqual
- 불 표현식을 검사하는 asssertTrue
- 적절할 때 에외가 발생하는지 검사하는 asssertRaises
테스트 메서드 실행 전 테스트 환경 설정
class MyTest(TestCase):
def setUp(self):
self.test_dir = TemporaryDirectory()
def tearDown(self):
self.test_dir.cleanup()
# 테스트 메서드
# ...
- 각 테스트 전에 임시 디렉터리를 생성
- 각 테스트가 종료한 후에 그 내용을 삭제
57. pdb를 이용한 대화식 디버깅을 고려하자
- import pdb; pdb.set_trace() 문으로 프로그램의 관심 지점에서 직접 파이썬 대화식 디버거를 시작할 수 있다
- 파이썬 디버거 프롬프트는 실행 중인 프로그램의 상태를 조사하거나 수정할 수 있도록 해주는 완전한 파이썬 셸이다
- pdb 셸 명령을 이용하면 프로그램 실행을 세밀하게 제어할 수 있다
- 또한 프로그램의 상태를 조사하거나 프로그램 실행을 이어가는 과정을 교대로 반복할 수 있다
def complex_func(a, b, c):
# ...
import pdb; pdb.set_trace()
58. 최적화하기 전에 프로파일하자
- 성능 저하를 일으키는 원인이 때로는 불분명하므로 파이썬 프로그램을 최적화하기 전에 프로파일해야 한다
- cProfile이 더 정확한 프로파일링 정보를 제공하므로 profile 모듈 대신에 cProfile을 사용하자
- Profile 객체의 runcall 메서드는 함수 호출 트리를 프로파일하는 데 필요한 모든 기능을 제공한다
- Stats 객체는 프로그램 성능을 이해하는 데 필요한 프로파일링 정보를 선택하고 출력하는 기능을 제공한다
from random import randint
max_size = 10**4
data = [randint(0, max_size) for _ in range(max_size)]
test = lambda: insertion_sort(data)
from cProfile import Profile
profiler = Profile()
profiler.runcall(test)
stats = Stats(profiler)
stats.strip_dirs()
stats.sort_stats('cumulative')
stats.print_stats()
- ncalls : 프로파일링 주기 동안 함수 호출 횟수
- tottime : 함수가 실행되는 동안 소비한 초 단위의 시간으로, 다른 함수 호출을 실행하는 데 걸린 시간은 배재한다
- tottime percall : 함수를 호출하는 데 걸린 평균 시간이며 초 단위다. 다르함수의 호출 시간은 배제한다. tottime을 ncalls로 나눈 값이다
- cumtime : 함수를 실행하는 데 걸린 초 단위 누적 시간이며, 다른 함수를 호출하는 데 걸린 시간도 포함된다
- cumtime percall : 함수를 호출할 때마다 걸린 시간에 대한 초 단위 평균 시간이며, 다른 함수를 호출하는 데 걸린 시간도 포함한다. cumtime을 ncalls로 나눈 값이다
stats.print_callers()
- 각 함수의 프로파일링 정보에 기여하는 호출자를 찾는 방법을 제공한다
- 이 프로파일러 통계 테이블은 호출된 함수를 왼쪽에 보여주며, 누가 이런 호출을 하는지를 오른쪽에 보여준다
59. tracemalloc으로 메모리 사용 현황과 누수를 파악하자
- 파이썬 프로그램이 메모리를 어떻게 사용하고, 메모리 누수를 일으키는지를 이해하기는 어렵다
- gc 모듈은 어떤 객체가 존재하는지를 이해하는 데 도움을 주지만, 해당 객체가 어떻게 할당되었는지에 대한 정보는 제공하지 않는다
- 내장 모듈 tracemalloc은 메모리 사용량의 근원을 이해하는 데 필요한 강력한 도구를 제공한다
- tracemalloc은 파이썬 3.4와 그 이후 버전에서만 사용할 수 있다
참조를 유지하며 메모리를 낭비하는 프로그램
# using_gc.py
import gc
found_objects = gc.get_objects()
print('%d objects before' % len(found_objects))
import waste_money
x = waste_memory.run()
found_objects = gc.get_objects()
print('%d objects after' % len(found_objects))
for obj in found_objects[:3]:
print(repr(obj)[:100])
- gc.get_objects는 객체가 어떻게 할당되는지 아무런 정보도 제공하지 않는다
프로그램에서 메모리를 가장 많이 사용하는 세 부분 출력
# top_n.py
import tracemalloc
tracemalloc.start(10) # 스택 프레임을 최대 10개까지 저장
time1 = tracemalloc.take_snapshot()
import waste_memory
x = waste_memory.run()
time2 = tracemalloc.take_snapshot()
stats = time2.compare_to(time1, 'lineno')
for stat in stats[:3]:
print(stat)
- 어떤 객체들이 프로그램 메모리 사용량을 주로 차지하고, 소스 코드의 어느 부분에서 할당되는지를 쉽게 알 수 있다
'프로그래머 > Python' 카테고리의 다른 글
[윤성우의 열혈 파이썬 중급편] 02. 수정 가능한 객체와 수정 불가능한 객체 (0) | 2020.12.15 |
---|---|
[윤성우의 열혈 파이썬 중급편] 01. reference count와 garbage collection (0) | 2020.12.15 |
[Effective Python 복습] Chapter 7. 협력 (0) | 2020.12.13 |
[Effective Python 복습] Chapter 6. 내장 모듈 (0) | 2020.12.11 |
[널널한 교수의 고급 파이썬] 05-5 파이썬 외부 라이브러리 (0) | 2020.12.11 |