짧은 요약 :

Pythonic 코딩을 위한 정리  
# 전반적으로 다루려고 하는 것들

-정렬된 리스트 탐색시 bisect사용  
-별표식을 사용해 언패킹? ->오류 줄이는 방법? ??  
-zip 사용하여 여러 리스트를 동시에 이터레이션하기!  
-왈러스 연산자? ??  
-f-문자열 (+문자열, 바이트객체?).  
-typing 모듈? ??  
-대입식 ??  
-튜플의 나머지 원소를 모두 잡아내는 언패킹??   

-매타클래스보다는 클래스 데코레이터를 사용하라 ??  
-__init_subclass__() 메서드 ??  
-동시성( 블로킹 I/O에는 스레드를 사용하지만, 병렬화를 위해 스레드를 사용하지 말라) ???  
-강건성과 성능(최적화하기 전에 프로파일링하라) ???  

-제너레이터  
-이터레이터  
-데코레이터  
-왈러스 연산자  
-다형성  
-메타클래스  
-디스크립터  
-동시성/병렬성  
-스레드  
-  
-코루틴  
-이벤트 루프  
-비동기  
-프로파일링  
-모킹과 테스트  
-가상환경  
-타입힌트  
-경고  

PEP8 (Python Enhancement Proposal#8)

공백관련(220221)
**라인 길이는 79문자 이하
**들여쓰기는 4칸 스페이스로
**긴 식을 다음 줄에 이어서 쓸 경우 다음줄에 4스페이스 들여쓰기
**각 함수와 클래스 사이에는 빈줄 두 줄
**클래스 안에서 메서드와 메스드 사이는 빈칸 한 줄
**딕셔너리의 키와 콜론(:) 사이에는 공백 없고, 한줄 안에 키-벨류 넣을 경우 콜론과 벨류 사이 공백하나
**변수대입서 ‘=’ 전후 스페이스 하나
**타입 표기를 덧붙이는 경우에는 변수 이름과 콜론 사이에 공백을 넣지 않고, 콜론과 타입 정보 사이에 스페이스 하나
**
num = 1 # type: int (예시) -> 타입 힌트 표기하는 것을 의미함

*명명관련(220222)
**함수, 변수, 애트리뷰트(클래스 내부 함수, 변수)는 lowercase_underscore처럼 소문자와 언더바로
**보호되야할 인스턴스의 애트리뷰트는 _leading_underscore처럼 앞에 언더바 사용
**private(한 클래스 안에서만 쓰이고 다른 곳에서는 쓰면 안 되는) 인스턴스 애트리뷰트는 __leading_underscore처럼 앞에 언더바 두개 사용
**클래시는 CapitalizedWord처럼 PascalCase 사용
**모델(함수, 클래스 모아놓은 파일) 수준의 상수는 ALL_CAPS처럼 모든 글자를 대문자로 하고 단어와 단어 사이 언더바로
**클래스에 들어 있는 인스턴스 메서드는 호출 대상 객체를 가리키는 첫번째 인자의 이름으로 반드시 self를 사용
**클래스 메서드는 클래스를 가리키는 첫 번째 인자의 이름으로 반드시 cls를 사용
(참고로 클래스는 설계도, 객체는 설계도로 구현한 대상, 인스턴스는 실제 소프트웨어로 구현된 것)
(참고로 클래스 메서드는 인스턴스를 만들어 실체화 하지 않아도 클래스를 통해 직접적으로 호출 할 수 있음, cls로 클래스 호출)
(참고로 인스턴스 메서드는 클래스를 통해 호출할 수 없고, 클래스의 인스턴스를 만들어 실체화 하여 생성된 인스턴스를 통해서 호출할 수 있음, self로 인스턴스 호출)
(참고로 정적 메서드는 인스턴스, 클래스 애트리뷰트 접근/호출 못 함)

*expression and statement(식과 문)
**긍정식을 부정하지 말고 부정을 내부에 넣어라(if not a is b –> if a is not b)
**빈 컨테이너(리스트, 딕셔너리, 세트), 빈 시퀀스(스트링, 리스트, 튜플) 체크할 때는 길이 체크보다는 False취급 사실 활용하라(if len(something) == 0 –> if not 컨테이너)
**마찮가지로 비어있지 않은 것을 체크할 때도 True 취급 활용(if len(someting) > 0 –> if 컨테이너)
**한줄짜리 if문, for 루프, while 루프, except 문 사용하지 말고 여러 줄에 나눠라
**식을 한줄 안에 다 쓸 수 없는 경우 식을 괄호로 둘러싸고 줄바꿈과 들여쓰기를 추가해서 가독성 높여라
**여러줄에 걸쳐 식을 쓸 때는 줄이 계속된다는 표시를 하는 \문자 보다는 괄호를 사용하라

*임포트
**import, from x import y는 항상 파일 맨 앞에 위치
**모듈 임포트시 절대 경로를 사용하라(현재 기준으로 상대경로 사용 x, 예를 들어 bar패키지 foo라면 현재 bar패키지 안에 있더라도 from bar import foo라고 해야)
**반드시 상대적 경로를 임포트해야 한다면 from . import foo처럼 사용
**임포트를 적을 때는 표준라이브러리 모듈, 서드 파티 모듈, 사용자가 직접 만든 모듈 선서로 섹션을 나눠라, 그리고 알파벳 순서로 임포트하라

f-string 사용하라 (better way 04)

*기존의 c style tuple or dictionary과 format 보다 간결하고, 명확하게 표현됨
**비교

key, value = 'my_values', 1.234  
f_string = f'{key:<10}={value:.2f}'  
c_tuple = '%-10s = %.2f' % (key, value)  
c_dict = '%(key)-10s = %(value).2f' %{'key': key, 'value': value}  
str_args = '{:<10} = {:.2f}'.format(key, value)  

**사용예

key, value = 'my_values', 1.234  
places, number = 3, 1.23456  
formatted = f'{key} = {value}'  
formatted = f'내가 고른 숫자는 {number:.{places}f}' #하드코딩 대신 변수를 사용해 형식 문자열 안에 파라미터화함  

range 보다는 enumerate를 사용하라(yield에 대하여, betterway 07, 220322)

yield는 generator를 만드는데 사용됨
**결과값 반환할 때 return대신 사용하는 return과는 다소 다른 방식
**
return list(“ABC”) vs yield “A” \n yield “B” \n yield “C”
**결과를 여러번 나누어서 제공
**
return은 list를 반환하고 yield는 generator를 반환

그렇다면 generator는 무엇인가?
**데이터 접근할 때 순차적으로 접근 가능하게 함
**리스트라면 전부 접근해야해서 예를들어, 만개의 데이터가 들어있는 리스트라면
처리시간 1초에 필요 메모리 1mb라 가정했을 때, 10000초와 10gb(10000mb)가 필요함
**하지만 제너레이터는 순차적으로 하나씩 가져올 수 있어서 가져올 때마다 1초와 1mb로 처리
**메모리 부족하거나 한번에 보여주지 않아도 될 때에 유용(그래서lazy iterator라고 불리기도)
**이론적으로 무한데이터도 만들 수 있음
**yield from을 쓰면 리스트를 바로 제너레이터로 변환할 수 있음
**
yield from [“A”, “B”, “C”] 이런식으로
*리스트 표현식처럼 제너레이터 표현식도 있음
**
abc = (ch for ch in “ABC”) 이런식

위와 비슷한 맥락에서 for i in range(len(something_list)): 대신
it = enumerate(something_list)
next(it)
이렇게 쓸 수 있고 이 enumerate는 lazy generator(yield 사용 후 만들어지는)로 이루어져 있음
**깔끔하게 for i, something in enumerate(something_list): 이렇게 짤 수 있음
**enumerate에 두번째 파라미터를 지정해줘서 시작도 지정할 수 있음
**
for i, something in enumerate(something_list, 1): 이런식으로..

mutable과 imutable 자료구조(220323 wednesday)

가역적 비가역적.. 변하는 자료구조와 안 변하는 자료구조
**가역적인 것은 대부분의 자료구조들 list, dictionary, set
**비가역적인 것은 tuple.
**
숫자형, 문자형 지정 변수도 비가역적..
***비가역적 자료구조들은 변하지 않음 -> 예를 들어, x=3이고 y=x라 했을 때 y += 1을 해도 x는 유지됨
**mutable은 call-by-reference, immutable은 call-by-value로 볼 수 있음
**참고문헌: https://ledgku.tistory.com/54

복잡한 식을 쓰는 대신 도우미 함수를 작성하라(Better way 5, 220324 thursday)

*if/else 조건식으로 간결하게 표현도 가능한 경우 있음
**사용예

red_str = my_values.get('빨강', [""])  
red = int(red_str[0]) if red_str[0] else 0  

*하지만 두세번만 반복되는 경우에도 함수 따로 만드는 것 권장
**사용예

def get_first_int(values, key, efault=0):  
    found = values.get(key, [""])  
    if found[0]:  
        return int(found[0])  
    return default  

인덱스 대신 언패킹해라(Better way 6, 220324 thursday)

*지양하는 사용예

listA = [("korean", "kimchi"), ("italian","pasta")]
for i in range(len(listA)):
    print(listA[i])

*지향하는 사용예

listA = [("korean", "kimchi"), ("italian","pasta")]
for (national, food) in listA:
    print( (national, food) )

Better way 8,9,10(Friday, 220325 )

zip을 사용하라
**(참고)리스트 컴프리헨션 사용하면 새로운 리스트 만들기 편함
**
counts = [len(n) for n in names] #names = [‘Zelenskyy’, ‘우크라이나’] #결과=[9,5]
*두개 리스트 접근할 때 zip 편함
**
사용예

for name, count in zip(names, counts):
    print(f'{name}: {count}')

**다만 길이가 다를 경우에는 작은 길이까지만 고려해줌
**
이 경우, zip_longest를 사용하면 짧은 경우에는 None을 넣어줌

loop 이후에 else블록 사용하지 말아라
*월러스 연산자 ( := ) 사용해서 대입 반복 피하라
**월러스 연산자는 대입연산자로 대입 해줌.. 이후 조건 체크 바로 해줄 수 있어서 편함
**
사용예

while fresh_fruit := pick_fruit(): #fresh_fruit에 값 넣어줌, 만약 없으면 break됨  


if (count := fresh_fruit.get('레몬', 0) ) >= 2: #카운트에 개수 세서 넣어줌. 뒤에 조건문 붙여줘서 한줄로 편하게 처리  

Bettery way 27 -map과 filter 대신 comprehension을 사용하라! (Thursday, 220609) - (오랜만에 업데이트)

*map과 filter 대신 comprehension

-다음 task 수행할 ..  
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  
squares = []  
for x in a:  
    if x % 2 == 0:
        squares.append(x**2)  
print(squares)  


-map, filter 이용 대신  
alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a))  
assert even_squares = list(alt)  


-comprehension 사용!  
squares = [x**2 for x in a if x % 2 == 0]  


-dict set 각각 dict comprehension set comprehension 있어 사용 가능!  
even_squares_dict = {x: x**2 for x in a if x % 2 == 0}# {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}    
threes_cubed_set = {x**3 for x in a if x % 3 == 0}# {216, 729, 27}     

Better way 28, 29

*comprehension의 하위 식은 3개 미만으로 해라. 왜냐하면 너무 복잡해지기 때문.
*comprehension에 왈러스 연산자( := )와 함수 호출을 사용하여 가독성 높일 수 있음.

Bettery way 30 (Wednesday, 220615)

*제너레이터(generator, yield를 사용하여 반환하는 함수)를 만들어서 사용하면
함수가 실제로 실행되지 않고 즉시 이터레이터를 반환, 메모리 크기를 제한할 수 있어
입력 길이가 아무리 길어도 처리 가능
**단, 반환하는 이터레이터에 상태가 있기 때문에 호출하는 쪽에서 재사용이 불가능함

Better way 31 (Wednesday, 220615)

*이터레이터 결과는 단 한번만 만들어져서 재호출 시 아무 결과도 안 옴
**(StopIteration 예외가 되는 것임)
*카피해서 리스트에 넣어두는 해결책이 있으나 입력이 너무 길 경우 메모리를 많이 먹으므로 비추
*대안으로 호출 때마다 이터레이터를 받아주는 방법이 있음(근데 람다식 쓰는 것이 보기는 안좋다고함)
**def normalize_func(get_ter): total = sum(get_iter()) # 새 이터레이터
result = []
for value in get_iter(): # 새 이터레이터
percent = 100 * value / total
result.append(percent)
return result

percentages = normalize_func(lambda: read_visits(path))
*더 나은 방법으로 이터레이터 프로토콜을 구현한 새로운 컨테이너 클래스를 제공하는 것이 있음
**class ReadVisits: def init(self, data_path):
self.data_path = data_path
def iter(self):
with open(self.data_path) as f:
for line in f:
yield int(line)
visits = ReadVisits(path) #클래스를 불러와서 객체 정의
percentages = normalize(visits) #객체를 보내줌
**유일한 단점은 입력 데이터를 여러번 읽는 다는 것.. 그래도 이게 제일 나은 듯?
*예외처리의 경우 - 반복 가능한 이터레이터인지 검사
**if iter(numbers) is numbers: #반복 안 가능?
raise TypeError(‘컨테이너를 제공해야 합니다’)
*예외처리 대안 - collection.abc 내장모듈 instance9 사용하여 검사
**from collections.abc import Iterator
if instance(numbers, Iterator): #반복 가능한 이터레이션인지 검사
raise TypeError(‘컨테이너를 제공해야 합니다’)

Better way 32, 33 (Thursday, 220616)

긴 리스트 컴프리헨션보다는 제너레이터를, yield from을 사용해 여러 제너레이터 합성을

