Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[클린코드 파이썬] 6장: 파이썬다운 코드를 작성하는 법 본문

Python/클린 코드, 이제는 파이썬이다

[클린코드 파이썬] 6장: 파이썬다운 코드를 작성하는 법

끈기JK 2024. 9. 1. 14:52

파이썬의 선(禪)

팀 피터스가 작성한 '파이썬의 선Zen of Python'은 파이썬 언어의 설계와 파이썬 프로그램을 위한 20가지 지침 모음이다. import this 문을 실행하면 나타난다.

import this
# The Zen of Python, by Tim Peters
#
# Beautiful is better than ugly.
# 생략

 

  1. 아름다운 것이 보기 흉한 것보다 낫다. 아름다운 코드란 읽기 쉽고 이해하기 쉬운 코드라 할 수 있다.
  2. 명시적인 것이 암시적인 것보다 낫다. 언어를 깊게 이해해야만 완전하게 알 수 있는 모호한 언어 기능 이면에 코드 기능을 감춰서는 안된다.
  3. 단순한 것이 복잡한 것보다 낫다. 복잡한 것은 뒤얽힌 것보다 낫다. 이 두 가지 경구는 무엇을 만들든 단순하게도, 복잡하게도 할 수 있다는 사실을 깨우쳐준다.
  4. 펼쳐놓은 수평 구조가 중첩된 계층 구조보다 낫다. 프로그래머들은 자신의 코드를 범주별로, 특히 하위 범주들을 겹겹이 중첩시킨 구조로 정리하는 것을 좋아한다. 하지만 이런 계층 구조는 조직화를 부여하기보단, 번잡함만 늘릴 뿐이다. 만약 코드에서 spam.eggs.bacon.ham() 또는 spam['eggs']['bacon']['ham'] 같은 부분이 보인다면, 여러분은 코드를 너무 난해하게 만들고 있는 것이다.
  5. 드문드문 여유로운 것이 조밀한 것보다 낫다. 프로그래머들은 print('\n'.join("%i bytes = %i bits which has %i possiblevalues." % (j, j*8, 256**j-1) for j in (1 << i for i in range(8))))처럼 짧은 코드에 많은 기능을 쑤셔넣기를 좋아하는 편이다. 하지만 여러 줄로 분산된 코드는 조밀하게 한 줄로 합쳐진 코드보다 읽기 쉬운 경우가 많다.
  6. 가독성은 중요하다. 이름을 지을 때 글자를 생략하지 말고, 지나치게 간소화된 코드도 쓰지 마라. 시간을 들여서라도 변수와 함수에 대한 설명적이고 구체적인 이름을 만들자. 코드 섹션 사이에 있는 빈 줄은 마치 책의 단락 구분과 동일한 기능을 수행하여 독자들에게 통으로 읽어야 할 부분이 무엇인지를 알려준다.
  7. 규칙을 어길 만큼 특별한 경우는 없다. 비록 실용성이 순수성에 우선한다 할지라도. 
  8. 에러를 마주치고 조용히 넘어가서는 안 된다. 명시적으로 에러를 감추려는 의도가 아니라면. 버그는 근본 원이이 발생한 시점으로부터 한참 후에 발견되기 때문에 디버깅하기도 더 어려워질 것이다.
  9. 모호함을 직면하고도 이를 어림짐작하려는 유혹에 빠져서는 안 된다. 컴퓨터는 마법이 아니다. 코드가 작동하지 않는다면 분명한 이유가 있기 때문이다. 오직 신중하고 비판적인 사고만이 문제를 해결할 수 있다. 어떤 해결책이 먹히는 듯할 때까지 무턱대고 시도하려는 유혹에 빠져서는 안 도니다.
  10. 문제를 해결할 (바람직하게도 단 하나의) 명확한 방법이 존재할 것이다. 같은 작업을 하는 코드에 서너 가지의 방법이 존재한다는 것은 양날의 검이다. 즉 코드 작성 방식에 융통성은 많아지겠지만, 다른 사람들의 코드를 읽기 위해 작성 가능한 모든 경우의 수를 다 배워야만 하는 어려움이 생긴다.
  11. 네덜란드인이 아니라면 그런 해결 방법이 명확히 보이지 않을지도 모른다. 이 말은 농담이다.
  12. 영원히 안하는 것보단 지금 하는 것이 낫다. 비록 때로는, 지금 *당장* 하는 것보단 영원히 안 하는 것이 나을 수도 있지만. 이 두 문장의 경구는 빠르게 실행되는 코드보다 느리게 실행되는 코드 쪽이 확실히 더 나쁘다는 것을 말해준다.
  13. 구현 결과를 설명하기 어렵다면 그것은 나쁜 아이디어다. 구현 결과를 설명하기 쉽다면 좋은 아이디어일 수도 있다.
  14. 네임스페이스는 정말 좋은 아이디어다. 자주 사용하자! 네임스페이스는 명명 충돌을 방지하고자 식별자를 위해 만든 별도의 컨테이너다. 예를 들어 open() 내장 함수와 webbrowser.open() 함수는 서로 이름이 같지만 각기 다른 함수를 참조한다.

 

 

 

