[파이썬 코딩의 기술] 파이썬다운 생각

피누·2020년 9월 17일
0

본 문서는 파이썬 코딩의 기술: Effective Python의 정리 내용입니다. 인용구문은 필자의 견해 또는 개인적인 궁금함을 담은 내용입니다.

Better Way 1 - 사용중인 파이썬의 버전을 알자

  • command line
    python python --version
  • sys module
import sys
print(sys.version_info)
print(sys.version)
  • 파이썬에는 CPython, Jython, PyPy 같은 다양한 구현체가 존재
  • PyPy는 JIT를 도입해 성능을 올린 구현체
  • 파이썬 공식 홈페이지에서 다운로드하면 CPyhon이 다운로드 된다.

Better Way 2 - PEP 8 스타일 가이드를 따르자

https://www.python.org/dev/peps/pep-0008/ 참고

  • List, Dict 등 컨테이너 타입의 자료형은 값이 비어있으면 if문안에서 False를 리턴한다.
  • Import는 표준 라이브러리 모듈, 서드파티 모듈, 자신이 만든 모듈 순으로 섹션을 구분한다.
  • 파이썬 Lint tool 로는 pylint, flake8 등이 존재

Better Way 3 - bytes, str, unicode의 차이점을 알자

  • Python3에서는 bytes, str 두가지 타입으로 문자 시퀀스를 표현
    • bytes는 raw 8 비트 값을 저장
    • str은 유니코드 문자를 저장
  • Python2에서는 unicode, str 두가지 타입으로 문자 시퀀스를 표현
    • str는 raw 8 비트 값을 저장
    • unicode는 유니코드 문자를 저장
  • 외부에 제공할 인터페이스에서는 유니코드를 인코드하고 디코드

ASCII(American Standard Code for Information Interchange)는 미국에서 정의한 표준화 부호체계로 010101 같은 이진 숫자를 통해 문자를 표현하기 위해 고안되었다.

아스키코드는 문자를 표현하기 위해 1바이트를 7비트와 통신 에러 검출을 위한(Parity Bit) 1로 사용한다. 총 2^7=128개의 문자를 표현 할 수 있는데 이는 영문 키보드로 입력 할 수 있는 케이스들만 커버가 가능하기에 1비트 확장한 ANSI 코드가 나왔다.

그러나 한글, 중국어 등 여러 국가에 언어에서는 제약이 있었다. 그리하여 Unicode라는 전 세계 언어 문자 정의를 위한 국제 표준 코드가 등장하였다.
참고: 아스키(ASCII)코드와 유니코드(Unicode)의 이해

  • 바이너리 데이터를 파일에서 읽거나 쓸 때는 바이너리 모드임을 알리기 위해 rb혹은 wb로 오픈한다.

Better Way 4 - 복잡한 표현식 대신 헬퍼 함수를 작성하자

  • 파이썬 문법을 이용하면 한 줄짜리 표현식이 쉽게 가능하지만 코드가 복잡해질 수 있다. 복잡한 표현식은 별도의 함수로 분리하자.

Better Way 5 - 시퀀스를 슬라이스하는 방법을 알자

  • 슬라이스의 기본 형태 : somelist[start:end:step]
    • start 인덱스는 포함, end 인덱스는 미포함
    • 처음 인덱스와 마지막 인덱스는 생략 가능 ex) a[:5], a[2:]
  • 리스트의 끝을 기준으로 오프셋을 계산할 때는 음수로 슬라이스
  • 슬라이싱은 start와 end 인덱스가 리스트의 경계를 벗어나도 적절하게 처리

    a[0] vs a[:1]
    전자는 빈 리스트일 경우 index error 발생, 반면 후자는 [] 빈 리스트를 반환한다.
    슬라이싱은 인덱스 범위가 벗어나도 자동으로 범위를 맞춰준다.
    참고 - Why does Python allow out-of-range slice indexes for sequences?

  • 슬라이싱의 결과는 완전히 새로운 리스트
    • 할당(왼쪽 구문)에 사용하면 원본 리스트에서 지정한 범위를 대체한다.
s = list("five little monkeys")
i = s.index('l')
n = len('little')
s[i : i+n ] = list("humongous")
''.join(s)
>> 'five humongous monkeys'

Better Way 6 - start, end, stride를 함께 쓰지말자

  • stride를 사용하고자 한다면 양수값을 사용하고, start와 end 인덱스를 생략하는 것이 가독성을 챙기고 혼란을 방지 할 수 있다.
  • 꼭 셋을 함께 사용해야한다면 적용 결과를 변수에 할당하여 사용하자.

Better Way 7 - map과 filter 대신 리스트 컴프리헨션을 사용하자

a = [1, 2, 3, 4]

# comprehension
even_squeres = [x**2 for x in a if x % 2 == 0]