리스트컴프리핸션 입력 사이즈 커지면 메모리 많이 사용
**제너레이터 식으로 해결
**
next함수로 다음 값 가져올 수 있음
*제너레이터 식은 두 제너레이터 식을 합성할 수도 있음

*yield from 사용으로 가독성도 높여줌
** for delta in move(4, 5.0): yield delta –> yield from move(4, 5.0)
**yield from은 근본적으로 인터프리터가 for루프 내포해서 성능 더 좋아짐

Bettery way 34, 35, 36 (Saturday, 220618)

send로 제너레이터에 데이터를 주입하지 말라, 제너레이터 안에서 throw로 상태 변화하지 말라, 이터레이터나 제너레이터 다룰 때 itertools 사용하라

*yield 식 사용한 제너레이터 함수는 이터레이션 출력이 가능하지만 단방향임
그래서 send메서드 사용해서 양방향 채널 만들어줄 수 있음
**하지만 방금 시작한 제너레이터는 최초 send 호출 때 보낼 인자가 없고 None만 뜸
****첫 값을 None을 주는 해결책이 있고, 코드가독성을 위해 yield from을 사용할 수도 있음
**
하지만 이 경우 다음 yield from 시작 때마다 None뜸.
**그래서 그냥 next를 사용하는 것을 권장

*제너레이터 안에서 Exception던질 수 있는 throw 메서드 있음-마지막 yield 실행에서 예외 발생
**가독성 많이 떨어짐. __iter__메서드 포함 클래스를 정의하여 사용하는 것이 더 나은 대안

*복잡한 이터레이션 코드 itertools에 거의 있음->help(itertools)해보시오
**연결 - chain:순차적으로 함침, repeat:반복, cycle:사이클반복, tee:병렬적으로 리스트 만들어줌, zip_logest:두 이터레이터 중 짧은 쪽의 경우 지정값 넣어서 합쳐줌
**원소 필터링 - islice:인덱싱으로 슬라이싱, takewhile:False나올 때까지 반환, dropwhile:False시작부터 True 전까지 반환, filterfalse:False인 것만 반환
**원소 조합 - accumulate:축적 반환, product:데카르트곱 반환, permutations:순열 반환, combinations:조합 반환, combinations_with_replacement:중복 조합 반환

Better way 52 (Sunday, 220619)

자식 프로세스를 관리하기 위해 subprocess를 사용하라

*subprocess 모듈을 사용해 자식 프로세스를 실행하고 입력과 출력 스트림을 관리할 수 있음
*자식 프로세스는 파이썬 인터프리터와 병렬로 실행되므로 CPU코어 최대 활용 가능(not컨커런트)
*간단 자식 프로세스 실행은 run함수, 파이프라인 필요한 경우 Popen 클래스 사용해야.
*자식프로세스 멈추거나 교착 방지하려면 communicate 메서드에 대해 timeout 사용하면 됨

Better way 53, 54 (Monday, 220620)

블로킹 I/O의 경우 스레드를 사용하고 병렬성을 피하라, 스레드에서 데이터 경합을 피하기 위해 Lock을 사용하라

*GIL(Global Interpreter Lock)은 CPython 자체와 사용하는 C 확장 모듈이 실행되면서 인터럽트가 함부로 발생하는 걸 방지
**(참고로 source -> bytecode –by interpreter)
*GIL 땜에 멀티스레딩이 코드 실행단에서는 성능향상이 안 됨
*그럼에도 멀티스레딩하는 이유는 동시성(컨커런시 concurency) 구현하기 쉽고 I/O 블로킹은 잘 되기 때문
**(GIL은 프로그램 병렬 실해은 막지만 시스템콜에는 영향 못 끼침)
*즉, multi threading은 코드를 가급적 손보지 않고 블리코이 I/O를 병렬로 실행하고 싶을 때 사용

GIL의 락은 코드 딴이지, 자료구조 접근 까지는 못 막아줌
*파이썬 스레딩이 자료구조를 접근하고 일시 중단되고 연산 순서가 섞이는데, 락 따로 해줘야함
*threading 내장 Lock 클래스 사용해주면 됨
**with문을 통해 사용하면 코드 가독성에 굿
**
with self.lock: self.count += offset

Better way 55 (Tuesday, 220621)

Queue를 사용해 스레드 사이의 작업을 조율하라

순차적 작업을 동시에 파이썬 스레드 이용할 시(특히 I/O 위주) 파으파리인 유용(파이썬 쓰레드를 사용한)
*근데 Busy waiting, 종료 알리기, 메미로 사용 폭발의 문제가 발생할 수 있음
**그래서 Queue 클래스를 가져와서 사용하면 블로킹 연산, 버퍼 크기 지정, jin을 통한 완료 대기를 지원해줘서 문제를 어느정도 해결해줌
**
굳이 dequeue로 직접 구현하는 것 보다 나음

Better way 56, 57, 58, 59 (Wednesday, 220622)

언제 동시성이 필요할지 인식하는 방법을 알아두라(56)

단일스레드 프로그램 -> 동시 실행되는 여러 흐름의 프로그램
바꾸는 것은 어려운 일
***예를 들어, 블로킹 I/O -> I/O를 병렬로 수행
**
동시 실행되는 여러 실행 흐름 만들어 내는 과정 : 팬아웃(fan-out)
**동시 작업 단위 모두 끝날 때까지 기다리는 과정 : 팬인(fan-in)
**
57-60 챕터에서 이것들을 하는 파이썬 내장도구들을 알아보고 장단과 대안을 알아볼 것임

요구에 따라 팬아웃을 진행하려면 새로운 스레드를 생성하지 말아라(57)

스레드(thread, 쓰레드) 이용하여 팬아웃, 팬인 구현 가능하지만
**스레드 시작하고 실행하는데 비용이 많이 들고, 스레드가 많이 필요하면 메모리 많이 사용된다
**또한 스레드 사이를 조율하기 위해 Lock과 같은 method 필요하다
**
스레드를 시작하거나 종료하기를 기다리는 코드에게 스레드 실행 중 발생한 예외를 반환하는 내장 기능은 없고 그래서 스레드 디버깅은 너무 어렵

동시성과 Queue를 사용하기 위해 코드를 어떻게 리팩터링해야 하는지 이해하라(58)

*작업자 스레드 수를 고정하고 Queue를 사용하면 스레드를 사용할 때 팬인과 팬아웃의 규모확장성을 개선할 수 있음
**queue에 넣는 과정 - 팬아웃, queue에서 빼는 과정 - 팬인
*하지만 Queue를 사용토록 리팩터링하려면 많은 작업이 필요
*또한 Queue는 전체 I/O 병렬성의 정도를 제한하는 단점 있음(Queue크기만큼으로 제한)

#동시성을 위해 스레드가 필요한 경우에는 ThreadpoolExecutor를 사용하라(59)

*ThreadpoolExecutor를 사용하면 간단한 리팩터링으로 I/O 병렬성 활성화할 수 있고 동시성 팬아웃 시작시 스레드 시작 비용 줄일 수 있음(풀 있으니..)
*풀 사용하므로 스레드 직접 사용 시의 잠재적 메모리 낭비 없애주지만 max workers 의 개수 미리 지정하므로 I/O 병렬성을 제한하는 것은 어쩔 수 없음

Better way 60 (Thursday, 220630)

I/O를 할 때는 코루틴을 사용해 동시성을 높여라

I/O 동시성 처리를 위해 코루틴 사용
동시에 실행되는 것처럼 보이는(동시성) 함수 많이 쓸 수 있음
***코루틴은 async와 await 키워드를 사용해 구현되며 제너레이터를 싱행하기 위한 인프라를 사용
**
코루틴 시작으로 드는 비용은 함수 호출 비용뿐이고 1KB 미만의 메모리만 사용
**코루틴은 매 await 식에서 일시 중단되고, 일시 중단된 대기 가능성(awaitable)이 해결된 다음에 async함수로부터 실행을 재개한다는 것이 스레드와 다름(제너레이터의 yield동작과 비슷)
**
여러 분리된 async 함수가 서로 장단을 맞춰 실행되면 마치 모든 async함수가 동시에 실행되는 것처럼 보임
***이를 통해 파이썬 스레드의 동시성 송작을 흉내낼 수 있음
**
하지만 이런 코루틴은 스레드와 달리 메모리 부가 비용이나 시작비용, 컨텍스트 전환비용이 들지 않고 복잡한 락과 동기화 코드가 필요 없음
**
이를 가능케 하는 메커니즘은 이벤트 루프(event loop)임
**참고로 def 정의 앞에 async를 붙여서 코루틴 사용함
****안에서 async함수 호출할 때 await 붙여줌
**
이 await는 마치 yield처럼 호출 즉시 실행되지 않고 제너레이터를 반환하는 것과 같은데 이러한 실행 연기 메커니즘이 팬아웃을 수행
**asyncio 내장 라이브러리가 제공하는 gather함수는 팬인을 수행
**
gather에 대해 적용한 await 식은 이벤트 루프가 코루틴을 동시에 실행하면서 코루틴이 완료될 때마다 코루틴 실행을 재개하라고 요청(예제에서)
**asyncio.run 함수를 사용해 코루틴을 이벤트 루프상에서 실행하고 각 함수가 의존하는 I/O 수행 가능
**
코루틴 사용 시 기존코드에 await와 async만 붙여서 요구사항 충족할 수 있어 편리함
***코루틴은 외부환경에 대한 명령(I/O와 같은)과 원하는 명령을 수행하는 방법을 구현하는 것(이벤트 루프)를 분리해준다는 점에서 아주 좋음
**참고로 async 키워드로 정의한 함수를 코루틴이라 부르고 코루틴을 호출하는 호출자는 await키워드를 사용해 자신이 의존하는 코루틴의 결과를 받을 수 있음
**코루틴은 수만 개의 함수가 동시에 실행되는 것처럼 보이게 만드는 효과적 방법 제공
**I/O를 병렬화하면서 스레드로 I/O를 수행할 때 발생할 수 있는 문제를 극복하기 위해 팬인과 팬아웃에 코루틴 사용 가능

Better way 61 (Friday, 220701)

스레드를 사용한 I/O를 어떻게 asyncio로 포팅할 수 있는지 알아두라

파이썬의 비동기 지원은 파이썬 언어에 잘 통합되어 있음
**그러므로 스레드와 블로킹 I/O를 사용하는 코드를 코루틴과 비동기 I/O를 사용하는 코드로 옮기기 쉬움
**
재사용 가능한 try/finally 동작 -> contextlib과 with문 사용
**응답성 최대로 높이려면 asyncio 이벤트 루프를 블록하지 말아야함
**즉, 파이썬은 for 루프, with 문, 제너레이터, 컴프리헨션의 비동기 버전을 제공함
**
코루틴 안에서 기존 라이브러리 도우미 함수를 대신해 즉시 사용할 수 있는 대안 제공
**asyncio 내장 모듈을 사용하면 스레드와 블로킹 I/O를 사용하는 기존 코드를 코루틴과 비동기 I/O를 사용하는 코드로 쉽게 포팅할 수 있음

Better way 62, 63, 64 (Sunday, 220703)

asyncio로 쉽게 옮겨갈 수 있도록 스레드와 코루틴을 함께 사용하라

*마이그레이션 때 ->
블로킹 I/O에 스레드를 사용하는 부분과 비동기 I/O에 코루틴을 사용하는 부분이 서로 호환되면서 공존할 수 있어야 함
**스레드가 코루틴을 실행할 수 있어야 하고 코루틴이 스레드를 시작하거나 기다릴 수 있어야 함
**
asyncio에는 이런 상호작용을 쉽게 제공할 수 있는 도구 있음!
**
최상위 함수가 I/O를 호출하는 모든 부분(이벤트 루프가 블록될 가능성이 있는) asyncio.run_in_executor로 감쌈
**run_in_executor 호출이 사용하는 자원이나 콜백이 제대로 동기화 됐는지 확인 -< asyncio.run_coroutine_threadsafe 사용
**
호출 계층의 잎 쪽으로 내려가면서 중간에 있는 함수와 메서드를 코루틴으로 변환하며 get_event_loop와 run_in_executor 호출을 없애려고 시도

정리하면 run_in_executor 메서드(await을 사용해 완료 기다리는 것 가능한 메서드)를 사용하면 코루틴이 ThreadPoolExecutor 스레드 풀을 사용해 동기적인 함수를 호출할 수 있음
**이 기능을 활용하면 코드를 하향식으로 asyncio로 마이그레이션할 수 있음
*asyncio 이벤트 루프의 run_uitl_complete 메서드를 사용하면 동기적인 코드가 코루틴을 호출하고 완료를 기다릴 수 있음
**asyncio.run_coroutine_threadsafeeh rkxdms rlsmddmf wprhdgkwlaks tmfpem rudrPdptjeh dkswjsgkrp wkrehdgka
**
이 두 메서드를 활용하면 코드를 상향식으로 asyncio로 마이그레이션할 때 도움 됨

응답성을 최대로 높이려면 asyncio 이벤트 루프를 블록하지 말라

*시스템 콜(블로킹 I/O와 스레드 시작도 포함해서)을 코루틴으로 만들면 프로그램의 응답성이 좋아지고 사용자가 느끼는 지연 시간을 줄일 수 있음
*debug=True 파라미터를 asyncio.run에 넘기면 이벤트 루프가 빨리 반응하지 못하게 방해하는 코루틴을 식별할 수 있음

진정한 병렬성을 살리려면 concurrent.futures를 사용하라

