파이썬스러운 코드 작성법(PYTHONIC CODE)

Updated:

파이썬에서는 파이썬스러운 코드 작성법이 존재한다. 파이썬스러운 코드를 파이쏘닉 코드(Pythonic code)라고 부른다. 개발자들이 놓치기 쉬운 부분이나 가독성을 높이는 코드 그리고 효율적인 코드들을 모아두었다. 이런 코드들은 파이썬 코드에서 널리 쓰이는 용법이므로 코드를 작성할 때와 타인의 코드를 읽을 때 모두 도움이 된다. 출처(inventwithpython.com)에서 원문을 볼 수 있다.

1. 잘못된 사용 방법

1.1. range는 enumerate()를 사용하자.

파이썬에서 많은 경우 len과 range를 조합한 for 문은 잘못된 사용이다.

>>> # Unpythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for i in range(len(animals)):
...     print(i, animals[i])
...
0 cat
1 dog
2 moose

enuerate 함수를 사용하자.

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for i, animal in enumerate(animals):
...     print(i, animal)
...
0 cat
1 dog
2 moose

index가 필요 없는 경우는 리스트를 직접 순회하자.

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for animal in animals:
...     print(animal)
...
cat
dog
moose

1.2. open/close는 with 문을 사용하자.

open/close를 사용하여 파일에 접근한 예제.

>>> # Unpythonic Example
>>> fileObj = open('spam.txt', 'w')
>>> fileObj.write('Hello, world!')
13
>>> fileObj.close()

open/close를 사용하여 파일에 접근한 예제로 try-except 문 사용에서 오류가 발생할 가능성이 존재한다.

>>> # Unpythonic Example
>>> try:
...     fileObj = open('spam.txt', 'w')
...     eggs = 42 / 0    # A zero divide error happens here.
...     fileObj.close()  # This line never runs.
... except:
...     print('Some error occurred.')
...
Some error occurred.

파이썬에서는 open/close 대신 with 문을 사용하자.

>>> # Pythonic Example
>>> with open('spam.txt', 'w') as fileObj:
...     fileObj.write('Hello, world!')
...

1.3. None과 비교는 is를 사용하자.

None 객체와의 비교는 is를 사용한다. ==를 사용하여 None 객체와의 비교를 하면 오류가 발생할 가능성이 존재한다.

>>> # Unpythonic Example
>>> if spam == None:
...     print('Not pythonic.')
...

None은 is로 비교하자.

>>> # Pythonic Example
>>> if spam is None:
...     print('Pythonic.')
...

1.4. Boolean 비교는 is나 ==를 사용하지 않는다.

Boolean 비교는 is나 ==를 사용하지 않고 if 문에 바로 기술한다.

>>> # Unpythonic Example
>>> if spam is True:
...     print('Not pythonic.')
...

if 문에 바로 기술한다.

>>> # Pythonic Example
>>> if isSpam:
...     print('Pythonic.')
...

False 비교문은 not 을 사용한다.

>>> # Pythonic Example
>>> if not isSpam:
...     print('Pythonic.')
...

2. 문자열 포맷팅

2.1. 문자열에 백슬래시가 많은 경우에는 원시 문자열을 사용하자.

이스케이프 문자를 사용하기 위해서는 백슬래시 문자를 추가로 넣어줘야 하는데 이는 가독성을 해치는 방법이다.

