Item 1: 파이썬 버전 몇을 사용하고 있는 지 알고 있을 것.
- Python2 사용하지 말 것.
- python version을 잘 확인할 것.
$ python --version
또는 runtime 중에도 sys module 사용하여 확인 가능
import sys
print(sys.version_info)
print(sys.version)
Item 2: PEP8 스타일 가이드를 따르자.
PEP는 Python Enhancement Proposal의 줄임말로, 파이썬 개발자들 사이에서는 통상적으로 지켜지는 룰이므로 이를 따르는 것이 좋고, 뿐만 아니라 나 혼자 해당 코드를 사용한다 하더라도 높은 유지보수성과 가시성을 위해서는 따르는 것이 좋다.
Whitespace
- tab 대신 space 쓸 것
- 문법적으로 의미있는 단계에서는 space 4개
- 한 라인은 79개 문자길이 이하
- 긴 expression에서 다음 line으로 넘어갈 때는 기본 indentation level 로부터 4개의 추가적인 space 줄 것
- 한 파일에서 functions와 classes는 2개의 line만큼 분리
- 한 클래스에서 methods는 1개 line만큼 분리
- dictionary에서 key와 콜론(:) 사이에는 공백이 없고, 상응하는 value 직전에는 하나의 공백(ex. key: value)
- 변수 할당에서 = 앞뒤에는 반드시 하나의 공백이 있어야함.
- type annotation에서 변수이름과 콜론 사이에는 공백이 없고, 타입정보 바로 앞에는 하나의 공백 사용(ex. num: int = 1)
Naming
- 함수, 변수, 속성은 lowercase_underscore 포맷이다.
- Protected instance attributes는 _leading_underscore 포맷이다.
- Private instance attributes는 __double_leading_underscore 포맷이다.
- 클래스는 (exception 포함) CapitalizeWord 포맷이다.
- Module 레벨의 상수는 ALL-CAPS 포맷이다
- 클래스 내의 Instance methods는 첫번째 인자로 self 써야한다.
- 클래스 메소드는 cls를 첫번째 인자로 써야한다.
Expressions and Statements
- inline negation을 써라.(ex. if not a is b 보다는 if a is not b를 써야함)
- empty container나 empty sequence를 length가 0인 것으로 비교하지 말것.(ex. []와 ''의 경우를 len(emptylist)==0 금지) 대신 if not somelist 과 같이 사용. 그러면 True or False로 평가함.
- if, for, while, except 등의 문법에서 여러개의 조건을 하나의 라인으로 쓰지 말 것.
- 꼭 한 라인에 써야한다면 괄호를 쓰거나, line breaks를 이용해 가시성을 높일 것.
- multiple line을 쓸 때는 \ 를 이용할 것.
Imports
- 파일 최상단에 import문 쓸 것
- 모듈 임포트 할 때는 명확하게 표현할 것 (ex. 그냥 import foo 하지말고 from bar import foo 이렇게 쓸 것)
- relative import를 꼭 해야한다면 from . import foo 와 같은 형식으로 할 것.
- 임포트는 standard library modules, third-party modules, 내가 만든 modules 순으로 import 할 것.
Item 3: bytes와 str의 차이를 알자
Python에는 문자 데이터의 sequence를 표현할 때 bytes와 str 의 두 가지를 사용.
Bytes
bytes의 instance는 raw, unsigned 8-bit value를 가지고 있다.(ASCII encoding으로 보여짐)
a = b'h\x65llo'
print(list(a))
print(a)
>>>
[104, 101, 108, 108, 111]
b'hello'
str
str의 instance는 인간 언어의 textual character를 표현하는 Unicode code points를 포함함.
a = 'a\u0300 propos'
print(list(a))
print(a)
>>>
['a', '`', ' ', 'p', 'r', 'o', 'p', 'o', 's']
à propos
목적에 따라서 bytes를, 또는 str을 다뤄야 할 때가 있기 때문에 이 둘 사이의 변환이 필요함.
def to_str(bytes_or_str):
if isinstance(bytes_or_str, bytes):
value = bytes_or_str.decode('utf-8')
else:
value = bytes_or_str
return value # Instance of str
def to_bytes(bytes_or_str):
if isinstance(bytes_or_str, str):
value = bytes_or_str.encode('utf-8')
else:
value = bytes_or_str
return value # Instance of bytes
bytes끼리는 연산(+, ==, 부등호, %)이 가능, str끼리도 가능.
bytes와 str 사이에는 연산이 대부분 불가능. 안하는 게 좋음.(변환 후 하는 것이 좋음)
데이터를 읽고 쓸 때도 bytes와 str을 잘 구별해야함.
bytes의 경우는 'wb', 'rb'를, str의 경우에는 'w', 'b' 사용.
Item 4: F-Strings 스트링 포맷 형식을 선호할 것.
Python에는 4가지의 스트링 포맷 형식이 있는데, 각설하고 그냥 F-String 형식으로 쓰라는 게 필자의 의견.
f_string = f'{key:<10} = {value:.2f}'
위와 같은 형식임.
Item 5: 복잡한 expression보다는 Helper Function을 사용하라.
예를 들어, 어떤 정보를 가져올 때 정보가 비어있거나 할 경우 default로 이를 0과 같은 값으로 설정하고 싶을 경우가 있다.
# For query string 'red=5&blue=0&green='
red = my_values.get('red', [''])[0] or 0
green = my_values.get('green', [''])[0] or 0
opacity = my_values.get('opacity', [''])[0] or 0
print(f'Red: {red!r}')
print(f'Green: {green!r}')
print(f'Opacity: {opacity!r}')
>>>
Red: '5'
Green: 0
Opacity: 0
위와 같이 red, green, opacity에서 get 메소드는 default를 설정해주는 역할을 함. 이 메소드를 안 쓰면 우리가 명시적으로 if -else 문을 사용해서 복잡하게 정의를 해줘야함. 위와 같은 상황에서 그냥 도우미 함수를 쓰는 게 훨씬 python스럽다는 게 필자의 의견.
Item 6: Indexing 보다는 Multiple assignment unpacking을 선호할 것.
보통은 index를 통하여 tuple 의 값을 접근했다.
item = ('Peanut butter', 'Jelly')
first = item[0]
second = item[1]
print(first, 'and', second)
>>>
Peanut butter and Jelly
하지만 Python에서는 multiple assignment를 지원함.
item = ('Peanut butter', 'Jelly')
first, second = item # Unpacking
print(first, 'and', second)
>>>
Peanut butter and Jelly
또 swap 의 경우도 원래는 temp라는 변수를 보통 이용해서 swap을 진행했는데, 파이썬의 경우는
def bubble_sort(a):
for _ in range(len(a)):
for i in range(1, len(a)):
if a[i] < a[i-1]:
a[i-1], a[i] = a[i], a[i-1] # Swap
위와 같이 a[i-1], a[i] = a[i], a[i-1] 처럼 swap 가능.
동작이 되는 과정은
(1) 우변이 먼저 튜플로 바뀜. (a[i], a[i-1])
(2) 그 튜플이 temporary 즉 이름없는 tuple에 담김.
(3) 좌변에 unpacking pattern으로 할당.
Item 7: range 보다는 enumerate를 선호할 것.
단순하게 컨테이너의 값들을 열거하는 게 아니라, index도 함께 표현하고 싶을 때가 있다.
flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
for i in range(len(flavor_list)):
flavor = flavor_list[i]
print(f'{i + 1}: {flavor}')
>>>
1: vanilla
2: chocolate
3: pecan
4: strawberry
위는 전혀 파이썬스럽지 않은 코드임. 이 때 enumerate라는 빌트인 함수를 사용하는 게 좋다. enumerate는 interator와 lazy generator를 wrap하고 loop index와 next value의 pair를 반환한다. 따라서
for i, flavor in enumerate(flavor_list):
print(f'{i + 1}: {flavor}')
>>>
1: vanilla
2: chocolate
3: pecan
4: strawberry
위와 같은 코드가 좋다. 그런데 만약 index를 1부터 시작하고 싶을 때, 위와 같은 코드가 아니라
for i, flavor in enumerate(flavor_list, 1):
print(f'{i}: {flavor}')
위처럼 두번째 인자에 1을 주면 첫 index의 시작을 1로 설정가능하다.
머신러닝과 딥러닝에서 데이터셋을 관리할 때, 위의 enumerate함수가 크게 도움이 된다.
Item 8: Parallel한 Iterator 를 위해서는 zip 을 사용할 것.
아래와 같은 코드를 보면
names = ['Cecilia', 'Lise', 'Marie']
counts = [len(n) for n in names]
counts 리스트는 names로부터 list comprehension에 의해 만들어졌다. names와 counts는 길이도 같고 같은 index에 값들이 서로 상응하기 때문에 병렬적으로 사용되는 경우가 많다.
longest_name = None
max_count = 0
for i in range(len(names)):
count = counts[i]
if count > max_count:
longest_name = names[i]
max_count = count
print(longest_name)
>>>
Cecilia
위와 같은 코드에서는 인덱스를 이용했는데, 이는 전혀 파이썬스러운 코드가 아님. enumerate를 이용하면 조금 낫긴 하지만 아래와 같이
for name, count in zip(names, counts):
if count > max_count:
longest_name = name
max_count = count
zip 을 이용하면 훨씬 깔끔함.
zip의 경우에는 해당 리스트들의 길이가 같아야하는데( 안 같으면 짧은 리스트의 마지막에 끊김), zip_longest를 사용하면 없는 위치에 None이 출력된다. 필요에 따라 사용하면 좋을 듯.
Item 9: for와 while 루프 뒤에 else 블록 사용을 피하자.
else 블록을 반복문 뒤에 사용하지 않고 반복문 안에서 condition을 만족하는 경우 바로 return하거나 flag를 이용하는 두 가지 방법을 사용하라는 것이 필자의 의견.
Item 10: Assignment Expressions를 이용해서 반복을 피하자.
파이썬 3.8부터 assignment expression(walrus operator라고도 부름)이 생김.
count = fresh_fruit.get('lemon', 0)
if count:
make_lemonade(count)
else:
out_of_stock()
위처럼 count를 구하고 나서 해당 count 변수를 if문에서 사용하는 경우 위의 방법보다는
if count := fresh_fruit.get('lemon', 0):
make_lemonade(count)
else:
out_of_stock()
:= 연산자(walrus)를 사용하는 것이 더욱 코드를 간결하고 의미를 정확하게 전달할 수 있다는 게 필자의 의견.
그런데 개인적인 생각이지만 남발하게 되면 오히려 가독성이 떨어지고 헷갈릴 수 있을 것 같다.
/ /문제제기 및 피드백 언제든지 감사히 받겠습니다.
'Computer Science > Effective Python' 카테고리의 다른 글
Chapter 5. Classes and Interfaces (0) | 2023.01.13 |
---|---|
Chapter 4. Comprehensions and Generators (1) | 2023.01.11 |
Chapter 3. Functions (0) | 2023.01.07 |
Chapter 2. Lists and Dictionaries (0) | 2023.01.06 |