*실행 속도 느림 문제를 CPU 코어 병렬화로 극복해줄 수 있음
하지만 파이썬은 GIL로 인해 스레드의 진정한 병렬화는 안 됨
**그래서 해결방안으로 C 언어를 사용한 확장 모듈로 작성하는 것
****GIL 신경 안 써도 되지만 C 재작성은 많은 비용이 듦->똑같이 작동하는지, 새로 생긴 버그 없는지 고아범위 테스트 있어야하므로..
**
concurrent.futures 내장 모듈을 통해 사용 쉬운 multiprocessing 가능
**
이 모듈 사용 시, 자식 프로세스로 다른 파이썬 인터프리터를 실행함으로써 파이썬에서 여러 CPU코어 활용 가능
**자식 프로세스의 GIL이 주 인터프리터 GIL과 분리되므로 각 자식 프로세스는 한 CPU 코어를 완전히 활용 가능

*기타..
concurrent.futures 모듈에 있는 ThreadPoolExecutor 대신 ProcessPoolExecutor로 바꾸면 속도 빨라짐
***그렇지만 사용할 수 있는 모든 방법을 다 써보기 전에는 multiprocessing이 제공하는 고급 기능을 시도하지 마시오
**
안에 내부적으로 돌아가는 것이 많으므로(이진데이터로 직렬화, 이를 로컬 소켓 통해 자식 스로페스에 복사, 이를 역직렬화, 모듈 임포트, 함수 실행 및 프로세스와 병렬로 실행, 결과를 이진 데이터로 직렬화, 부모에게 이를 전달, 부모는 다시 역직렬화, 자식에게 받은 것들 병합)

**격리란 프로그램의 다른 부분과 상태를 공유할 필요가 없는 함수를 실행한다는 뜻
**레버리지란 부모와 자식 사이에 주고 받아야 하는 데이터 크기는 작지만, 이 데이터로 인해 자식 프로세스가 계산해야 하는 연산의 양이 상당히 크다는 뜻

**즉, multiprocessing 모듈과 ProcessPoolExecutor 클래스는 병렬성을 활용하고자 엄청나게 많은 일을 함

Better way 37 (Wednesday, 220706)

내장 타입을 여러 단계로 내포시키기보다는 클래스를 합성하라

*딕셔너리, 긴 튜플, 다른 내장 타입이 복잡하게 내포된 데이터를 값으로 사용하는 딕셔너리를 만들지 말라
*완전한 클래스가 제공하는 유연성이 필요하지 않고 가벼운 불변 데이터 컨테이너가 필요하다면 namedtuple을 사용하라
*내부 상태를 표현하는 딕셔너리가 복잡해지면 이 데이터를 관리하는 코드를 여러 클래스로 나눠서 재작성하라!

Better way 38 (Thursday, 220707)

간단한 인터페이스의 경우 클래스 대신 함수를 받아라

*여러분이 전달한 함수를 실행하는 경우, 이런 함수를 훅(hook)라고 부름
**참고로, 상태가 없는 함수를 훅으로 사용하는 경우가 많음
**참고로, 일급 시민(first-class citizen)은 언어 안에서 아무런 제약 없이 사용할 수 있는 데이터 값을 뜻하는데,, 그냥 아무 제약이 없는 값을 의미함!

*python 클래스에 call 특별 메서드를 정의할 수 있음
**__call__을 사용하면 객체를 함수처럼 호출할 수 있음
**인스턴스에 대해 callable 내장 함수를 호출하면, 다른 일반 함수나 메서드와 마찬가지로 True가 반환. 이런 방식으로 정의, 호출될 수 있는 모든 객체를 호출 가능(callable)객체라고 부름

*정리
**파이썬의 여러 컴포넌트 사이에 간단한 인터페이스가 필요할 때는 간단한 함수를 사용
**파이썬 함수나 메서드는 일급 시민이므로 함수나 함수 참조를 식에 사용할 수 있음
**call 특별 메서드를 사용하면 클래스의 인스턴스인 객체를 일반 파이썬 함수처럼 호출할 수 있음
**상태를 유지하기 위한 함수가 필요한 경우에는 상태가 있는 클로저를 정의하는 대신 call 메서드가 있는 클래스를 정의할지 고려해보라

Better way 39, 40 (Saturday, 220709)

객체를 제너릭하게 구성하려면 @classmathod를 통한 다형성을 활용하라

*다형성을 사용하면 계층을 이루는 여러 클래스가 자신에게 맞는 유일한 메서드 버전을 구현할 수 있음
*도우미 함수를 활용하면 객체를 직접 만들고 연결할 수 있음
*객체를 구성할 수 있는 제너릭한 방법이 필요한데, 방법으로는 클래스 메서드(class method) 다형성을 사용하는 것

*정리
**파이썬의 클래스에는 생성자가 init 메서드 뿐임
**@classmethod를 사용하면 클래스에 다른 생성자를 정의할 수 있음
**클래스 메서드 다형성을 활용하면 여러 구체적인 하위 클래스의 객체를 만들고 연결하는 제너릭한 방법을 제공할 수 있음

#super로 부모 클래스를 초기화하라
*자식 클래스에서 부모 클래스를 초기화하는 오래된 방법은 바로 자식 인스턴스에서 부모 클래스의 init 메서드를 직접 호출하는 것
*이 접근 방법은 기본적인 클래스 계층의 경우에는 잘 작동하지만, 다른 경우, 특히 다중 상속에 의해 영향을 받는 경우 예측할 수 없는 방식으로 작동할 수 있음
**왜냐하면 모든 하위 클래스에서 init 호출 순서가 정해져 있지 않기 때문
*또 다이아몬드 상속으로 인해 문제가 생길 수 있음

*이러한 문제들을 해결하기 위해 파이썬에는 super 라는 내장 함수와 표준 메서드 결정 순서(Method Resolution Roder, MRO)가 있음
**특히 super 사용 시, 다이아몬드 계층의 콩통 상위 클래스를 단 한번만 호출하도록 보장함
**호출 순서는 이 클래스에 대한 MRO 정의를 따름
**super 에 파라미터를 제공해야 하는 유일한 경우는 자식 클래스에서 상위 클래스의 특정 기능에 접근해야 하는 경우 뿐

정리
**파이썬은 표준 메서드 결정 순서(MRO)를 활용해 사우이 클래스 초기화 순서와 다이아몬드 상속 문제를 해결
**부모 클래스를 초기화할 때는 super 내장 함수를 아무 인자 없이 호출하는 것이 좋음
**
super를 아무 인자 없이 호출하면 파이썬 컴파일러가 자동으로 올바른 파라미터를 넣어줌

Better way 41 (Monday, 220711)

기능을 합성할 때는 믹스인 클래스를 사용하라

파이썬 다중 상속 가능하지만 그래도 피하는 편이 좀 더 안정적
*다중 상속할 경우 믹스인 사용을 고려해라
**믹스인은 자식 클래스가 사용할 메서드 몇 개만 정의하는 클래스임
**
참고로 여기 예제의 객체 직렬화란 연속적 serial로 바꿔주는 것
**믹스인의 큰 장점은 제너릭 기능을 쉽게 연결할 수 있고 필요할 때 오버라이드(상속받아 일부 변경) 가능한 점
**믹스인 끼리도 합성 가능

*정리
**믹스인을 사용해 구현할 수 있는 기능을 인스턴스 애트리뷰트와 __init__을 사용하는 다중 상속을 통해 구현하지 말 것
**믹스인 클래스가 클래스별로 특화된 기능을 필요로 한다면 인스턴스 수준에서 끼워 넣을 수 있는 기능(정해진 메서드를 통해 해당 기능을 인스턴스가 제공하게 만듦)을 활용할 것
**믹스인에는 필요에 따라 인스턴스 메서드는 물론 클래스 메서드도 포함될 수 잇음
**믹스인을 합성하면 단순한 동작으로부터 더 복잡한 기능을 만들어낼 수 있음

Better way 42, 43 (Tuesday, 220712)

비공개 애트리뷰트보다는 공개 애트리뷰트를 사용하라

파이썬에서 애트리뷰트에 대한 가시성은 공개(public)과 배공개(private) 두가지
객체 뒤에 점 연산자(.)을 붙여 공개 애트리뷰트에 접근 가능
**애트리뷰트 이름 앞에 밑줄을 두개(__) 붙이면 비공개 필드가 됨
***클래스 외부에서 비공개 필드에 접근하면 예외 발생
***하위 클래스는 부모 클래스의 비공개 필드에 접근할 수 없음
**
하지만 비공개 애트리뷰트의 동작은 애트리뷰트 이름을 바꾸는 단순한 방식으로 구현되기 때문에
**이름을 잘 설정하는 방식을 알면 하위나 외부 클래스에서 원하는 클래스의 비공개 애트리뷰트에 접근 가능
**
이는 파이썬의 모토인 “우리는 모두 책임질줄 아는 성인이다”에 따른 것
**또한 파이썬은 애트리뷰트에 접근할 수 있는 언어 기능에 대한 훅을 제공하기 때문에 원할 경우 객체 내부 마음대로 주무를 수 있음
**
*그래서 명명을 통해 필드 앞에 밑줄이 하나만 있으면 관례적으로 보호(protected) 필드를 나타냄
**일반적으로 상속을 허용하는 클래스(부모클래스) 쪽에서 보호 애트리뷰트를 사용하고 오류를 내는 편이 더 나음
**부모 클래스 쪽에서 자식 클래스의 애트리뷰트 이름이 자신의 애트리뷰트 이름과 겹치는 일을 방지하기 위해 비공개 애트리뷰트를 사용할 수 있음

*정리
**파이썬 컴파일러는 비공개 애트리뷰트를 자식 클래스나 클래스 외부에서 사용하지 못하도록 엄격히 금지하지 않음
**여러분의 내부 API에 있는 클래스의 하위 클래스를 정의하는 사람들이 여러분이 제공하는 클래스의 애티르뷰트를 사용하지 못하도록 막기보다는 애트리뷰트를 사용해 더 많은 일을 할 수 있게 허용하는 것이 좋음
**비공개 애트리뷰트로 (외부나 하위 클래스의) 접근을 막으려고 시도하기보다는 보호된 필드를 사용하면서 문서에 적절한 가이드를 남기는 것이 선호됨
**여러분이 코드 작성을 제어할 수 없는 하위 클래스에서 이름 충돌이 일어나는 경우를 막고 싶을 때만 비공개 애트리뷰트를 사용할 것을 권함

커스텀 컨테이너 타입은 collections.abc를 상속하라

*파이썬 프로그래밍의 상당 부분은 데이터를 포함하는 클래스를 정의하고 이런 클래스에 속하는 객체들이 서로 상호작용하는 방법을 기술하는 것으로 이루어짐(어플리케이션 서비스들)
*모든 파이썬 클래스는 함수와 애트리뷰트를 함께 캡슐화하는 일종의 컨테이너라 할 수 있음
*파이썬은 데이터 관리용으로 리스트, 튜플, 집합, 딕셔너리 등의 내장 컨테이너 타입 제공
**이러한 내장 컨테이너를 상속받아 하위 클래스 정의하면 유용
*collections.abc 모듈 안에는 컨테이너 타입에 정의해야 하는 전형적인 메서드를 모두 제공하는 추상 기반 클래스 정의가 여러가지 들어 있음
**collections.abc 모듈 이용하면 하위 클래스에서 깜빡하여 구현하지 못 한 것을 잡아줌

*정리
**간편하게 사용할 경우에는 파이썬 컨테이너 타입(리스트나 딕셔너리 등)을 직접 상속하라
**커스텀 컨테이너를 제대로 구현하려면 수많은 메서드를 구현해야 한다는 점에 주의하라
**커스텀 컨테이너 타입이 coolection.abc에 정의된 인터페이스를 상속하면 커스텀 컨테이너 타입이 정상적으로 작동하기 위해 필요한 인터페이스와 기능을 제대로 구현하도록 보장할 수 있음

Better way 11 (Tuesday, 220712)

시퀀스를 슬라이싱하는 방법을 익혀라

*시작 인덱스에 있는 원소는 슬라이스에 포함되지만 끝 인덱스의 원소는 포함 안 됨
*맨 앞부터 슬라이싱 할 때는 0 생략하라
*쓸데없이 끝 인덱스 적지 마라
*리스트 끝에서부터 원소 찾고 싶을 때는 음수 인덱스 사용하면 됨
*인덱스 범위 넘어서는 시작과 끝 인덱스는 조용히 무시됨
*리스트 슬라이싱한 결과는 완전히 새로운 리스트임
*시작과 끝 인덱스가 없는 슬라이스를 대입하면 참조하는 리스트가 되고 참조 대상이 바뀌면 피참조도 바뀜

Better way 12 (Wednesday, 220713)

스트라이드와 슬라이스를 한 식에 함께 사용하지 마라

*스트라이드(stride)는 일정한 간격을 두고 슬라이싱을 할 수 있는 특별한 구문
**odds = x[::2] 이런 식
*슬라이싱 구문에 스트라이드까지 들어가면 너무 혼잡하니 그렇게는 쓰지 마라
**시작, 끝값 증가값과 함께 사용하지 말 것 권장

Better way 13 (Thursday, 220714)

슬라이싱보다는 나머지를 모두 잡아내는 언패킹을 사용하라

*예시 : one, two, *others = [1, 2, 3, 4, 5, 6, 7] #one=1, two=2, others=[3,4,5,6,7] 이렇게 별표 식 써서 언패킹하면 잘 됨

Better way 14 (Friday, 220715)

복잡한 기준을 사용해 정렬할 때는 key 파라미터를 사용하라

