본 문서는 파이썬 코딩의 기술: Effective Python의 정리 내용입니다. 인용구문은 필자의 견해 또는 개인적인 궁금함을 담은 내용입니다.
None
을 반환하는 함수가 오류를 일으키기 쉬운 이유는 None
이나 다른 값(0이나 빈 문자열)이 조건식에서 False로 평가되기 때문이다.때에 따라서
None
을 리턴하는 것이 유용한 상황이 있다. 이럴 때는 해당 함수가None
을 반환 할 수 있음을 명시하자. ex)type hinting
의Optional
클로저(closure)
란 자신이 정의된 스코프
에 있는 변수를 참조하는 함수def sort_priority(values, group):
def helper(x):
if x in group: # helper가 선언된 sort_priority 스코프에는 group 변수가 존재하므로 클로저에서 참조 가능하다.
return (0, x)
return (1,x)
values.sort(key=helper)
일급 객체
, 일급 객체는 함수를 직접 참조하고, 변수 할당, 인수 전달 등이 가능하다.표현식
에서 스코프 탐색 순서, 아래에서도 찾지 못하면 NameError
예외가 발생할당식
에서는 다른 방식으로 동작현재 스코프에 정의되어 있다면
새로운 값으로 갱신현재 스코프에 존재하지 않으면 변수 정의로 취급
, 새로 정의 되는 변수의 스코프는 그 할당을 포함하고 있는 함수가 된다.def sort_priority2(numbers, group):
found = False # 스코프: sort_priority2
def helper(x):
if x in group:
found = True # 스코프: helper
return (0, x)
return (1, x)
numbers.sort(key=helper)
return found
nonlocal
문은 특정 변수 이름에 할당 할 때 스코프 탐색이 일어나야함을 나타낸다.nonlocal
대신 클래스로 상태를 감싸는 방법이 좋다.nonlocal
이 없기때문에, 상위 스코프 탐색을 위해서 아이템이 한개만 있는 리스트와 같은 mutable
수정 가능한 값으로 문제를 우회한다.개인적인 견해로는 이 부분에 동의하지 않는다. 명시적으로 리스트를 담아서 주는 것이 명확해보이며, 제네레이터 함수는 상태를 가지기 때문에 재사용이 불가능하고 사용하는 쪽에서 상태 관리가 되어야한다.
def normalize(numbers):
total = sum(numbers)
return [100 * value / total for value in numbers]
it = read_visits('/my_numbers.txt') # return iterator
percentages = nomalize(it)
print(percentages)
>>> []
위 결과가 나오는 이유는 sum
함수에서 이터레이터를 이미 소진하였기 때문에, complehension
에서는 빈 이터레이터를 순회하게 된다. 이 문제를 해결하려면 이터레이터를 명시적으로 소진하기 전에 전체 콘텐츠 복사본을 리스트에 저장해야한다.
def normalize(numbers):
numbers = list(numbers)
total = sum(numbers)
return [100 * value / total for value in numbers]
문제는 결국 모든 리스트를 메모리에 올리게 됨으로 이터레이터 콘텐츠 복사본이 클 경우 문제가 될 수 있다.
가장 좋은 방법은 이터레이터 프로토콜을 구현하는 것이다. 파이썬의 for x in foo
구문은 실제로 iter(foo)
를 호출하고 내장함수 iter
는 매직메서드 foo.__iter__
를 호출한다. __iter__
메서드는 이터레이터 객체를 반환해야한다. for 루프는 이터레이터를 모두 소진 할 때까지 (StopIteration
예외가 발생 할 때까지) 이터레이터 객체에 __next__
를 계속 호출한다.
이터레이터 객체는 __next__
를 구현해야하는데, __iter__
메서드를 제너레이터로 구현하면 간단하다.
class ReadVisits(object):
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) # __iter__를 제네레이터 함수로 구현하였으므로 별도의 __next__ 구현은 필요없다.
iter
에 이터레이터를 넘기면 이터레이터 자체가 반환되는 반면 컨테이너 타입을 넘기면 매번 새 이터레이터 객체가 반환된다. 따라서 이 동작으로 이터레이터 타입구분이 가능하다.if iter(numbers) is iter(numbers): # numbers는 이터레이터 타입
# ... something
이터레이터, 제네레이터 참고하면 좋은 글 : https://mingrammer.com/translation-iterators-vs-generators/
*args
를 사용하면 함수에서 가변 개수의 위치 인수를 받을 수 있다.*
연산자를 쓰면 시퀀스에 들어 있는 아이템을 함수으 위치 인수로 사용 할 수 있다.args는 디버깅이 어렵게 하는 원인 중 하나가 될 수 있다. 꼭 필요한 경우에만 사용하고 이외에는 명시적으로 인수를 설정하는 것이 좋다.
mutable
한 값을 디폴트로 선언 할 경우, 해당 오브젝트는 모든 곳에서 공유됨으로 문제가 발생 할 수 있다. 따라서 mutable
또는 동적인 값들은 None
으로 선언하고 내부에서 처리는 것이 좋다.*
기호는 위치 인수의 끝과 키워드 전용 인수의 시작을 가리킨다.def safe_division(number, divisor, *, ignore_overflow=False, ignore_zeroe_division=False)
**kwargs
를 사용하여 함수 키워드 전용 인수를 흉내 낼 수 있다.