# map, filter
even_numbers = filter(lambda x: x % 2 ==0, a)
even_sequeres = map(lambda x: x**2, even_numbers)
  • 리스트 컴프리헨션은 추가적인 람다 표현식이 필요 없어 내장 함수인 map이나 filter보다 명확하다
  • dictionary와 set에서도 컴프리헨션 표현식을 지원한다.

    javascript와 같이 map,filter를 체이닝해서 사용하고자 한다면 mapfilter를 오버라이드 하거나, PyFunctional와 같은 라이브러리를 사용할 수 있다.
    참고글 : What is the Python way of chaining maps and filters?

Better Way 8 - 리스트 컴프리헨션에서 표현식을 두 개 넘게 쓰지 말자

matrix = [[1,2,3], [4,5,6], [7,8,9]]
flag = [x for row in matrix for x in row]
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9]

## condition by loop level
filtered = [[x for x in row if x % 3 == 0] 
           for row in matrix if sum(row) >= 10 ]
>>> [[6], [9]]
  • 리스트 컴프리헨션은 다중 루프와 루프 레벨별 다중 조건을 지원한다.
  • 표현식이 두 개가 넘게 들어 있는 리스트 컴프리헨션은 가독성을 해치므로 피하자.

Better Way 9 - 컴프리헨션이 클 때는 제너레이터 표현식을 고려하자

  • comprehension의 문제점은 새로운 메모리에 시퀀스(리스트, 딕셔너리 등) 객체를 새로 생성한다는 점이다. 따라서 메모리를 비교적 많이 사용한다.
  • generator expression은 시퀀스를 모두 메모리에 로딩하지 않고 한 번에 한 아이템을 내주는 iterator를 리턴한다
  • generator expression은 () 문자 사이에 컴프리헨션과 비슷한 문법으로 생성한다.
iter = (len(x) for x in [1,2,3]) # generator expression

next(it)
>>> 1
next(it)
>>> 2

제너레이터는 __iter__를 통해 생성 할 수 있다. 함수 본체 안에 yield 키워드를 가진 함수는 모두 제너레이터 함수이다. 함수는 값을 반환하고, 제네레이터 함수는 제네레이터 객체가 반환된다.

next()는 함수 본체에 있는 다음 yield로 진행하며, 함수 본체가 반환 될 때(return) 이를 포함하고 있는 제너레이터 객체가 평가 될 때는 Itertor 프로토콜에 따라 StopIteration예외를 발생시킨다.

  • 제너레이터 표현식은 다른 제너레이터 표현식과 함께 사용 할 수 있다. 서로 연결되어 있을 때 매우 빠르게 실행된다.
iter = (len(x) for x in [1,2,3])
squere_tuple_iter = ((x, x**2) for x in iter)

next(squere_tuple_iter)
>>> (1, 1)
next(squere_tuple_iter)
>>> (2, 4)

Better Way 10 - range보다는 enumerate를 사용하자

  • enumerate는 이터레이터를 순회하면서 각 아이템의 인덱스를 얻어오는 간결한 문법을 제공
  • range로 루프를 실행하고 시퀀스에 인덱스로 접근하기보다는 enumerate를 사용하는 게 좋다.
  • enumerate에 두 번째 파라미터를 사용하면 시작 넘버를 설정 할 수 있다(기본값 0)
for i, seq in enumerate(sequence, 1): # i: 1 ~ len(seqeunce)
   # someting....
  

Better Way 11 - 이터레이터를 병렬로 처리하려면 zip을 사용하자

  • 내장 함수 zip은 여러 이터레이터를 병렬로 순회할 때 사용할 수 있다.
  • 파이썬3의 zip은 튜플을 생성하는 lazy generator이다. 파이썬 2의 zip은 전체 결과를 튜플 리스트로 반환한다. (eger loading)
  • 길이가 다른 이터레이터를 사용하면 zip은 그 결과를 조용히 잘라낸다.
  • 내장 모듈 itertoolszip_longest 함수를 쓰면 이터레이터 길이에 상관없이 병렬로 순회 할 수 있다.

Better Way 12 - for와 while 루프 뒤에는 else 블록을 쓰지 말자

  • 파이썬 루프에는 else 블록을 둘 수 있다. else는 루프문이나 try/except 구문에 사용 할 수 있는데, 루프가 정상적으로 수행이 끝나거나(break문 없이), try가 성공적으로 동작 했을 때 수행된다.
  • 루프 뒤에 else 블록을 사용하면 직관적이지 않고 혼동하기 쉬우니 피하자

Better Way 13 - try/except/else/finally에서 각 블록의 장점을 이용하자

  • try/fianlly 구문을 사용하면 예외 발생 여부와 상관없이 정리 코드(리소스 해제 등)를 실행 할 수 있다.
  • else 블록은 try 블록의 코드가 성공적으로 실행 된 후 실행되며, try 블록 코드 양을 최소로 줄이는데 도움을 준다.
profile
어려운 문제를 함께 풀어가는 것을 좋아합니다.

0개의 댓글