sort함수 key 파라미터에 lambda 함수 사용해주면 편리
예시 : tools.sort(key = lambda x: x.name) # x의 name 애트리뷰트 알파뱃 순으로 오름차순 정렬됨
***만약 대소문자 구분 안 해주고 싶으면 x.name.lower() 해주면 됨.. 왜냐하면 그냥 소팅해주면 대문자가 소문자 보다 앞에 오기 때문
*여러 가지로 소팅해주고 싶다면? 튜플 사용해주면 됨! **예시 : power_tools.sort(key=lambda x : (x.weight, x.name))
***근데 이 경우 오름차순 또는 내림차순 한가지만 적용됨
**
수로 정의된 애트리뷰트가 있다면 -를 붙여줘서 오름차순 내림차순이 섞인 정렬을 가능케 할 수는 있음
**위 경우가 아니라면 sort를 두번 호출하는 방식을 써야하는데 이 때는 원하는 sort기준 역순으로 호출 두번해야함
**
예: power_tools.sort(key=lambda x: x.name)
*** power_tools.sort(key=lambda x: x.weight, reverse=True)
**
* 이 예는 weight기준 내림차순, 이름 기준 오름차순으로 정렬한 것

Better way 15, 16, 17, 18 (Monday, 220718)

딕셔너리 삽입 순서에 의존할 때는 조심하라

*파이썬 3.6 이전에는 난수 seed를 사용하는 해시 테이블 알고리즘이어서 삽입 순서와 일치 x
*하지만 3.6부터 딕셔너리 삽입 순서가 보존되도록 개선됨
*파이썬 dict는 아니지만 비슷한 객체를 만들 경우 삽입 순서가 보존된다고 가정할 수 없으니 주의하라

in을 사용하고 딕셔너리 키가 없을 때 KeyError를 처리하기보다는 get을 사용하라

*if (names := votes.get(key)) is None: votes[key] = names = [] **이런식으로 get사용하고, 더해서 월러스 연산을 통한 대입식 사용하면 짧고 가독성을 높여줄 수 있음
*setdefault를 통해 초기값을 지정해줄 수 있음 -> data.setdefault(key, value) #value가 초기값
**하지만 defaultdict를 사용하는 것을 권장.. 이유는 다음 better way에 있음

내부 상태에서 원소가 없는 경우를 처리할 때는 setdefault보다 defaultdict를 사용하라

*키로 어떤 값이 들어올지 모르는 딕셔너리를 관리해야 하는데 collections 내장 모듈에 있는 defaultdict 인스턴스가 괜찮은 선택지임
*임의의 키가 들어 있는 딕셔너리가 여러분에게 전달됐고, 그 딕셔너리가 어떻게 새엇ㅇ됐는지 모르는 경우, 딕셔너리의 우너소에 접근하려면 우선 get을 사용
*하지만 setdefault가 더 짧은 코드를 만들어내는 몇 가지 경우에는 setdefault를 사용하는 것도 고려는 해볼만 함(setdefault가 범용성, 유연성은 떨어짐, 다음참고)

__missing__을 사용해 키에 따라 다른 디폴트 값을 생성하는 방법을 알아두라

*디폴트 값을 만드는 계산 비용이 높거나 만드는 과정에서 예외가 발생할 수 있는 상황에서는 dict의 setdefault 메서드를 사용하지 말라
*defaultdict에 전달되는 함수는 인자를 받지 않음 따라서 접근에 사용한 키 값에 맞는 디폴트 값을 생성하는 것은 불가능
*디폴트 키를 만들 때 어떤 키를 사용했는지 반드시 알아야 하는 상황이라면 직접 dict의 하위 클래스와 __missing__메서드를 정의하면 됨

Better way 19

*참고로 함수는 재사용과 리팩터링 쉬워지게 함 ㅇㅇ

함수가 여러 값을 반환하는 경우 절대로 네 값 이상을 언패킹하지 말라

*더 많은 값을 언패킹 해야 한다면 경량 클래스나 namedtuple을 사용(Better way 37: 내장 타입을 여러 단계로 내포시키기보다는 클래스를 합성하라 참고)하고 함수도 이런 값을 반환하게 만드는 것이 나음
*호출하는 쪽에서 파이썬 언패킹 구문 쓸 수 있음
*별표 식을 사용해 언패킹 가능
*언패킹 구문에 변수가 네 개 이상 나오면 실수하기 쉬우므로 변수를 네 개 이상 사용하는 대신 작은 클래스를 반환하거나 namedtuple 인스턴스를 반환하라

Better way 20

None을 반환하기보다는 예외를 발생시켜라

*특별한 의미를 표시하는 None을 반환하는 함수를 사용하면 None과 다른 값(예: 0이나 빈 분자열)이 조건문에서 False로 평가도리 수 있기 때문에 실수하기 쉬움
*None을 반환하는 대신 예외를 발생시키고 호출자가 예외를 제대로 처리하도록 해야 함
*None을 반환하지 않는 다는 사실을 타입 애너테이션으로 명시할 수 있음

Better way 21

변수 영역과 클로저의 상호작용 방식을 이해하라

*클로저(closure)란 자신이 정의된 영역 밖의 변수를 참조하는 함수
*파이썬은 시퀀스를 비교할 때 0번 인덱스에 있는 값을 비굫나 다음, 이 값이 같으면 다시 1번 인덱스에 잇는 값을 비교
*영역 지정 버그(scoping bug, 스코핑 버그)라고도 불리는 ‘클로저 안에서 기존 변수 가져올 경우 새로운 변수 정의한 것으로 취급’은 지극히 의도한 동작
*nonlocal사용하면 클로저 밖으로 데이터 끌어낼 수 잇음
**하지만 지양되어야함.. 특히 함수가 길고 nonlocal문이 지정한 변수와 대입이 이뤄지는 위치의 거리가 멀면 함수 동작을 이해하기 더 힘들어지므로
*요약
**클로저 함수는 자신이 정의된 영역 외부에서 정의된 변수도 참조할 수 있음
**기본적으로 클로저 내부에 사용한 대입문은 클로저를 감싸는 영역에 영향을 끼칠 수 없음
**클로저가 자신을 감싸는 영역의 변수를 변경한다는 사실을 표시할 때는 nonlocal 문을 사용해야 함
**간단한 함수가 아닌 경우에는 nonlocal 문을 사용하지 말 것을 권장

Better way 22 (Friday, 220722)

변수 위치 인자를 사용해 시각적인 잡음을 줄여라

위치 인자(positional argument)(또는 위치 기반 인자)를 가변적으로 받을 수 있으면 함수 호출이 더 깔끔해지고 시각적 잡음도 줄어든다(이런 위치 인자를 가변 인자나 스타 인자라고 부름 - *args)
*사용 예는 아래와 같음
**def log(message, *values):
**log(‘내 숫자는’, 1, 2)
**log(‘안녕’)
*단점도 있음
**선택적인 위치 인자가 함수에 전달되기 전에 항상 튜플로 변환됨
**이렇게 만들어지는 튜플은 제너레이터가 만들어낸 모든 값을 포함하며, 이로 인해 메모리를 아주 많이 소비하거나 프로그램이 중단돼버릴 수 있음
**또한 함수에 새로운 위치 인자를 추가하면 해당 함수를 호출하는 모든 코드를 변경해야하는 문제도 있음
*정리
**def문에서 *args를 사용하면 함수가 가변 위치 기반 인자를 받을 수 있음
**스타 연산자(
연산자)를 사용하면 함수에게 시퀀스 내의 원소들을 전달할 수 있음
**제너레이터에 *연산자를 사용하면 프로그램이 메모리를 모두 소진하고 중단될 수 있음
**새로운 위치 기반 인자를 넣으면 감지하기 힘든 버그가 생길 수 있음

Better way 23 (Monday, 220725)

키워드 인자로 선택적인 기능을 제공하라

*키워드 인자를 넘기는 순서는 관계없음
*위치 인자를 필요에 따라 섞어 쓸 수도 있음
*단 위치 기반 인자를 지정하려면 키워드 인자보다 앞에 지정해야 함
*함수를 호출하고 싶다면 연산자를 사용할 수 있음
*스타스타연산자(
연산자)는 파이썬이 딕셔너리에 들어있는 값을 함수에 전달하되 각 값에 대응하는 키를 키워드로 사용하도록 명령함
*다만 중복되는 인자가 없어야 함
*스타스타 연산자를 여러번 사용할 수도 있음
*다만 겹치는 키가 없어야 함
*아무 키워드 인자나 받는 함수를 만들고 싶다면 **kwargs라는 파라미터를 사용
*장점 정리
**호출의 의미를 명확히 알려줄 수 있음
**디폴트 값을 지정할 수 있음
**하위 호환성을 제공하면서 함수 파라미터를 확장할 수 있는 방법을 제공
*선택적인 인자를 지정하는 최선의 방법은 항상 키워드 인자를 사용하고 위치 인자를 절대 사용하지 않는 것

Better way 24 (Thursday, 220728)

None과 독스트링을 사용해 동적인 디폴트 인자를 지정하라

*키워드 인자의 값으로 정적으로 정해지지 않는 타입의 값을 써야할 때가 있음
*디폴트 인자는 함수 여러번 호출할 때 다른 시간 찍히지 않음, 함수가 정의되는 시점에 datetime.now가 단 한번만 호출되기 때문에 타임스탬프는 항상 같은 것이 찍힘
*일반적인 관례는 디폴트 값으로 None을 지정하고 실제 동작을 독스트링에 문서화하는 것
*인자가 None인 경우에는 적절한 디폴트 값을 할당해야함
**if when is None: ** when = datetime.now()
**print(f’{when}: {message}’)
*디폴트 인자 값으로 None을 사용하는 것은 인자가 가변적인(mutable) 경우 특히 중요
*디폴트 값은 단 한 번만 평가되기 때문에 default에 지정된 딕셔너리가 decode 호출에 모두 공유됨
*디폴트 값으로 None을 지정하고 함수의 독스트링에 동작 방식을 기술하는 것이 관례
*정리
**디폴트 인자 값은 함수 정의가 속한 모듈이 로드되는 시점에 단 한 번만 평가됨
**동적인 값을 가질 수 있는 키워드 인자의 디폴트 값을 표현할 때는 None을 사용하는 것 추천
**실제 동작인 디폴트 인자가 어떻게 동작하는지는 문서화해둘 것

Better way 25 (Friday, 220729)

위치로만 인자를 지정하게 하거나 키워드로만 인자를 지정하게 해서 함수 호출을 명확하게 만들라

인자목록에 있는 * 기호는 위치 인자의 마지막과 키워드만 사용하는 인자의 시작을 구분해줌
**def safe_division_c(number, divisor, *, ignore_overflow=False, ignore_zero_division=False)
*인자 목록의 / 기호는 위치로만 지정하는 인자의 끝을 표시함
*인자 목록에서 /와 *기호 사이에 있는 모든 파라미터는 위치를 사용해 전달할 수도 있고 이름을 키워드로 사용해 전달할 수도 있음
**def safe_division_c(number, divisor, /, ndigits=10,
, ignore_overflow=False, ignore_zero_division=False)

Better way 26 (Monday, 220801)

functions.wrap을 사용해 데코레이터를 정의하라

파이썬은 함수에 적용할 수 있는 데코레이터(decorator)를 정의하는 특별한 구문을 제공
*데코레이터는 자신이 감싸고 있는 함수가 호출되기 전과 후에 코드를 추가로 실행해줌
*데코레이터는 자신이 감싸고 잇는 함수의 입력인자, 반환값, 함수에서 발생한 오류에 접근할 수 있음
*함수의 의미를 강화하거나 디버깅을 하거나 함수를 등록하는 등의 일에 이런 기능을 유용하게 쓸 수 있음
**특히 함수 호출이 재귀적으로 내포되는 경우를 디버깅할 때 유용
**특히 *args와 **kwargs를 사용해 감싸진 함수의 모든 파라미터를 전달받을 수 있음
*이 데코레이터를 함수에 적용할 때는 @ 기호를 사용함
** @기호를 사용하는 것은 함수에 대해 데코레이터를 호출한 후, 데코레이터가 반환한 결과를 원래 함수가 속해야 하는영역에 원래 함수와 같은 이름으로 등록하는 것과 같음
*의도하지 않은 부작용이 생기기도 하는데 앞에서 호출한 감싸진 함수의 이름이 달라지는 것
**이런 부작용은 디버거와 같이 인트로스펙션을 하는 도구에서 문제가됨
**
인트로스펙션은 실행 시점에 프로그램이 어떻게 실행되는지 관찰하는 것을 뜻함
**위와 비슷하게 실행 시점에 사용되기 때문에 가끔 혼동할 수 있는 용어로는 리플렉션이 있고 리플렉션은 실행 시점에 프로그램을 조작하는 것을 뜻함
**문제 해결 방법은 functools 내장 모듈에 정의된 wraps 도우미 함수를 사용한 ㄴ것
**이 함수는 데코레이터 작성을 돕는 데코레이터
**
from functools import wraps
**def trace(func):
**
@wraps(func)
** def wrapper(args, *kwargs):
**
….
** return wrapper
**
@trace
**def fibonacci(n):
**

*파이썬 함수에는 이 예제와 같이 더 많은 표준 애트리뷰트가 있음( name, module, annotations )
*함수의 인터페이스를 처리하려면 이런 애트리뷰트도 보존돼야 함
*wraps를 사용하면 이 모든 애트리뷰트를 제대로 복사해서 함수가 제대로 작동하게 해줌
*정리
**파이썬 데코레이터는 실행 시점에 함수가 다른 함수를 변경할 수 있게 해주는 구문임
**데코레이터를 사용하면 디버거 등 인트로스펙션을 사용하는 도구가 잘못 작동할 수 있음
**직접 데코레이터를 구현할 때 인트로스펙션에서 문제가 생기지 않길 바란다면 functools 내장 모듈의 wraps 데코레이터를 사용해야 함

메타클래스와 애트리뷰트