의미 있는 들여쓰기

파이썬은 들여쓰기가 선택사항으로 허용되는 정도가 아니라, 코드에 일관성 있는 가독성을 강제한다.

 

 

 

흔히 잘못 사용되는 구문

 

range()보다는 enumerate()를 사용하자

animals = ['cat', 'dog', 'moose']
for i in range(len(animals)):
    print(i, animals[i])

# 0 cat
# 1 dog
# 2 moose

리스트나 시퀀스를 내장 enumerate() 함수에 전달하면 해당 인덱스에 대응하는 항목과 인덱스에 대한 정수가 반환된다.

# 파이썬다운 예
animals = ['cat', 'dog', 'moose']
for i, animal in enumerate(animas):
    print(i, a nimal)

# 0 cat
# 1 dog
# 2 moose

인덱스 없이 항목만 필요한 경우도, 파이썬다운 방식으로 리스트를 직접 순회할 수 있다.

# 파이썬다운 예
animals = ['cat', 'dog', 'moose']
for animal in animas:
    print(a nimal)

# cat
# dog
# moose

 

 

 

open()과 close()보다는 with 문을 사용하자

open() 함수는 파일을 읽거나 쓰는 메소드가 포함된 파일 객체를 반환한다.

# 파이썬답지 않은 예
fileObj = open('spam.txt', 'w')
fileObj.write('Hello, world!')
fileObj.close()

이러한 방식으로 코드를 작성하면 파일이 닫히지 않을 수 있다.

이렇게 하기보다는 with 문을 사용해서, 실행 흐름이 with 문 블록을 벗어나면 자동으로 close()를 호출하게 할 수 있따.

# 파이썬다운 예
with open('spam.txt', 'w') as fileObj:
    fileObj.write('Hello, world!')

 

 

 

== 대신 is를 써서 None과 비교하자

== 동등 연산자equality operator는 두 객체의 값value을 비교하는 반면, is 동일 연산자identity operator는 두 객체의 아이디identity를 비교한다. None과 비교할 때는 거의 대부분 == 연산자보다는 is 연산자를 사용해야 한다.

class SomeClass:
    def __eq__(self, other):
        if other is None:
            return True
            
spam = SomeClass()
spam == None
# True
spam is None
# False

클래스가 == 연산자를 이렇게 오버로딩할 가능성은 희박하지만, 관용적으로 파이썬에서는 만약의 경우를 대비해서 항상 == None 보다는 is None을 사용해야 한다.

마지막으로, True와 False 값에 is 연산자를 사용해서는 안 된다. spam == True 또는 spam == False처럼 == 동등 연산자를 사용해 True와 False 값을 비교해야 한다. 실제로는 if spam == True: 또는 if spam == False: 보다 if spam: 또는 if not spam: 처럼 연산자와 부울 값을 완전히 생략하는 방식이 좀 더 일반적인 사용 예다.

 

 

 

문자열 포매팅

 

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

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

# 파이썬답지 않은 예
print('The file is in C:\\Users\\AI\\Desktop\\Info\\Archive\\Spam')
# 파이썬다운 예
print(r'The file is in C:\UsersAI\Desktop\Info\Archive\Spam')

 

 

f-문자열을 사용한 문자열 포매팅

f-문자열의 중괄호 사이에 변수 이름을 포함시켜 해당 변수에 저장된 문자열을 삽입할 수 있다.

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.'

f-문자열 내부에서 문자 중괄호를 사용해야 할 경우 중괄호를 추가로 사용하여 이스케이프할 수 있다.

spam = 42
f'This prints the value in spam: {spam}'
'This prints the value in spam: 42'
f'This prints literal curly braces: {{spam}}'
'This prints literal curly braces: {spam}'

 

 

 

리스트의 얕은 사본 만들기

시작과 끝의 두 가지 인덱스를 모두 생략하면 시작 인덱스는 0, 끝 인덱스는 리스트의 끝이 된다. 이는 효과적으로 리스트의 사본을 만들어낸다.

spam = ['cat', 'dog', 'rat', 'eel']
eggs = spam[:]
eggs
# ['cat', 'dog', 'rat', 'eel']
id(spam) == id(eggs)
# False

그러나 [:]가 좀 이상해 보이기도 해서, copy 모듈의 copy() 함수를 사용하여 리스트의 얕은 사본을 만드는 편이 더 가독이성이 좋다.

# 파이썬다운 예
import copy
spam = ['cat', 'dog', 'rat', 'eel']
eggs = copy.copy(spam)
id(spam) == id(eggs)
# False

