파이썬 코딩의 기술
내장 모듈
42. functools.wraps로 함수 데코레이터를 정의하자
- 데코레이터는 런타임에 한 함수로 다른 함수를 수정할 수 있게 해주는 파이썬 문법이다
- 데코레이터를 사용하면 디버거와 같이 객체 내부를 조사하는 도구가 이상하게 동작할 수도 있다
- 직접 데코레이터를 정의할 때 이런 문제를 피하려면 내장 모듈 functools의 wraps 데코레이터를 사용하자
def trace(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print('%s(%r, %r) -> %r' %
(func.__name__, args, kwargs, result))
return result
return wrapper
@trace
def fibonacci(n):
if n in (0,1):
return n
return (fibonacci(n-2) + fibonacci(n-1))
print(fibonacci) # trace 함수는 그 안에 정의된 wrapper를 반환
help(fibonacci) # 그 wrapper 함수가 fibonacci라는 이름에 할당 됨
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
#...
return wrapper
@trace
def fibonacci(n):
# ...
help(fibonacci)
43. 재사용 가능한 try/finally 동작을 만드려면 contextlib와 with문을 고려하자
- with문을 이용하면 try/finally 블록의 로직을 재사용할 수 있고, 코드를 깔끔하게 만들 수 있다
- 내장 모듈 contextlib의 contextmanager 데코레이터를 이용하면 직접 작성한 함수를 with 문에서 쉽게 사용할 수 있다
- 컨텍스트 매니저에서 넘겨준 값은 with 문의 as 부분에 할당된다. 컨텍스트 매니저에서 값을 반환하는 방법은 코드에서 특별한 컨텍스트에 직접 접근하려는 경우에 유용하다
def my_function():
logging.debug('Some debug data')
logging.error('Error log here')
logging.debug('More debug data')
@contextmanager
def debug_logging(level):
logger = logging.getLogger()
old_level = logger.getEffectiveLevel()
logger.setLevel(level)
try:
yield
finally:
logger.setLevel(old_level)
with debug_logging(logging.DEBUG):
print('Inside:')
my_function() # 디버그 메시지가 모두 화면에 출력
print('After:')
my_function() # 디버깅 메시지가 출력되지 않음
with 타깃 사용하기
@contextmanager
def log_level(level, name):
logger = logging.getLogger(name)
old_level = logger.getEffectiveLevel()
logger.setLevel(level)
try:
yield logger
finally:
logger.setLevel(old_level)
with log_level(logging.DEBUG, 'my-log') as logger:
logger.debug('This is my message!')
logging.debug('This will not print')
logger = logging.getLogger('my-log')
logger.debug('Debug will not print')
logger.error('Error will print')
44. copyreg로 pickle을 신뢰할 수 있게 만들자
- 내장 모듈 pickle은 신뢰할 수 있는 프로그램 간에 객체를 직렬화하고 역직렬화하는 용도로만 사용할 수 있다
- pickle 모듈은 간단한 사용 사례를 벗어나는 용도로 사용하면 제대로 동작하지 않을 수도 있다
- 빠뜨린 속성 값을 추가하거나 클래스에 버전 관리 기능을 제공하거나 안정적인 임포트 경로를 제공하려면 pickle과 함께 내장 모듈 copyreg를 사용해야 한다
class GameState(object):
def __init__(self):
self.level = 0
self.lives = 4
state = GameState()
state.level += 1
state.lives += 1
state_path = '/tmp/game_state.bin'
with open(state_path, 'wb') as f:
pickle.dump(state, f)
with open(state_pth, 'rb') as f:
state_after = pickle.load(f)
print(state_after.__dict__)
기본 속성값
class GameState(object):
def __init__(self, level=0, lives=4, points=0):
self.level = level
self.lives = lives
self.points = points
def pickle_game_state(game_state):
kwargs = game_state.__dict__
return unpickle_game_state, (kwargs,)
def unpickle_game_state(kwargs):
return GameState(**kwargs)
# GameStae 객체와 직렬화 함수 등록
copyreg.pickle(GameState, pickle_game_state)
state = GameState()
state.points += 1000
serialized = pickle.dumps(state)
state_after = pickle.loads(serialized)
print(state_after.__dict__)
클래스 버전 관리
# lives 제거
class GameState(object):
def __init__(self, level=0, points=0):
# ...
def pickle_game_state(game_state):
kwargs = game_state.__dict__
kwargs['version'] = 2
return unpickle_game_state, (kwargs,)
def unpickle_game_state(kwargs):
version = kwargs.pop('version', 1)
if version == 1:
kwargs.pop('lives')
return GameState(**kwargs)
copyreg.pickle(GameState, pickle_game_state)
state_after = pickle.loads(serialized)
print(state_after.__dict__)
안정적인 임포트 경로
# 클래스 이름 변경
class BetterGameState(object):
def __init__(self, level=0, points=0, magic=5):
# ...
copyreg.pickle(BetterGameState, pickle_game_state)
state = BetterGameState()
serialized = pickle.dumps(state)
45. 지역 시간은 time이 아닌 datetime으로 표현하자
- 서로 다른 시간대를 변환하는 데는 time 모듈을 사용하지 말자
- pytz 모듈과 내장 모듈 datetime으로 서로 다른 시간대 사이에서 시간을 신뢰성 있게 변환하자
- 항상 UTC로 시간을 표현하고, 시간을 표시하기 전에 마지막 단계로 UTC 시간을 지역 시간으로 변환하자
from datetime import datetime, timezone
# 현재 시각을 UTC로 얻어와서 지역시간(태평양 연안 표준시)로 변환
now = datetime(2014, 8, 10, 18, 18, 30
now_utc = now.replace(tzinfo = timezone.utc)
now_local = now_utc.astimezone()
print(now_local)
# 지역시간을 다시 UTC의 유닉스 타임스탬프로 변경
time_str = '2014-08-10 11:18:30'
now = datetime.strptime(time_str, time_format)
time_tuple = now.timetuple()
utc_now = mktime(time_tuple)
print(utc_now)
# NYC 도착시간을 UTC datetime으로 변환
arrival_nyc = '2014-05-01 23:33:24'
nyc_dt_naive = datetime.strptime(arrival_nyc, time_format)
eastern = pytz.timezone('US/Eastern')
nyc_dt = eastern.localize(nyc_dt_naive)
utc_dt = pytz.utc.normalize(nyc_dt.astimezone(pytz.utc))
print(utc_dt)
# 샌프란시스코 지역 시간으로 변환
pacific = pytz.timezone('US/Pacific')
sf_dt = pacific.normalize(utc_dt.astimezone(pacific))
print(sf_dt)
46. 내장 알고즘과 자료 구조를 사용하자
- 알고리즘과 자료구조를 표현하는 데는 파이썬의 내장 모듈을 사용하자
- 이 기능들을 직접 재구현하지는 말자. 올바르게 만들기가 어렵기 때문.
double ended queue(deque)
import collections
fifo = deque()
fifo.append(1) # 생산자
x = fifo.popleft() # 소비자
- deque는 큐의 처음과 끝에서 아이템을 삽입하거나 삭제할 때 항상 일정한 시간이 걸리는 연산을 제공한다
정렬된 딕셔너리(OrderedDict)
a = {}
a['foo'] = 1
a['bar'] = 2
# 무작위로 'b'에 데이터를 추가해서 해시 충돌을 일으킴
while True:
z = randint(99, 1013)
b = {}
for i in range(2):
b[i] = i
b['foo'] = 1
b['bar'] = 2
for i in range(z):
del b[i]
if str(b) != str(a):
break
print(a) # {'foo' : 1, 'bar' : 2}
print(b) # {'bar' : 2, 'foo' : 1}
print(a == b) # True
a = OrderedDict()
a['foo'] = 1
a['bar'] = 2
b = OrderedDict()
b['foo'] = red
b['bar'] = blue
for value1, value2 in zip(a.values(), b.values()):
print(value1, value2)
# 1 red
# 2 blue
기본 딕셔너리
import collections
stats = {}
key = 'my_counter'
if key not in stats:
stats[key] = 0
stats[key] += 1
stats = defaultdict(int)
stats['my_counter'] += 1
- 딕셔너리를 사용할 때 한 가지 문제는 어떤 키가 이미 존재한다고 가정할 수 없다는 것
- collectioins 모듈의 defaultdict 클래스는 키가 존재하지 않으면 자동으로 기본값을 저장
- int는 0을 반환, list, set은 빈 list, 빈 set으로 초기화
힙 큐
- 힙(heap)은 우선순위 큐(priority queue)를 유지하는 유용한 자료다
- heapq 모듈은 표준 list 타입으로 힙을 생성하는 heappush, heappop, nsmallest 같은 함수를 제공
- 이진트리(binary tree) 기반의 최소 힙 자료구조
- 가장 작은 값은 언제나 인덱스 0, 즉 이진 트리의 루트에 위치
a = []
heappush(a, 5)
heappush(a, 3)
heappush(a, 7)
heappush(a, 4)
print(heappop(a), heappop(a), heappop(a), heappop(a)) # 3 4 5 7
a = []
heappush(a, 5)
heappush(a, 3)
heappush(a, 7)
heappush(a, 4)
assert a[0] == nsmallest(1, a)[0] == 3
print(a) # [3, 4, 7, 5] -> 이진 트리 구조
a.sort()
print(a) # [3, 4, 5, 7]
바이섹션
x = list(range(10**6))
i = x.index(991234)
i = bisect_left(x, 991234)
- list에서 아이템을 검색하는 작업은 index 메서드 호출 시 길이에 비례하여 선형적 증가
- 바이너리 검색의 복잡도는 로그 형태로 증가
- 이진분할 알고리즘 사용
이터레이터 도구
- 내장모듈 itertools는 이터레이터를 구성하거나 이터레이터와 상호 작용하는 데 유용한 함수를 다수 포함
- 이터레이터 연결
- chain : 여러 이터레이터를 순차적인 이터레이터 하나로 결합
- cycle : 이터레이터의 아이템을 영원히 반복
- tee : 이터레이터 하나를 병렬 이터레이터 여러 개로 나눈다
- zip_longest : 길이가 서로 다른 이터레이터들에도 잘 동작하는 내장 함수 zip의 변형
- 이터레이터에서 아이템 필터링
- islice : 복사 없이 이터레이터를 숫자로 된 인덱스로 슬라이스
- takewhile : 서술 함수가 True를 반환하는 동안 이터레이터의 아이템을 반환
- dropwhile : 서술 함수가 처음으로 False를 반환하고 나면 아터레이터의 아이템을 반환
- filterfalse : 서술 함수가 False를 반환하는 이터레이터의 모든 아이템을 반환. 내장 함수 filter의 반대 기능
- 이터레이터에 있는 아이템들의 조합
- product : 이터레이터에 있는 아이템들의 cartesian product을 반환. 길게 중첩된 리스트 컴프리헨션에 대한 훌륭한 대안
- permuattions : 이터레이터에 있는 아이템을 담은 길이 N의 순서 있는 순열을 반환
- combinations : 이터레이터에 있는 아이템을 중복되지 않게 담은 길이 N의 순서 없는 조합을 반환
import itertools
letters = ['a', 'b', 'c', 'd', 'e', 'f']
booleans = [1, 0, 1, 0, 0, 1]
decimals = [0.1, 0.7, 0.4, 0.5]
print(list(itertools.chain(letters, booleans, decimals)))
# ['a', 'b', 'c', 'd', 'e', 'f', 1, 0, 1, 0, 0, 1, 0.1, 0.7, 0.4, 0.5]
from itertools import count , izip
for number, letter in izip(count(0, 10), ['a', 'b', 'c', 'd', 'e']):
print('{0}: {1}'.format(number, letter))
# 0: a 10: b 20: c 30: d 40: e
from itertools import izip
print(list(izip[1,2,3], ['a','b','c']))
# [(1, 'a'), (2, 'b'), (3, 'c')]
- 기존 zip 보다 약간의 성능 향상
from itertools import imap
print list(imap(lambda x: x * x, xrange(10)))
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- map()과 거의 비슷한 방식으로 작동
xrange vs range
# 10을 초과하는 숫자는 생성하지 않음
odd_numbers_to_10 = itertools.takewhile(lambda i: i <= 10, (x for x in xrange(1000) if x % 2))
# 1000개의 수가 담긴 큰 리스트를 생성
odd_numbers_tllo_10 = itertools.takewhile(lambda i: i <= 10, (x for x in range(1000) if x % 2))
from itertools import islice
for i in islice(range(10), 5):
print(i)
# 0 1 2 3 4
for i in islice(range(100), 0, 100, 10):
print(i)
# 0 10 20 30 40 50 60 70 80 90
from itertools import tee
i1, i2, i3 = tee(xrange(10), 3)
print(list(i1)) # [0,1,2,3,4,5,6,7,8,9]
print(list(i2)) # [0,1,2,3,4,5,6,7,8,9]
print(list(i3)) # [0,1,2,3,4,5,6,7,8,9]
print(list(i1)) # []
print(list(i2)) # []
print(list(i3)) # []
r = (x for x in range(10) if x < 6>)
i1, i2, i3 = tee(r, 3)
print(list(r)) # [0,1,2,3,4,5]
print(list(i1)) # []
print(list(i2)) # []
print(list(i3)) # []
- 한 번 사용된 레퍼런스는 더 이상 값을 참조하지 않는다
- 원본 제너레이터인 r을 실행시키면 나머지 복제본들도 다 참조가 끊어지는 특성이 있다
from itertools import cycle, izip
for number, letter in izip(cycle(range(2)), ['a','b','c','d','e']):
print('{0}: {1}'.format(number, letter)
# 0: a
# 1: b
# 0: c
# 1: d
# 0: e
from itertools import repeat
print(list(repeat('Hello, world!', 3)))
# ['Hello, world!', 'Hello, world!', 'Hello, world!']
from itertools import dropwile
print(list(dropwhile(lambda x: x < 10, [1,4,6,7,11,34,66,100,1])))
# [11, 34, 66, 100, 1]
from itertools import takewhile
print(list(takewhile(lambda x: x < 10, [1, 4, 6, 7, 11, 34, 66, 100, 1])))
# [1, 4, 6, 7]
from itertools import ifilter
print(list(ifilter(lambda x: x < 10, [1, 4, 6, 7, 11, 34, 66, 100, 1]))
# [1, 4, 6, 7, 1]
from operator import itemgetter
from itertools import groupby
attempts = [
('dan', 87),
('erik', 95),
('jason', 79),
('erik', 97),
('dan', 100)
]
# Sort the list by name for groupby
attempts.sort(key=itemgetter(0))
# Create a dictionary such that name: scores_list
print({key: sorted(map(itemgetter(1), value)) for key, value in groupby(attempts, key=itemgetter(0))})
# {'dan': [87, 100], 'jason': [79], 'erik': [95, 97]}
from collections import defaultdict
counts = defaultdict(list)
attempts = [('dan', 87), ('erik', 95), ('jason', 79), ('erik', 97), ('dan', 100)]
for (name, score) in attempts:
counts[name].append(score)
print(counts)
# defaultdict(<type 'list'>, {'dan': [87, 100], 'jason': [79], 'erik': [95, 97]})
'프로그래머 > Python' 카테고리의 다른 글
[Effective Python 복습] Chapter 8. 제품화 (0) | 2020.12.13 |
---|---|
[Effective Python 복습] Chapter 7. 협력 (0) | 2020.12.13 |
[널널한 교수의 고급 파이썬] 05-5 파이썬 외부 라이브러리 (0) | 2020.12.11 |
[널널한 교수의 고급 파이썬] 05-3, 05-4 파이썬 표준 라이브러리 (0) | 2020.12.11 |
[널널한 교수의 고급 파이썬] 05-2 자동화된 코딩스타일 정비도구 (0) | 2020.12.11 |