*파이썬 특징으로 메타클래스 들 수 있음
**메타클래스란 파이썬의 class문을 가로채서 클래스가 정의될 때마다 특별한 동작을 제공하는 것
*또다른 파이썬 기능으로는 동적으로 애트리뷰트 접근을 커스텀화해주는 내장 기능
*파이썬의 객체지향과 메타클래스, 애트리뷰트를 함께 사용하면 간단한 클래스를 복잡한 클래스로 쉽게 변환할 수 있음
*단, 동적인 애트리뷰트를 객체 오버라이드하면 예기치 못한 부작용이 생기기도 함
**쉽게 이해하기 어려워짐

Better way 44 (Tuesday, 220802)

세터와 게터 메서드 대신 평범한 애트리뷰트를 사용하라

*게터와 세터 같은 유틸리티 메서드를 쓰면 기능을 캡슐화하고, 필드 사용을 검증하고, 경계를 설정하기 쉬워짐
**단, 클래스를 호출하는 쪽에 영향을 미치지 않음을 보장하는 것이 중요
*파이썬에서는 명시적인 세터나 게터 메서드를 구현할 필요가 없음
**항상 단순한 공개 애트리뷰트로부터 구현을 시작한는 것을 권장
*애트리뷰트가 설정될 때 특별한 기능을 수행해야 한다면 @property 데코레이터와 대응하는 setter 애트리뷰트로 옮겨갈 수 있음
**@property
**def voltage(self):
** return self._voltage
**@voltage.setter
**def voltage(self, voltage):
** self._voltage = voltage
** self.current = self._voltage / self.ohms
*게터나 세터를 정의할 때 가장 좋은 정책은 관련이 있는 객체 상태를 @property.setter 메서드 안에서만 변경하는 것
**동적으로 모듈을 임포트하거나, 아주 시간이 오래 걸린느 도우미 함수를 호출하거나, I/O를 수행하거나, 비용이 매우 많이 드는 데이터베이스 질의를 수행하는 등 호출하는 쪽에서 예상할 수 없는 부작용을 만들어내면 안 됨
*property의 가장 큰 단점은 애트리뷰트를 처리하는 메서드가 하위클래스 사이에서만 공유될 수 있다는 것
**하지만 이 경우 재사용 가능한 프로퍼티 로직을 구현할 때는 물론 다른 여러 용도에도 사용할 수 있는 디스크립터(descriptor)가 제공되어, 이걸 사용하면 됨
*정리
**새로운 인터페이스를 정의할 때는 간단한 공개 애트리뷰트에서 시작하고, 세터나 게터 메서드를 가급적 사용하지 말라
**객체에 있는 애트리뷰트에 접근할 때 특별한 동작이 필요하면 @property로 이를 구현할 수 있음
**@property 메서드를 만들 때는 최소 놀람의 법칙을 따르고 이상한 부작용을 만들어내지 말라
**@property 메서드가 빠르게 실행되도록 유지하라. 느리거나 복잡한 작업의 경우(특히 I/O를 수행하는 등의 부수효과가 있는 경우)에는 프로퍼티 대신 일반적인 메서드를 사용하라

Better way 45 (Wednesday, 220803)

애트리뷰트를 리팩터링하는 대신 @property를 사용하라

*내장 @property를 사용하면 로직을 수행하는 애트리뷰트를 정의할 수 있음
**애트리뷰트를 그때그때 요청에 따라 계산해 제공하도록 바꾸는 것이 그 예

*객체가 처음부터 제대로 인터페이스를 제공하지 않거나 아무 기능도 없는 데이터 컨테이너 역할만 하는 경우가 자주 발생함(시간이 지나 코드가 커지건, 프로그램 다루는 영역 넓어지거나 등)

*@property는 실제 세계에서 마주치는 문제를 해결할 때 도움이 됨
**하지만 과용은 금물임
**이 메서드를 반복해서 확장해나가는 대신 리팩터링 필요

*정리
**@property를 사용해 기존 인스턴스 애트리뷰트에 새로운 기능을 제공할 수 있음
**@property를 사용해 데이터 모델을 점진적으로 개선할 것
**@property 메서드를 너무 과하게 쓰고 있다면, 클래스와 클래스를 사용하는 모든 코드의 리팩터링을 고려해야함

Better way 46 (Thursday, 220804)

재사용 가능한 @property 메서드를 만들려면 디스크립터를 사용하라

*property 내장 기능의 가장 큰 문제점은 재사용성
**@property가 데코레이션하는 메서드를 같은 클래스에 속하는 여러 애트리뷰트로 사용할 수 없음
**또한 서로 무관한 클래스 사이에서 @property 데코레이터를 사용한 메서드를 재사용할 수 없음
*재사용을 위해서 더 나은 방법은 디스크립터를 사용하는 것
*디스크립터 프로토콜은 get__과 __set 메서드를 제공하고 이 두 메서드를 사용하면 재사용 가능

*정리
**@property 메서드의 동작과 검증 기능을 재사용하고 싶다면 디스크립터 클래스를 만들어야 함
**디스크립터 클래스를 만들 때는 메모리 누수를 방지하기 위해 WeakKeyDictionary를 사용해야함
**__getattribute__가 디스크립터 프로토콜을 사용해 애트리뷰트 값을 읽거나 설정하는 방식을 정확히 이해하는 것이 좋음

Better way 47 (Monday, 220808)

지연 계산 애트리뷰트가 필요하면 getattr, getattribute, __setattr__을 사용하라

*파이썬 object 훅을 사용하면 시스템에 서로 접합하는 제너릭 코드를 쉽게 작성할 수 있음
*스키마를 표현하는 클래스를 더 일반화할 때, 파이썬에서는 getattr__이라는 특별한 메서드를 사용하면 됨
**어떤 클래스 안에 __getattr
메서드 정의가 있으면, 이 객체의 인스턴스 딕셔너리에서 찾을 수 없는 애트리뷰트에 접근할 때마다 getattr__이 호출됨
**이러한 기능은 스키마가 없는 데이터에 지연 계산으로 접근하는 등의 활용이 필요할 때 아주 유용
**스키마가 없는 데이터에 접근하면 __getattr__이 한 번 실행되면서 프로퍼티를 적재하는 힘든 작업을 모두 처리
**
이후 모든 데이터 접근은 기존 결과를 읽게 됨
*트랜젝션이 필요한 경우 기존의 애트리뷰트를 확인하는 빠른 경로로 객체의 인스턴스 딕셔너리를 사요하기 때문에 __getattr
훅으로는 이런 기능을 안정적으로 만들 수 없음
**이 경우 __getattribute__라는 다른 object 훅을 제공
*__getattribute__와 __setattr__의 문제점은 원하든 원하지 않든 어떤 객체의 모든 애트리뷰트에 접근할 때마다 함수가 호출된다는 것

*정리
**__getattr__과 __setattr__을 사용해 객체의 애트리뷰트를 지연해 가져오거나 저장할 수 있음
**__getattr__은 애트리뷰트가 존재하지 않을 때만 호출되지만, __getattribute__는 애트리뷰트를 읽을 때마다 항상 호출된다는 점을 이해해야함
**__getattribute__와 __setattr__에서 무한 재귀를 피하려면 super()에 있는(즉, object 클래스에 있는) 메서드를 사용해 인스턴스 애트리뷰트에 접근해야 함

Better way 48 (Tuesday, 220809)

__init_subclass__를 사용해 하위 클래스를 검증하라

*매타클래스의 가장 간단한 활용법 중 하나는 어떤 클래스가 제대로 구현됐는지 검증하는 것
*표준 파이썬 메타클래스 방식의 또 다른 문제점은 클래스 정의마다 메타클래스를 단 하나만 지정할 수 있다는 것
type 정의를 복잡한 계츠응로 설계함으로써 이런 문제를 해결할 수도 있음
**하지만 이런 접근 방법은 합성성(composability)를 해침
**모든 로직을 중복 정의해야하므로, 코드 재사용이 줄어들고 불필요한 준비 코드가 늘어남
***__init_subclass__ 특별 클래스 메서드를 사용하면 이 문제도 해결할 수 있음
**
이는 다중 상속과도 잘 어우러짐

정리
**메타클래스의 new 메서드는 class 문의 모든 본문이 처리된 직후에 호출됨
**메타클래스를 사용해 클래스가 정의된 직후이면서 클래스가 생성되기 직전인 시점에 클래스 정의를 변경할 수 있음
**
하지만 메타클래스는 원하는 목적을 달성하기에 너무 복잡해지는 경우가 많음
**init_subclass 를 사용해 하위 클래스가 정의된 직후, 하위 클래스 타입이 만들어지기 직전에 해당 클래스가 원하는 요건을 잘 갖췄는지 확인해야 함
**init_subclass 정의 안에서 super().__init_subclass__를 호출해 여러 계층에 걸쳐 클래스를 검증하고 다중 상속을 제대로 처리해야 함

Better way 49 (Wednesday, 220810)

__init_subclass__를 사용해 클래스 확장을 등록하라

*메타클래스의 다른 용례로 프로그램이 자동으로 타입을 등록하는 것이 있음
*클래스 등록에 init_subclass(또는 메타클래스)를 사용하면, 상속 트리가 제대로 돼 있는 한 클래스 등록을 잊어버릴 일이 없다고 보장할 수 있음
*객체-관계 매핑(ORM), 확장성 플러그인 시스템, 콜백 훅에도 마찮가지로 잘 작동함

*정리
**클래스 등록은 파이썬 프로그램을 모듈화할 때 유용한 패턴
**메타클래스를 사용하면, 프로그램 안에서 기반 클래스를 상속한 하위 클래스가 정의될 때마다 등록 코드를 자동으로 실행할 수 있음
**메타클래스를 클래스 등록에 사용하면 클래스 등록 함수를 호출하지 않아서 생기는 오류를 피할 수 있음
**표준적인 메타클래스 방식보다는 init_subclass__가 더 나음.
***__init_subclass
쪽이 더 깔끔하고 초보자가 이해하기에도 더 쉬움

Better way 50 (Thursday, 220811)

__set_name__으로 클래스 애트리뷰트를 표시하라

*메타클래스를 사용하면 class 문에 직접 훅을 걸어서 class 본문이 끝나자마자 필욯나 동작을 수행할 수 있음
**__init_subclass__를 사용해 하위 클래스를 검증하라에 동작 참조
*필드 이름을 여러번 수동으로 지정하는 대신 메타클래스를 사용해 디스크립터의 Field.name과 Field.internal_name을 자동으로 대입할 수 있음
*클래스가 정의될 때마다 파이썬은 해당 클래스 안에 들어 잇는 디스크립터 인스턴스의 __set_name__을 호출함
*__set_name__은 디스크립터 인스턴스를 소유중인 클래스와 디스크립터 인스턴스가 대입될 애트리뷰트 이름을 인자로 받음

*정리
**메타클래스를 사용하면 어떤 클래스가 완전히 정의되기 전에 클래스의 애트리뷰트를 변경할 수 있음
**디스크립터와 메타클래스를 조합하면 강력한 실행 시점 코드 검사와 선언적인 동작을 만들 수 있음
**set_name 특별 메서드를 디스크립터 클래스에 정의하면 디스크립터가 포함횐 클래스의 프로퍼티 이름을 처리할 수 있음
**디스크립터가 변경한 클래스의 인스턴스 딕셔너리에 데이터를 저장하게 만들면 메모리 누수를 피할 수 있고, weakref 내장 메서드를 사용하지 않아도 됨

Better way 51 (Friday, 220812)

합성 가능한 클래스 확장이 필요하면 메타클래스보다는 클래스 데코레이터를 사용하라

메타클래스를 사용하면 클래스 생성을 다양한 방법으로 커스텀화할 수 있지만 처리할 수 없는 경우 있음
*해결 방법은 메타클래스를 사용해 클래스에 속한 모든 메서드를 자동으로 감싸는 것
*하지만 라이브러리에 있는 메타클래스를 사용하는 경우에는 코들르 변경할 수 없기 때문에 이 방법 사용 못 함
*이를 해결하기 위해 파이썬은 클래스 데코레이터를 지원함
**클래스 데코레이터는 함수 데코레이터처럼 사용할 수 있음
**
클래스 선언 앞에 @ 기호와 데코레이터 함수를 적으면 됨
*클래스를 확장하면서 합성이 가능한 방법을 찾고 있따면 클래스 데코레이터가 가장 적합한 도구임

*정리
**클래스 데코레이터는 class 인스턴스를 파라미터로 받아서 이 클래스를 변경한 클래스나 새로운 클래스를 반환해주는 간단한 함수임
**준비 코드를 최소화하면서 클래스 내부의 모든 메서드나 애트리뷰트를 변경하고 싶을 때 클래스 데코레이터가 유용
**메타클래스는 서로 쉽게 합성할 수 없지만, 여러 클래스 데코레이터를 충돌 없이 사용해 똑같은 클래스를 확장할 수 있음

8. 강건성과 성능

오류가 발생해도 문제가 없도록 프로덕션화(productionize)해 코드에 방탄 처리를 해야 함
파이썬에서는 프로그램을 강건하게(robust) 만드는 데 도움이 되는 다양한 기능과 모듈이 제공됨
*강건성에는 규모 확장성(scalability)과 성능이라는 차원이 포함됨

Better way 65 (Monday, 220905)

try/except/else/finally의 각 블록을 잘 활용하라

파이썬에서 예외를 처리하는 과정에서는 특정 동작을 수행하고 싶은 네가지 경우가 있으며
각각 try, except, else, finally 라는 네 블록이 여기에 해당
서로 다른 목적에 쓰이며 다양하게 조합하면 유용