[:]를 코드 작성에 사용하는 방법은 추천하지 않는다. [:]와 copy.copy() 모두 얕은 사본을 만든다는 것을 명심하자.

 

 

 

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

 

딕셔너리에서 get()과 setdefault()를 사용하자

존재하지 않는 딕셔너리 키에 접근하려고 하면 KeyError 에러가 발생하는데, 프로그래머들은 이러한 상황을 피하고자 간혹 파이썬답지 않은 코드를 작성하게 된다.

# 파이썬답지 않은 예
numberOfPets = {'dogs': 2}
if 'cats' in numberOfPets:  # cats가 키로 존재하는지 확인
    print('I have', numberOfPets['cats'], 'cats.')
else:
    print('I have 0 cats.')

# I have 0 cats

 

이런 패턴은 흔하기 때문에, 딕셔너리에는 키가 존재하지 않을 경우에 반환할 기본값을 지정할수 있는 get() 메소드가 있다.

# 파이썬다운 예
numberOfPets = {'dogs': 2}
print('I have', numberOfPets.get('cats', 0), 'cats.')

# I have 0 cats

 

반대로 키가 없으면 기본값을 설정할 수도 있다.

# 파이썬답지 않은 예
numberOfPets = {'dogs': 2}
if 'cats' not in numberOfPets:
    numberOfPets['cats'] = 0

numberOfpets['cats'] += 10
numberOfpets['cats']
# 10

딕셔너리는 더 파이썬다운 setdefault() 메소드를 제공한다.

# 파이썬다운 예
numberOfPets = {'dogs': 2}
numberOfPets.setdefault('cats', 0)  # cats가 존재할 경우 아무것도 하지 않는다
# 0
numberOfPets['cats'] += 10
numberOfPets['cats']
# 10

 

 

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

이 클래스 사용 방법은 collections 모듈을 임포트한 다음에 기본값에 사용할 데이터 타입을 넘겨 collections.defaultdict()를 호출하는 식으로 기본 딕셔너리를 만들 수 있다. 예를 들어 int를 collections.defaultdict()에 넘기면 존재하지 않는 키의 기본값에 대해 0을 사용하는 딕셔너리와 유사한 객체를 만들 수 있다.

import collections
scores = collections.defaultdict(int)
scores
# defaultdict(<class 'int'>, {})
scores['Al'] += 1  # Al키를 처음부터 설정할 필요가 없다
scores

int() 함수를 호출하는 것이 아니라 그냥 전달하는 것이므로 collections.defaultdict(int)에서 int 뒤의 괄호를 생략한다.

 

 

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

파이썬에는 switch 문이 없기 때문에 간혹 파이썬 프로그래머들은 다음 예시와 같이 코드를 작성한다.

# 다음의 모든 if와 elif 조건문은 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'

일부 파이썬 프로그래머들은 if-elif 문을 사용하는 대신 딕셔너리 값을 설정하는 쪽을 선호한다.

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

딕셔너리를 사용하면 코드는 좀 더 간결해지지만 다소 읽기 어려워질 수도 있다. 이 코딩 규약을 사용할지 말지는 사용자에게 달려 있다.

 

 

 

조건식: 파이썬의 '보기 흉한' 3항 연산자

파이썬에서는 if와 else 키워드를 특이하게 배열해서 구현했다.

valueIfTrue = 'Access granted'
valueIfFalse = 'Access denied'
condition = True
message = valueIfTrue if condition else valueIfFalse
message
# 'Access granted'

귀도 반 로섬은 농담삼아 자신의 구문 디자인을 '일부러 보기 흉하게 만들었다."고 표현했다. 3항 연산자를 제공하는 대부분의 언어들은 조건을 먼저 나열하고 그다음에 참값과 거짓값을 나열한다.

 

 

 

변수값 작업

 

체이닝 할당과 비교 연산자

숫자가 일정 범위 내에 있는지 확인해야 할 때

# 파이썬답지 않은 예
if 42 < spam and spam < 99:

그러나 파이썬에서는 체이닝 비교 연산자가 있기 때문에 and 연산자를 사용할 필요가 없다.

# 파이썬다운 예
if 42 < spam < 99:

 

= 할당 연산자도 체이닝이 된다. 여러 변수를 한 줄의 코드에서 같은 값으로 설정할 수 있다.

# 파이썬다운 예
spam = eggs = bacon = 'string'
print(spam, eggs, bacon)
# string string string

 

 

변수가 여러 값 중 하나인지 여부를 확인하자

여러 값을 튜플에 넣고 in 연산자를 사용하여 해당 튜플에 변수의 값이 존재하는지를 확인할 수 있다.

# 파이썬다운 예
spam = 'cat'
spam in ('cat', 'dog', 'moose')
# True