>>> # Unpythonic Example
>>> print('The file is in C:\\Users\\Al\\Desktop\\Info\\Archive\\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

원시 문자열(raw string)은 r 접두사가 붙은 문자열 리터럴이며, 백슬래시를 이스케이프 문자로 취급하지 않는다. 백슬래시가 많은 경우 원시 문자열로 표현하자.

>>> # Pythonic Example
>>> print(r'The file is in C:\Users\Al\Desktop\Info\Archive\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

2.2. F-문자열 사용한 문자열 포매팅

F-문자열(format string)은 다른 문자열이 내포된 문자열을 만드는 편리한 방법이다.

>>> name, day, weather = 'Al', 'Sunday', 'sunny'
>>> f'Hello, {name}. Today is {day} and it is {weather}.'
'Hello, Al. Today is Sunday and it is sunny.'

중괄호에는 표현식도 포할될 수 있다.

>>> width, length = 10, 12
>>> f'A {width} by {length} room has an area of {width * length}.'
'A 10 by 12 room has an area of 120.'

3. 리스트의 얕은 사본 만들기

리스트의 값 복사가 필요한 경우, =로 값을 지정하면 참조만 복사한다. 사본 복사가 필요한 경우는 copy 모듈의 copy를 사용하자.

>>> # Pythonic Example
>>> import copy
>>> spam = ['cat', 'dog', 'rat', 'eel']
>>> eggs = copy.copy(spam)
>>> id(spam) == id(eggs)
False

4. 파이썬다운 딕셔너리 사용법

4.1. get()과 setdefault()를 사용하여 기본값을 설정하자.

cats가 딕셔너리에 존재하는지 확인하는 파이썬답지 않은 예제 코드이다.

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' in numberOfPets: # Check if 'cats' exists as a key.
...     print('I have', numberOfPets['cats'], 'cats.')
... else:
...     print('I have 0 cats.')
...
I have 0 cats.

딕셔너리에 get 메소드로 접근하면 키가 없을 경우 지정한 기본값을 받아올 수 있다.

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> print('I have', numberOfPets.get('cats', 0), 'cats.')
I have 0 cats.

딕셔너리에 키가 없는 경우 기본값을 지정할 때는 setdefault 메소드를 사용하자. 아래는 파이썬 답지 않은 방법이다.

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' not in numberOfPets:
...     numberOfPets['cats'] = 0
...
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

setdefault() 메소드를 사용하면 파이썬다운 코드 작성이 된다.

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> numberOfPets.setdefault('cats', 0) # Does nothing if 'cats' exists.
0
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

4.2. 기본값을 위해 collections.defaultdict를 사용하자

collections.default 클래스를 사용하여 기본값에 사용할 데이터 타입을 지정할 수 있다. 아래처럼 int 타입을 지정하면 초기값이 0으로 설정된다.

>>> import collections
>>> scores = collections.defaultdict(int)
>>> scores
defaultdict(<class 'int'>, {})
>>> scores['Al'] += 1 # No need to set a value for the 'Al' key first.
>>> scores
defaultdict(<class 'int'>, {'Al': 1})
>>> scores['Zophie'] # No need to set a value for the 'Zophie' key first.
0
>>> scores['Zophie'] += 40
>>> scores
defaultdict(<class 'int'>, {'Al': 1, 'Zophie': 40})

list를 데이터 타입으로 지정하여 빈 list를 기본값으로 사용한 예제이다.

>>> import collections
>>> booksReadBy = collections.defaultdict(list)
>>> booksReadBy['Al'].append('Oryx and Crake')
>>> booksReadBy['Al'].append('American Gods')
>>> len(booksReadBy['Al'])
2
>>> len(booksReadBy['Zophie']) # The default value is an empty list.
0

4.3. switch 문 대신 딕셔너리를 사용하자.

if-elif-else 문의 반복은 파이썬스럽지 않다. 딕셔너리를 사용하면 이를 간결하게 표현할수 있다.

반복되는 elif 는 파이썬스럽지 않다.

# All of the following if and elif conditions have "season ==":
if season == 'Winter':
    holiday = 'New Year\'s Day'
elif season == 'Spring':
    holiday = 'May Day'
elif season == 'Summer':
    holiday = 'Juneteenth'
elif season == 'Fall':
    holiday = 'Halloween'
else:
    holiday = 'Personal day off'

딕셔너리를 사용하면 간결하게 파이썬스러운 표현이 가능하다.

holiday = {'Winter': 'New Year\'s Day',
           'Spring': 'May Day',
           'Summer': 'Juneteenth',
           'Fall':   'Halloween'}.get(season, 'Personal day off')

5. 파이썬의 3항 연산자

3항 연산자가 필요한 경우 if-else 문으로 작성하는 것이 가장 파이썬스러운 표현이다.

>>> # Pythonic Example
>>> condition = True
>>> if condition:
...     message = 'Access granted'
... else:
...     message = 'Access denied'
...
>>> message
'Access granted'

파이썬에서의 3항 연산자는 if와 else를 사용하여 표현한다. 파이썬에서의 3항 연산자는 보기 흉한 형태가 되었다. 귀도 반 로섬은 이에 대해 일부러 흉하게 만들었다고 표현했다. (파이썬의 3항 연산자는 오랜 논란의 역사가 있다…) 파이썬의 3항 연산자는 보기 흉한 형태이나 개발자들 사이에서 널리 쓰이고 있다.

>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
1 >>> message = valueIfTrue if condition else valueIfFalse
>>> message
'Access granted'
2 >>> print(valueIfTrue if condition else valueIfFalse)
'Access granted'
>>> condition = False
>>> message = valueIfTrue if condition else valueIfFalse
>>> message
'Access denied'

and와 or를 사용한 가짜 3항 연산자는 파이썬스럽지 않으며 오류 가능성도 내포한다.

>>> # Unpythonic Example
>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
>>> condition and valueIfTrue or valueIfFalse
'Access granted'

조건 표현식을 중첩하여 사용하는 방법은 파이썬스럽지 않다.

>>> # Unpythonic Example
>>> age = 30
>>> ageRange = 'child' if age < 13 else 'teenager' if age >= 13 and age < 18 else 'adult'
>>> ageRange
'adult'

6. 변수값 작업

6.1. 체이닝 할당과 비교 연산자

일정 범위의 값을 확인하는 파이썬스럽지 않은 예제이다.

# Unpythonic Example
if 42 < spam and spam < 99:

파이썬스러운 방법이다.

# Pythonic Example
if 42 < spam < 99:

파이썬스러운 할당 연산자 체이닝 예제이다.

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> print(spam, eggs, bacon)
string string string

파이썬스러운 == 비교 연산자 체이닝 예제이다.

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> spam == eggs == bacon == 'string'
True

6.2. 변수가 여러 값 중 하나인지 확인하는 방법

변수가 여러 값 중에 하나인지 확인하는 방법으로 == 비교 연산자를 나열하는 방법이 있을 것이다. 이 보다는 아래 예제와 같이 여러 값을 튜플에 넣고 in 연산자를 사용하여 변수의 값이 존재하는지를 확인할 수 있다. 보다 파이썬스러운 방법이다.

>>> # Pythonic Example
>>> spam = 'cat'
>>> spam in ('cat', 'dog', 'moose')
True

7. 리스트 컴프리헨션

리스트 컴프리헤션(List Comprehensions)을 사용하면 map()과 filter()를 사용하지 않고 파이썬스러운 방법으로 새 리스트를 만들 수 있다. map()과 filter()는 오래된 방법이 되었다.

>>> # Unpythonic Example
>>> mapObj = map(lambda n: str(n), [8, 16, 18, 19, 12, 1, 6, 7])
>>> list(mapObj)
['8', '16', '18', '19', '12', '1', '6', '7']

리스트 컴프리헨션을 사용하여 map을 대체하였다.

>>> # Pythonic Example
>>> [str(n) for n in [8, 16, 18, 19, 12, 1, 6, 7]]
['8', '16', '18', '19', '12', '1', '6', '7']

필터를 사용하여 짝수만 반환하여 새로운 리스트를 생성한다.

>>> # Unpythonic Example
>>> filterObj = filter(lambda n: n % 2 == 0, [8, 16, 18, 19, 12, 1, 6, 7])
>>> list(filterObj)
[8, 16, 18, 12, 6]

리스트 컴프리헨션을 사용하여 filter를 대체하였다.

>>> # Pythonic Example
>>> [n for n in [8, 16, 18, 19, 12, 1, 6, 7] if n % 2 == 0]
[8, 16, 18, 12, 6]

Leave a comment