*finally 블록
예외가 발생하더라도 정리 코드를 실행해야 한다면 try/finally 를 사용하라
**예외가 발생하면 호출쪽에 예외 전달하기 전에 finally 블록에 있는 메서드가 먼저 호출됨

*else 블록
코드에서 처리할 예외와 호출 스택을 거슬러 올라가며 전달할 예외를 명확히 구분하기 위해 try/catch/else 사용 권장
try 블록이 예외를 발생시키지 않으면 else블록이 실행됨
else블록을 사용하면 try 블록 안에 들어갈 코드를 최소화할 수 있음
예외를 서로 구분할 수 있으므로 가독성이 좋아짐
**참고로 raise는 에러를 발생시킴(에러메세지 알려주는 기능)

*모든 요소 한꺼번에 사용(4가지 모두)
복합적인 문장 안에 모든 요소를 다 넣고 싶다면 try/except/else/finally를 모두 사용
**예를 들어, 수행할 작업에 대한 설명을 파일에서 읽어 처리한 다음, 원본 파일 자체를 변경하고 싶은 경우
**이 경우, try 블록을 사용해 파일을 읽고 처리하며, try 블록 안에서 발생할 것으로 예상되는 예외를 처리하고자 except 블록을 사용
**else 블록을 사용해 원본 파일의 내용을 변경하고
**이 과정에서 오류가 새익면 호출한 쪽에 예외를 돌려줌
**finally 블록은 파일 핸들을 닫음
else블록에서 결과 데이터를 파일에 다시 쓰는 동안 예외가 발생하면, 예상대로 finally 블록이 실행돼 파일 핸들 닫아줌

*정리
**try/finally 복합문을 사용하면 try 블록이 실행되는 동안 예외가 발생하든 발생하지 않든 정리 코드를 실행할 수 있음
**else 블록을 사용하면 try 블록 안에 넣을 코드를 최소화하고, try/except 블록과 성공적인 경우에 수행해야 할 코드를 시각적으로 구분할 수 있음
**try 블록이 성공적으로 처리되고 finally 블록이 공통적인 정리 작업을 수행하기 전에 실행해야 하는 동작이 있는 경우 else 블록을 사용할 수 있음

Better way 66 (Thursday, 220908)

재사용 가능한 try/finally 동작을 원한다면 contextlib과 with 문을 사용하라

*파이썬의 with 문은 코드가 특별한 컨텍스트(context) 안에서 실행되는 경우를 표현
**예를 들어, 상호 배제 락(뮤텍스)을 with 문 안에서 사용하면 락을 소유했을 때만 코드 블록이 실행되는 것을 의미
*contextlib 내장 모듈을 사용하면 직접 만든 객체나 함수를 with 문에 쉽게 쓸 수 있음
**contextlib 모듈을 with 문에 쓸 수 있는 함수를 간단히 만들 수 있는 contextmanager 데코레이터를 제공
**이 데코레이터를 사용하는 방법이 enter__와 __exit 특별 메서드를 사용해 새로 클래스를 정의하는 방법보다 훨씬 쉬움

from contextlib import contextmanager  


@contextmanager  
def debug_logging(level):  
    logger = logging.getLogger()
    old_level = logger.getEffectiveLevel()  
    logger.setLevel(level)  
    try:  
        yield  
    finally:  
        logger.setLevel(old_level)  

*yield 식은 with 블록의 내용이 실행되는 부분을 지정

with와 대상 변수 함께 사용하기

*with 문에 전달된 컨텍스트 매니저가 객체를 반환할 수 있음
**이렇게 반환된 객체는 with 복합문의 일부로 지정된 지역 변수에 대입됨
**이를 통해 with 블록 안에서 실행되는 코드가 직접 컨텍스트 객체와 상호작용할 수 있음
*with문에 open을 전달하면 open은 with 문에서 as를 통해 대상으로 지정된 변수에게 파일 핸들을 전달하고
**with 블록에서 나갈 때 이 핸들을 닫음
**이러한 접근 방법은 코드 실행이 with 문을 벗어날 때 결국에는 파일이 닫힌다고 확신할 수 있게 해줌
**파일 핸들이 열린 채로 실행되는 코드의 양을 줄이도록 동기부여해줌
**일반적으로 파일 핸들이 열려 있는 부분을 줄이면 좋음
*상태를 격리할 수 있고, 컨텍스트를 만드는 부분과 컨텍스트 안에서 실행되는 코드를 서로 분리할 수 있는 것이 with 문의 또다른 장점

정리
**with문을 사용하면 try/finally 블록을 통해 사용해야 하는 로직을 재활용하면서 시각적인 잡음도 줄일 수 있음
**contextlib 내장 모듈이 제공하는 contextmanager 데코레이터를 사용하면 직접 만든 함수를 with문에 사용할 수 있음
**컨텍스트 매니저가 yield하는 값은 with 문의 as 부분에 전달됨
**
이를 활용하면 특별한 컨텍스트 내부에서 실행되는 코드 안에서 직접 그 컨텍스트에 접근할 수 있음

Better way 67 (Friday, 220909)

지역 시간에는 time 보다는 datetime을 사용하라

*협정 세계시(Coordinated Universal Time, UTC)는 시간대(timezone)와 독립적으로 시간 나태니는 표준
*현재 위치를 기준으로 시간을 따지는 인간에게 UTC는 다소 불편
*파이썬 시간대 변환 방법은 두가지로 예전방식인 time모듈과 새로운 방식 datetime이 있음
**time 보다는 datetime이 권장되고 pytz 함께 사용하면 편리

*time의 경우 윈도우 같은 플랫폼에서 제공하는 시간대 관련 몇가지 기능의 사용이 제한됨
**플랫폼에 따라 다르게 작동하는 문제인데 호스트 운영체제의 C 함수에 의존적이기 때문
*그러므로 여러 시간대 사이의 변환을 다룬다면 datetime 모듈 사용이 권장됨
*datetime 모듈의 경우 지역 시간을 UTC로 된 유닉스 타임스탬프로 쉽게 바꿀 수도 있음

from datetime import datetime, timezone  


time_str = '2020-08-27 19:13:04'  
now = datetime.strptime(time_str, time_format) #시간대 설정이 안 된 시간으로 문자열 구분 분석  
time_tuple = now.timetuple()  # 유닉스 시간 구조체로 변환  
utc_now = time.mktime(time_tuple) #구조체로부터 유닉스 타임스탬프 생성  
print(utc_now) #1598523184.0  

*pytz 모듈을 내려받아 기본 설치가 제공하지 않는 시간대 정보를 추가할 수 있음

*정리
**여러 다른 시간대를 변환할 때는 time 모듈을 지양
**여러 다른 시간대를 신뢰할 수 있게 변환하고 싶으면 datetime과 pytz 모듈을 함께 사용하는 것을 권장
**항상 시간을 UTC로 표시하고, 최종적으로 표현하기 직전에 지역 시간으로 변환할 것

Better way 68 (Wednesday, 220914)

copyreg를 사용해 pickle을 더 신뢰성 있게 만들라

*pickle 내장 모듈을 사용하면 파이썬 객체를 바이트 스트림으로 직렬화하거나 바이트 스트림을 파이썬 객체로 역직렬화할 수 있음(pickle의 목적은 이진 채널을 통해 서로 파이썬 객체를 넘기는 것)
*설계상 pickle 모듈의 직렬화 형식은 안전하지 않음.
**왜냐하면 악의적인 pickle데이터가 자신을 역직렬화하는 파이썬 프로그램의 일부를 취약하게 만들 수 있기 때문
*json 모듈은 설계상 안전.
**JSON데이터에는 객체 계층 구조를 간단하게 묘사한 값이 들어 있고 JSON 데이터를 역직렬화해도 파이썬 프로그램이 추가적인 위험에 노출되는 일이 없음.
**그래서 서로를 신뢰할 수 없는 프로그램이 통신해야 할 경우에는 JSON 같은 형식을 사용해야함

*pickle 모듈을 사용하면 상태를 쉽게 저장할 수 있음
*pickle 의 주 용도는 객체 직렬화를 쉽게 만드는 것
**pickle 을 사용하는 방식이 아주 간단한 수준을 벗어나는 순간, pickle 모듈의 동작은 예상할 수 없이 이상해짐
*copyreg 내장 모듈을 사용하면 이런 문제를 쉽게 해결할 수 있음
**copyreg 내장 모듈에 디폴티 애트리뷰트값을 등록해서 문제 해결하는 것

*파이썬 객체의 필드를 제거해 예전 버전 객체와의 하위 호환성이 없어지는 경우도 발생
이 경우 디폴트 인자를 사용하는 접근 방법은 사용할 수 없음
***copyreg 함수에게 전달하는 함수에 버전 파라미터를 추가하면 이 문제 해결 가능
**
새로운 객체를 피클링할 때 직렬화한 데이터의 버전을 2로 설정하는 것

pickle을 할 때 마주칠 수 있는 다른 문제점으로 클래스 이름이 바뀌어 코드가 깨지는 경우임
**이 경우 copyreg를 써서 객체를 언피클할 때 사용할 함수에 대해 안정적인 식별자를 지정해줌
**
이로 인해 여러 다른 클래스에서 다른 이름으로 피클된 데이터를 역직렬화할 때 서로 전환할 수 있음

*정리
**신뢰할 수 있는 프로그램 사이에 객체를 직렬화하고 역직렬화할 때는 pickle 내장 모듈이 유용
**시간이 지남에 따라 클래스가 바뀔(애트리뷰트의 추가나 삭제 등) 수 있으므로 이전에 피클한 객체를 역직렬화하면 문제가 생길 수 있음
**직렬화한 객체의 하위 호환성을 보장하고자 copyreg 내장 모듈과 pickle을 함께 사용하는 것 권장

Better way 69 (Thursday, 220915)

정확도가 매우 중요한 경우에는 decimal을 사용하라

*파이썬은 수치 데이터를 처리하는 코드를 작성하기에 매우 좋은 언어
*정수 타입은 실용적으로 어떤 크기의 정수든 표현 가능
*2배 정밀도 부동소수점 타입은 IEEE 754 표준을 따름
2배 정밀도(배정도)에서 정밀도가 두배라는 말은 일반적으로 float에 비해 정밀도가 두배 이상 높다는 뜻
**IEEE 754 표준에서 32비트 타입(binary 32)은 24개의 유효 숫자 비트와 8비트 지수를 사용하고, 64비트 타입(binary 64)는 53개의 이진 유효 숫자 비트와 11비트 지수를 사용하므로
**
십진수로 보면 유효 숫자 개수는 대략 두배이고 지수의 범위는 열 배 정도 차이가 남
**부동소수점이라는 말은 소수점 위치가 고정적이지 않아서 붙은 이름
***부동소수점 수가 과학적 표기법에 사용하는 1.23e10과 같은 방식을 사용해 값을 표현하기 때문에 붙은 이름
**
예를 들어, 11.23을 11.23, 1.123e1, 0.1123e2 등과 같이 여러 가지 형태로 표현할 수 있으며 소수점 위치가 고정되지 않음
*허수값을 포함하는 표준 복소수 타입도 제공
*IEEE 754 부동소수점 수의 내부(이진) 표현법으로 올바른 값보다 소수점 아래 작은 수만큼 오차가 생길 수 있음
**이유는 십진 유한 소수는 분모가 10의 거듭제곱 꼴이어서 분모를 소인수 분해하면 2와 5의 거듭제곱으로 나타나지만 이진 소수는 2의 거듭제곱 꼴로 표현 가능한 수만 유한하게 표현할 수 있기 때문
**예를 들어 0.2(1/5)를 이진 소수로 표현하면 0.001100110011… 이라는 순환소수가 되고 이런 경우에는 정해진 소수점 이하 자릿수의 십진수와 이진수를 변환하는 과정에서 약간의 값의 차이가 발생할 수 밖에 없음
*위 문제에 대한 해결 방법은 decimal 내장 모듈에 있는 Decimal 클래스를 사용하는 것
**Decimal 클래스는 디폴트로 소수점 이하 28번째 자리까지 고정소수점 수 연산을 제공(자릿수 더 늘리는 것도 가능)
**이 기능 활용 시 IEEE 754 부동소수점 수에 존재하는 문제 우회 가능, 반올림 처리도 원하는 대로 더 정확하게 처리 가능
*정확한 답이 필요하다면 좀 더 주의를 기울여서 Decimal 타입 생성자에 str을 사용해야함
*계산한 값이 너무 작아서 반올림 시 0이 나올 경우 Decimal 클래스의 quantize 메서드를 사용하면 원하는 소수점 이하 자리까지 원하는 방식으로 근삿값을 계산해줌
**하지만 정밀도에 한계는 있고(예를 들어 1/3은 근사치 사용해야함..), 정밀도 제한 없이 유리수를 사용하고 싶다면 fractions 내장 모듈에 있는 Fraction 클래스를 사용해야함

*정리
**파이썬은 실질적으로 모든 유형의 숫자 값을 표현할 수 있는 내장 타입과 클래스를 제공
**돈과 관련된 계산 등과 같이 높은 정밀도가 필요하거나 근삿값 계산을 원하는 대로 제어해야 할 때는 Decimal 클래스가 이상적
**부동소수점 수로 계산한 근사값이 아니라 정확한 답을 계산해야 한다면 Decimal 생성자에 float 인스턴스 대신 str 인스턴스를 넘겨야 함

Better way 70 (Thursday, 220929)

최적화하기 전에 프로파일링을 하라

파이썬 동적 특성으로 인한 시간 지연(또는 빠른) 경우
**문자열 조작, 제너레이터(빠름)
**애트리뷰트 접근, 함수호출(느림)
**최적화 전에 프로그램 성능 측정하는 것이 중요한데 이러한 실행시간 차지하는 거 보는 것이 프로파일러
**
단, 코드 자체의 성능을 측정해야함(네트워크나 디시크 자원 접근이 아니라.. 캐시를 사용한다면 캐시 미리 예열해둬야함)
*프로파일러 필드
**(ncalls: 함수 몇번 호출, tottime: 함수 실행 시 걸린 시간 합계, tottime percale: 호출될 때 걸린 시간 평균, cumtime: 걸린 누적 시간, cumtime percall: 호출될 때마다 걸린 누적 시간의 평균)
*파이썬 프로파일러는 각 함수를 프로파일링한 정보에 대해 그 함수를 호출한 함수들이 얼마나 기여했는지를 보여주는 print_callers 메서드 제공

*정리(먼저)
**파이썬 느린 원인 불명하므로 최적화 전에 프로파일링 하는 것 중요
**cProfile 모듈 사용하여 profile 보다 더 정확한 정보 제공받을 수 있음
**함수 호출 트리를 독립적으로 프로파일링하려면 Profile 객체의 runcall 메서드를 사용
**stats 객체를 사용하면 프로파일링 정보 중에서 프로그램 성능을 이해하기 위해 살펴봐야할 부분만 선택 출력 가능

Better way 71 (Friday, 220930)

생산자-소비자 큐로 Deque를 사용하라

*정리
collections 내장 모듈에 있는 duque 클래스는 큐 길이와 관계없이 상수 시간만에 append와 popleft를 수행함
**FIFO 큐 구현에 이상적임
**반면 일반 list는 길이가 길어짐에 따라 pop, append의 성능도 나빠짐 (worst O(N) )
***pop(0)의 경우 큐 길이가 늘어남에 따라 길이 제곱에 비례해 성능 나빠짐
**
왜냐하면 전체 리스트 내용을 다시 재대입하기 때문

Better way 72 (Saturday, 221001)

정렬된 시퀀스를 검색할 때는 bisect를 사용하라

정리
**리스트에 들어 있는 정렬된 데이터를 검색할 때 index 메서드를 사용하거나 for 루프와 맹목적인 비교를 사용하면 선형 시간이 걸림
**bisect 내장 모듈의 bisect_left 함수는 정렬된 리스트에서 원하는 값을 찾는 데 로그시간이 걸림, 즉, 훨씬 빠름
**
이진탐색이니깐!

Better way 73(Thursday, 221208)

우선순위 큐로 heapq를 사용하는 방법을 알아두라

정리
**우선순위 큐를 사용하면 선입선출이 아니라 원소의 중요도에 따라 원소 처리 가능
**리스트 사용 우선순위 큐는 성능 당연히 선형보다 나쁨(구성 원소 modify 후 재배치땜에)
**heapq 내장 모듈 사용하면 됨
**
힙이니까 search O(logN)
*lt 특별메서드 구현해놓으면 클래스에 비교 기능과 자연스러운 정렬 순서 제공 가능
**
def lt(self, other):
return self.due_date < other.due_date

Better way 74(Friday, 221209)

bytes를 복사하지 않고 다루려면 memoryview와 bytearray를 사용하라

*정리
**memoryview 내장타입은 슬라이스에 대해 파이썬 고성능 버퍼 프로토콜로 읽고쓰기를 지원, 복사가 없는 인터페이스 제공
**bytearray 내장타입은 복사가 없는 읽기함수(socket.recv_from과 같은)에 사용 가능한 bytes와 비슷한 변경 가능 타입 제공
**memoryview로 bytearray를 감싸면 복사에 따른 비용 추가 부담 없이 수신받은 데이터를 버퍼에서 원하는 위치에 스플라이스 가능

9. 테스트와 디버깅

파이썬은 정적 타입 검사(static type checking)을 수행하지 않음. 또한, 파이썬 인터프리터가 작동 확인 안 함. 근본적으로 파이썬은 동적 언어이며, 이는 장점과 단점이 명확함(축복이자 저주). 어처구니 없는 실수로 오류 발생하기 쉽다는 뜻. 프로덕션 배포 전에 더 잘 테스트할 필요가 있음. 파이썬의 동적 기능을 사용해 프로그램의 동작을 다른 동작으로 오버라이드함으로써 테스트를 구현하고 예상대로 작동하는지 확인할 수 있음. “테스트”는 코드에 대한 보험으로 잘 작성한 테스트가 많이 있다면 실제로 파이썬 코드를 변경하기 더 쉬워짐.

Better way 75(Friday, 221209)

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

*정리
**일반 print시 타입 정보는 안 보임. **repr을 내장 파이썬 타입 값에 대해 호출하면 해당 값을 표현하는 출력 가능한 문자열을 얻음, eval사용 시 원래 값 돌려받음. **형식화 문자열의 %s는 str과 마찬가지로 사람이 읽을 수 있는 문자열 만드는데, str.%r은 repr과 같은 역할.
F-string에서 !r 접미사 뭍이지 않고 텍스트 치환식 사용 시 가독 문자열 만들어짐. **직접 클래스의 repr 특별 메서드를 정의해서 인스턴스의 출력 가능한 표현을 원하는 대로 만들 수 있고 디버깅 때 더 자세한 정보 표시 가능.

Better way 76(Friday, 221209)

TestCase 하위 클래스를 사용해 프로그램에서 연관된 행동 방식을 검증하라

*정리
unittest 내장 모듈에 있는 TestCase 클래스의 하위 클래스를 정의하고 테스트하려는 동작마다 메서드를 정의함으로써 테스트를 정의할 수 있음. TestCase 하위 클래스 안에서 테스트 메서드의 이름은 test로 시작해야함. **from unites import TestCase, main
**
from utils import to_str
**
$ python3 utils_test.py

*테스트 안에서는 파이썬 내장 assert 문을 사용하지 말고, assertEqual과 같이 TestCase 클래스에 정의된 여러 가지 도우미 메서드를 사용해 원하는 동작을 확인. **assertEqual: 두 값이 같은지 비교, assertTrue: 주어진 불 식이 참인지 검증 등.

*준비코드를 줄이려면 subTest 도우미 메서드를 사용해 데이터 기반 테스트를 정의해야함. **예외 발생 검증 위해 with문 안에 컨택스트 매니저로 사용할 수 있는 assertRaises 도우미 메서드가 있음(try/except문과 비슷)
**미묘한 경우(edge case)가 많다면 각 함수를 서로 다른 TestCase 하위 클래스에 정의할 때도 있음. **subTest 도우미 메서드로 한 테스트 메서드 안에 여러 테스트 정의 가능.

Better way 77(Friday, 221209)

setUp, tearDown, setUpModule, tearDownModule을 사용해 각각의 테스트를 격리하라

테스트 메서드 실행 전, 테스트 환경 구축하는 것을 테스트 하네스(test harness) 라고 부름. *위를 하려면 TestCase 하위 클래스 안에 setUp과 tearDown 메서드를 오버라이드 해야 함. *setUp은 테스트 메서드 실행 전, tearDown은 테스트 메서드 실행 다음에 호출. *프로그램이 복잡해지면 코드를 독립적으로 실행(목(mock)등의 도구 사용) 대신 여러 모듈 사이의 end-to-end 상호작용 검증 테스트 필요->통합테스트. *이 통합테스트 비용이 너무 듦(시간과 돈).
**해결을 위해 unittest모듈에서 테스트 하네스 초기화 지원. **
한번 초기화해두면 다음에 재사용 편함.

*정리
**단위 테스트(함수, 클래스 등의 기본 단위를 격리시켜 검증하는 테스트)와 통합테스트(모듈 간의 상호작용을 검증하는 테스트)를 모두 작성하는 것이 중요. **setUp과 tearDown 메서드를 사용하면. 테스트 사이를 격리할 수 있으므로 더 깨끗한 테스트 환경을 제공할 수 있음.
**통합테스트의 경우 모듈 수준의 함수인 setUpModule과 tearDownModule을 사용하면 테스트 모듈과 모듈 안에 포함된 모든 TestCase 클래스의 전체 생명주기 동안 필요한 테스트하네스를 관리할 수 있음.

Better way 78(Monday, 221212)

목을 사용해 의존 관계가 복잡한 코드를 테스트하라

*테스트 작성 때 필요한 공통 기능으로 사용하기에는 너무 느리거나 어려운 함수와 클래스의 목을 만들어 사용하는 기능이 있음. *데이터베이스를 완전히 자동으로 실행하려면, 단순한 단위테스트를 진행하는 경우에도 스키마를 설정하고 데이터를 채워 넣는 등 너무 많은 작업 필요. *서버 가동하려면 많은 시간 소요되기 때문에 단위 테스트 진행이 오래 걸리고 관리 어려워짐. *데이터베이스를 모킹하는 것이 나은 방안. **목(mock, mocking)은 자신이 흉내 내려는 대상에 의존하는 다른 함수들이 어떤 요청을 보내면 어떤 응답을 보내야할지 알고, 요청에 따라 적절한 응답 돌려줌. **페이크(fake)는 DatabaseConnection의 기능을 대부분 제공하지만 더 단순한 단일 스레드 인메모리 데이터베이스 사용. **파이썬 unittest.mock 내장 모듈을 사용하면 목을 만들고 테스트에 사용할 수 있음. *개별 파라미터에 관심이 없다면 unittest.mock.ANY 상수를 이용해 어떤 인자를 전달해도 관계 없다고 표현 가능. *assert_called_with 메서드를 사용하면, 가장 최근에 목을 호출할 때 어떤 인자 전달됐나 확인 가능. **여러 파라미터에 대해 과도하게 구체적으로 만들기 보다는 ANY를 사용해 좀 더 자유롭게 테스트 가능. *unittest.mock.patch 관련 함수들은 목 주입을 더 쉽게 만들어줌. **patch함수는 임시로 모듈이나 클래스의 애트리뷰트에 다른 값을 대입. **patch를 사용하면 앞에서 본 db에 접근하는 함수를 임시로 대치 가능.

*정리
**unitest.mock 모듈은 Mock 클래스를 사용해 인터페이스의 동작을 흉내 낼 수 있게 해줌. 테스트를 할 때, 테스트 대상 코드가 호출해야 하는 의존 관계 함수를 설정하기 힘들 경우에 목을 사용하면 유용.
**목을 사용할 때는 테스트 대상 코드의 동작을 검증하는 것과 테스트 대상 코드가 호출하는 의존 관계 함수들이 호출되는 방식을 검증하는 것이 모두 중요. Mock.assert_called_once_with나 이와 비슷한 메서드들을 사용해 이런 검증을 수행. **목을 테스트 대상 코드에 주입할 때는 키워드를 사용해 호출해야 하는 인자를 쓰거나, unittest.mock.patch 또는 이와 비슷한 메서드들을 사용.

 # Better way 79(Monday, 221212)

의존 관계를 캡슐화해 모킹과 테스트를 쉽게 만들라.

*정리
**단위 테스트를 작성할 때 목을 만들기 위해 반복적인 준비 코드를 많이 사용해야 한다면, 테스트 대상이 의존하는 다른 기능들을 더. 쉽게 모킹할 수 있는 클래스로 캡슐화하는 것이 좋음. **unittest.mock 내장 모듈의 Mock 클래스는 클래스를 시뮬레이션할 수 있는 새로운 목을 반환. 이 목은 목 메서드처럼 작동할 수 있고 클래스 내 각각의 애트리뷰트에 접근할 수 있음. **단대단 테스트를 위해서는 테스트에 상요할 목 의존 관계를 주입하는 데 명시적인 연결점으로 쓰일 수 있는 도우미 함수를 더 많이. 포함하도록 코드를 리팩터링 하는 것이 좋음.

Better way 80(Monday, 221212)

pdb를 사용해 대화형으로 디버깅해라

*유용한 명령어(관찰)
**where: 현재 실행 중인 프로그램의 호출 스택 출력. **up: 현재 관찰 중인 함수를 호출한 쪽(위)으로 호출 스택 영역을 한 단계 이동해서 해당 함수의 지역변수 관찰
**down: 실행 호출 스택에서 한 수준 아래로 이동
*실행 제어 명령어
**step: 디버거 프롬프트를 표시
**next: 다음 제어를 디버거로 돌려서 디버거 프롬프트를 표시
**return: 반환될 때까지 프로그램 실행
**continue: 다음 중단점에 도달할 때까지 프로그램 계속 실행
**quit: 디버거 나가면서 프로그램 중단

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

Better way 81(Monday, 221212)

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

파이썬 기본 구현인 CPython은 메모리 관리를 위해 참조 카운팅을 사용
**참조가 모두 없어지면 참조된 객체도 메모리에서 삭제되고 메모리 공간을 다른 데이터에 내어줌->가비지 컬렉트
*이론적으로 메모리 할당, 해제 ㅅ힌경 안 써도 되지만 실전에서는 쓸모없는 참조를 유지하기 때문에 메모리 소진 경우 생김
**하지만 사용하거나 누수되는 메모리 알아내기 매우 어려움
*메모리 사용을 디버깅하는 방법
**1. gc 내장 모듈을 사용해 쓰레기 수집기가 알고 잇는 모든 객체 나열시키는 것
**
둔탁하지만 메모리 사용 어디인지, 감 잡기 쉬움
**이러한 gc.get_objects의 문제점은 객체가 어떻게 할당됐는지 알려주지 않아서 메모리 누수 객체 알아내기 어렵다는 점
**2. 그래서 tracemalloc 내장 모듈 사용
**
객체를 자신이 할당된 장소와 연결시켜줌
***메모리 사용 이전과 이후 스냅샷을 만들어 비교하면 변경 부분 파악 가능

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

10. 협업

파이썬은 API를 잘 정의하고 인터페이스 경계를 명확히 하고 싶을 때 도움
다른 사람과 협업하려면 코드를 작성할 때 심사숙고해야함
협업할 수 있는 메커니즘 이해해야함

Better way 82(Tuesday, 221213)

커뮤니티에서 만든 모듈을 어디서 찾을 수 있는지 알아두라

*정리
**파이썬 패키지 인덱스(PyPI)에는 파이썬 커뮤티니가 개발하고 유지하는 풍부한 공통 패키지가 들어 있음
**pip는 PyPl에 있는 패키지를 설치하고 사용할 때 쓸 수 있는 명령줄 도구
**대다수는 자유, 오픈소스 소프트웨어

Better way 83(Tuesday, 221213)

가상 환경을 사용해 의존 관계를 격리하고 반복 생성할 수 있게 하라

*정리
**가상 환경을 사용하면 한 컴퓨터 안에서 pip를 사용해 패키지의 여러 버전을 충돌 없이 설치 가능
**python -m vent 명령으로 가상 환경을 만들고 source bin/activate로 가상 환경을 활성화하며, deactivate로 비활성화
**python -m pip freeze를 사용해 어떤 환경 안의 모든 의존 관계를 덤프할 수 있음(python -m pip freeze > requirements.txt)
**python3 -m pip install -r requirements.txt를 사용해 환경 다시 만들어낼 수 있음

Better way 84(Wednesday, 221214)

모든 함수, 클래스, 모듈에 독스트링을 작성하라

*모듈 예시
“”” 단어의 언어 패턴을 찾을 때 쓸 수 있는 라이브러리.

여러 단어가 서로 어떤 연관 관계에 잇는지 검사하는 것이 어려울 때가 있다!
이 모듈은 단어가 가지는 특별한 특성을 쉽게 결정할 수 있게 해준다.

사용 가능 함수:

  • palindrome: 주어진 단어가 회문인지 결정한다.
  • check_anagram: 주어진 단어가 어구전철(똑같은 글자들로 순서만 바뀐 경우)인지 결정한다. “””

*클래스 예시
class Player:
“”” 게임 플레이어를 표현한다.

하위 클래스는 ‘tick’ 메서드를 오버라이드해서 플레이어의 파워 레벨 등에 맞는 움직임 애니메이션을 제공할 수 있다.

공개 애트리뷰트:

  • power: 사용하지 않은 파워업들(0과 1 사이의 float)
  • coins: 현재 레벨에서 발견한 코인 개수(integer).
    “””

*함수 예시
def find_anagrams(word, dictionary):
“”” 주어진 단어의 모든 어구전철을 찾는다.

이 함수는 ‘딕셔너리’ 컨테이너의 원소 검사만큼 빠른 속도로 실행된다.

Args:
word: 대상 단어. 문자열.
dictionary: 모든 단어가 들어 있는 collections.abc.Container 컬렉션.

Returns: 찾은 어구 전철들로 이뤄진 리스트. 아무것도 찾지 못한 경우 Empty.
“””

*타입 애너테이션 적용한 함수 예시
def find_anagrams(word: str, dictionary: Container[str]) -> List[str]:
“”” 주어진 단어의 모든 어구전철을 찾는다.

이 함수는 ‘딕셔너리’ 컨테이너의 원소 검사만큼 빠른 속도로 실행된다.

Args:
word: 대상 단어. 문자열.
dictionary: 모든 단어가 들어 있는 collections.abc.Container 컬렉션.

Returns: 찾은 어구 전철들로 이뤄진 리스트. 아무것도 찾지 못한 경우 Empty.
“””

*정리
**독스트링을 사용해 모든 모듈, 클래스, 메서드, 함수에 대해 문서를 작성하고 코드를 변경할 때마다 독스트링 최신 상태 유지 권장
**모듈의 경우 모듈의 내용과 사용자가 알아야 하는 중요한 클래스나 함수를 독스트링에 소개
**클래스의 경우: 동작, 중요한 애트리뷰트, 하위 클래스의 동작 등을 class 문 뒤에 나오는 독스트링에 문서화
**함수와 메서드의 경우: 모든 인자, 반환 값, 발생하는 예외, 기타 세부적인 동작 등을 def 문 바로 뒤에 오는 독스트링에 설명
**타입 애너테이션을 사용하는 경우: 타입 애너테이션에 들어 있는 정보를 독스트링에 기술하지 비권장. 왜냐하면 타입 애너테이션과 독스트링에 모두 타입 정보를 기술하는 것은 불필요한 중복 작업이기 때문.

Better way 85(Thursday, 221215)

패키지를 사용해 모듈을 체계화하고 안정적인 API를 제공하라

*정리
**파이썬 패키지는 다른 모듈을 포함하는 모듈.
패키지를 사용하면 서로 분리돼 충돌이 일어나지 않는 유일한 절대 모듈 경로를 사용하는 이름 공간으로 코드를 나눌 수 있음
**다른 소스 파일이 들어 있는 디렉터리에 init.py 파일을 추가하면 간단한 패키지를 만들 수 있음
소스 파일들은 디렉터리로 인해 생긴 패키지의 자식 모듈이 됨
패키지 디렉터리에는 다른 패키지가 들어갈 수 있음
**모듈 외부에서 볼 수 있게 허용할 이름을 all 특별 애트리뷰트에 지정해 공개 API를 제공할 수 있음
**패키지의 init.py 파일에 외부에 공개할 이름만 임포트하거나 패키지 내부에서만 사용할 이름 앞에 _를 붙임으로써 패키지 내부에서만 사용할 수 있는 이름을 감출 수 있음
**단일 코드베이스나 단일 팀 안에서 협업을 진행한다면 아마도 __all__로 API를 명시할 필요가 없을 것

Better way 86(Friday, 221216)

배포 환경을 설정하기 위해 모듈 영역의 코드를 사용하라

*프로덕션 환경이 있고 개발환경이 있음
**개발환경과 프로덕션 환경이 아주 많이 다른 경우 있음
**일부를 오버라이드해서 배포되는 환경에 따라 다른 기능을 제공하드록 만듬

*정리
**고유한 가정과 설정이 있는 다양한 배포 환경에서 프로그램을 실행해야 하는 경우가 많음
**모듈 영역에서 일반적인 파이썬 문을 사용하면 각 배포환경에 맞게 모듈의 내용을 조정할 수 있음
**모듈 내용은 모든 외부 조건에 따라 달라질 수 있는 결과물임
외부 조건에는 sys나 os 모듈을 사용해 알아낸 호스트 인트로스펙션 정보가 포함

Better way 87(Friday, 221216)

호출자를 API로부터 보호하기 위해 최상위 Exception을 정의하라

API의 경우 새로운 예외 계층 구조를 정의하는 편이 훨씬 강력
**모듈에 최상위 Exception을 정의하고 모듈이 발생시키는 다른 모든 예가 이 최상위 예외를 상속하게 만듦으로써 API에서 발생하는 예외의 계층 구조를 만들 수 있음
*효과
**최상위 예외가 있으면 API를 호출하는 사용자가 API를 잘못 사용한 경우를 더 쉽게 이해할 수 있다는 점
**API 모듈 코드의 버그를 발견할 때 도움 줌
**
모듈의 최상위 예의를 잡아내는 try/except 문이 호출하는 쪽에서 파이썬 기반 Except클래스를 잡아내는 다른 except블록을 추가해야함, 두가지 사용하여 예외처리 굿(촘촘)
*미래의 API를 보호히ㅐ준다는 뜻

*정리
**모듈에서 사용할 최상위 예외를 정의하면 API 사용자들이 자신을 API로부터 보호할 수 있음
**최상위 예외를 잡아내면 API를 소비하는 코드의 버그를 쉽게 찾을 수 있음
**파이썬 Exception 기반 클래스를 잡아내면 API 구현의 버그를 쉽게 찾을 수 있음
**중간 단계의 최상위 예외를 사용하면, 미래에 새로운 타입의 예외를 API에 추가할 때, API를 사용하는 코드가 깨지는 일을 방지할 수 있음

Better way 88(Tuesday, 221220)

순환 의존성을 깨는 방법을 알아두라

*모듈이 임포트 되면 파이썬이 실제로 어떤 일을 하는지를 깊이 우선 순위로 나타내면 아래와 같음
**1. sys.path에서 모듈 위치를 검색
**2. 모듈의 코드를 로딩하고 컴파일 되는지 확인
**3. 임포트할 모듈에 상응하는 빈 모듈 객체를 만듬
**4. 모듈을 sys.modules에 넣음
**5. 모듈 객체에 있는 코드를 실행해서 모듈의 내용을 정의
*순환 의존 관계에서 문제는 어떤 모듈의 애트리뷰트를 정의하는 코드(5단계)가 실제로 실행되기 전까지는 모듈 애티르뷰트가 정의되지 않는다는 점
*가장 좋은 해결책은 리팩토링해서 의존 관계 트리의 맨 밑바닥으로 보내는 것
*다른 방법 3가지도 있음
**임포트 순서 바꾸기, 하지만 이는 PEP 8 스타일 가이드에 위배되므로 권하지 않음
**임포트, 설정, 실행 - 임포트 시점에 실제로 함수를 전혀 실행하지 않게 만듬, 이런 구조 잘 작동하며 의존관계 주입 같은 다른 패턴에 적용 가능
**동적 임포트 - import문을 함수나 메서드 안에서 사용, 모듈을 정으히ㅏ고 임포트하는 방식을 구조적으로 바꾸지 않아도 되는 장점 있음, 일반적으로 그러나 이런 동적 임포트는 피해야함. 왜냐하면 import 비용이 무시하기 힘들며, 루프 안에서 임포트 사용하면 악영향 더 커짐

*정리
**두 모듈이 임포트 시점에 서로를 호출하면 순환 의존 관계가 생김
순환 의존 관계가 있으면 프로그램이 시작되다가 오류가 발생하면서 중단될 수 있음
**순환 의존 관계를 깨는 가장 좋은 방법은 상호 의존 관계를 의존 관계 트리의 맨 아래에 위치한 별도의 모듈로 리팩터링하는 것
**동적 임포트는 리팩터링과 복잡도 증가를 최소화하면서 모듈 간의 순환 의존 관계를 깨는 가장 단순한 해법

Better way 89(Tuesday, 221220)

리팩터링과 마이그레이션 방법을 알려주기 위해 warning을 사용하라

*코드베이스가 커지면 API를 호출하는 지점 수가 너무 많이지거나 여러 소스 코드 저장소에 호출 지점이 흩어지므로, API 변경과 호출 지점 변경을 함께 일관성 있게 수행하는 것이 실용적이지 않거나 불가능할 수 있음
**코드를 리팩터링하고 API에 맞춰 변경하도록 협력을 요청할 수 있는 방법 찾아야 함
*선택적인 키워드 인자를 받게 하는 것으로 파라미터 스펙 맞추도록 할 수 있음
*파이썬이 제공하는 warnings 내장 모듈을 통해 의존하는 모드가 변경됐으므로 각자의 코드를 변경하라고 안내할 수 있음
*warnings.warn 함수는 stacklevel이라는 파라미터를 지원, 호출 스택에서 경고를 발생시킨 위치를 제대로 보고할 수 있음
*로깅을 사용해 경고를 잡아내면, 프로그램에 오류 보고 시스템이 설정된 경우 프로덕션 환경에서도 중요한 경고를 통보받을 수 있음

*정리
**warning 모듈을 사용하면 여러분의 API를 호출하는 사용자들에게 앞으로 사용 금지될 사용법에 대해 알려줄 수 있음
경고메세지는 API 사용자들이 API 변경으로 인해 자신의 코드가 깨지기 전에 코드를 변경하도록 권장
**-w error 명령줄 인자를 파이썬 인터프리터에게 넘기면 경고를 오류로 높일 수 있음
의존 관계에서 잠재적인 회귀 오류가 있는지 잡아내고 싶은 자동화 테스트에서 이런 기능이 특히 유용
**프로덕션 환경에서는 경고를 logging 모듈로 복제해 실행 시점에 기존 오류 보고 시스템이 경고를 잡아내게 할 수 있음
**다운스트림 의존 관계에서 알맞은 때 경고가 발동되도록 코드가 생성하는 경고에 대해 테스트를 작성하면 유용

Better way 90(Tuesday, 221220)

typing과 정적 분석을 통해 버그를 없애라

*올바른 방향으로 사용하고 하위 의존 관계를 맞게 활용하는지 검사하는 메커니즘이 필요
**def subtract(a: int, b: int) -> int: #함수에 타입 애너테이션을 붙임
*덕 타입에 대한 제너릭 기능을 사용하면 다양한 타입을 처리할 수 있으므로 반복적인 수고를 줄일 수 있고 테스트도 단순해짐
*일반적인 코드 작성 및 개발에서 도입 순서는 우선 아무 타입 애너테이션도 사용하지 않으면서 최초 버전을 작성하고, 이어서 테스트를 작성한 다음, 타입 정보가 가장 유용하게 쓰일 수 있는 곳에 타입 정보를 추가하는 것
*타입 힌트는 어떤 코드에 의존하는 많은 호출자에게 기능을 제공하는 API와 같이 코드베이스의 경계에서 가장 중요
*타입 힌트를 추가하는 데 드는 노력이 너무 크다면 도입을 좀 더 검토해 보는 것도 필요

*정리
**파이썬은 변수, 필드, 함수, 메서드에 타입 정보를 추가할 수 있게 특별한 구문과 typing 내장 모듈을 제공
**정적인 타입 검사기를 사용하면 타입 정보를 활용해 런타임에 발생할 수 있는 다양한 일반적인 오류를 방지할 수 있음
**타입을 프로그램에 도입하고, API에 타입을 적용하고, 타입 정보를 추가해도 생산성이 떨어지지 않도록 해주는 다양한 모범 사례가 있음