파이썬 코딩의 기술

Page 1


1장 파이썬다운 생각

파이썬 커뮤니티는 특별한 스타일을 따르는 코드를 설명하는 데 ‘파이썬다운 (Pythonic)’이라는 형용사를 사용하기 시작했다. 이러한 파이썬 특유의 표현들은

시간이 흐르면서 그 표현을 사용하여 다른 사람과 작업하면서 쌓인 경험을 통해 생겨났다. 이 장에서는 파이썬으로 가장 일반적인 작업을 하는 최선의 방법이 무엇인지 알아본다. 2장 함수

파이썬의 함수에는 다양한 외부 기능이 있어 프로그래머의 삶을 한결 편하게 만 들어준다. 일부 기능은 다른 프로그래밍 언어와 유사하지만, 많은 기능이 파이 썬에서만 가능하다. 이 장에서는 함수를 사용하여 의도한 대로 명확하게 표현하 는 방법, 재사용을 늘리는 방법, 버그를 줄이는 방법을 다룬다. 3장 클래스와 상속

파이썬은 객체 지향 언어다. 파이썬으로 작업을 하다 보면 종종 새 클래스를 작 성하고 클래스가 인터페이스와 계층을 통해 상호 작용하는 방법을 정의해야 할 때가 있다. 이 장에서는 클래스와 상속을 사용하여 객체가 의도한 대로 동작하 게 하는 방법을 알아본다. 4장 메타클래스와 속성

메타클래스와 동적 속성은 파이썬의 강력한 기능이다. 그러나 지극히 기이하고 예상치 못한 동작을 초래하기도 한다. 이 장에서는 최소 놀람 규칙(rule of least surprise)을 따를 수 있도록 이러한 메커니즘을 사용하는 일반적인 용법을 배

운다. 5장 병행성과 병렬성

파이썬을 사용하면 동시에 여러 가지 작업을 매끄럽게 처리하는 병행 프로그램 을 쉽게 작성할 수 있다. 또한, 시스템 호출, 서브프로세스, C 확장을 이용한 병 렬 작업을 수행할 수도 있다. 이 장에서는 이러한 미묘하게 다른 상황에서 파이 썬을 활용하는 가장 좋은 방법을 살펴본다.

파이썬 코딩의 기술(본문)5차.indd 4

2016-03-18 오전 10:40:47


6장 내장 모듈

파이썬은 프로그램을 작성하는 데 필요한 많은 중요 모듈과 함께 설치된다. 이 러한 표준 패키지는 파이썬의 언어적인 특성과 밀접하게 관련되어 있으며 파이 썬 언어 명세의 일부일 수도 있다. 이 장에서는 필수 내장 모듈을 다룬다. 7장 협력

파이썬 프로그램을 협업하여 작성할 때는 코드를 어떻게 작성할지 신중하게 생 각해야 한다. 혼자 작업하더라도 다른 사람이 작성한 모듈을 사용하는 방법을 이해해야 한다. 이 장에서는 파이썬 프로그램을 협업으로 작성하는 데 도움이 되는 표준 도구와 모범 사례를 알아본다. 8장 제품화

파이썬은 다양한 배포 환경을 지원한다. 프로그램을 견고하고 안전하게 작성할 수 있도록 도와주는 내장 모듈도 있다. 이 장에서는 파이썬을 사용하여 런타임 시 품질과 성능을 극대화하기 위해 프로그램을 디버깅, 최적화, 테스트하는 방 법을 배운다.

책에서 사용한 규칙 책에 실린 파이썬 코드 조각은 고정폭 글꼴로 작성했으며 문법을 강조했다. 책 의 판형에 코드 예제를 좀 더 잘 맞추고 가장 중요한 부분을 강조하려고 파이썬 스타일을 좀 더 예술적으로 사용했다. 줄이 길 때는 화살표

로 한 줄이라는 것

을 표시했다. 주석 표시(#...)로 코드를 줄여서 설명할 필요가 없는 코드는 생략 했다. 예제 코드의 양을 줄이려고 포함되어 있는 문서도 뺐다. 하지만 여러분은 프로젝트를 진행할 때 이렇게 해서는 안 된다. 반드시 스타일 가이드(Better way 2 “PEP 8 스타일 가이드를 따르자” 참고)에 따라 문서를 작성해야 한다 (Better way 49 “모든 함수, 클래스, 모듈에 docstring을 작성하자” 참고). 책에 실린 코드 조각 대부분은 해당 코드를 실행했을 때 출력되는 결과와 함께 수록되어 있다. 여기서 ‘출력’은 콘솔이나 터미널의 출력, 즉 대화식 인터프리터

파이썬 코딩의 기술(본문)5차.indd 5

2016-03-18 오전 10:41:21


에서 파이썬 프로그램을 실행하면 볼 수 있는 결과를 의미한다. 출력 섹션은 고 정폭 글꼴로 되어 있고 파이썬 대화식 프롬프트처럼 줄 앞 부분에 >>> 기호를 표시했다. 코드 조각을 파이썬 셸에 입력하고 예상한 결과가 출력되는 식이다. 마지막으로 고정폭 글꼴로 된 부분 중에 >>>로 시작되지 않는 부분이 있다. 이 것은 파이썬 인터프리터의 결과가 아니라 실행된 프로그램의 결과다. 이러한 예 제는 종종 $ 문자로 시작되며, 내가 배시(Bash) 같은 셸에서 프로그램을 실행한 다는 뜻이다.

예제 코드 다운로드 책에 실린 예제는 잘라내지 않은 전체 프로그램으로 제공하니 유용하게 사용하 기 바란다. 이렇게 제공된 코드를 직접 이리저리 수정해보면 프로그램이 설명대 로 동작하는 이유를 이해할 수 있다. 책에 실린 모든 코드 조각의 소스 코드는 다음 깃허브 리포지토리에서 확인할 수 있다. •https://github.com/gilbutITbook/006764

파이썬 코딩의 기술(본문)5차.indd 6

2016-03-18 오후 1:29:14


목차

1장 ▶ 파이썬다운 생각

015

Better way 1 사용

중인 파이썬의 버전을 알자  016

Better way 2 PEP

8 스타일 가이드를 따르자  017

Better way 3 bytes,

str, unicode의 차이점을 알자  020

Better way 4 복잡한

표현식 대신 헬퍼 함수를 작성하자  024

Better way 5 시퀀스를 Better way 6 한

슬라이스하는 방법을 알자  028

슬라이스에 start, end, stride를 함께 쓰지 말자  032

Better way 7 map과

filter 대신 리스트 컴프리헨션을 사용하자  034

Better way 8 리스트

컴프리헨션에서 표현식을 두 개 넘게 쓰지 말자  036

Better way 9 컴프리헨션이

클 때는 제너레이터 표현식을 고려하자  039

Better way 10 range보다는

enumerate를 사용하자  041

Better way 11 이터레이터를

병렬로 처리하려면 zip을 사용하자  043

Better way 12 for와

while 루프 뒤에는 else 블록을 쓰지 말자  046

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

finally 블록  050 else 블록  051 모두 함께 사용하기  051

2장 ▶ 함수

053

Better way 14 None을

반환하기보다는 예외를 일으키자  054

Better way 15 클로저가

변수 스코프와 상호 작용하는 방법을 알자  057

데이터 얻어오기  060 파이썬 2의 스코프  062 Better way 16 리스트를 Better way 17 인수를

파이썬(본문)4차.indd 10

반환하는 대신 제너레이터를 고려하자  063

순회할 때는 방어적으로 하자  066

2016-03-16 오후 4:18:48


Better way 18 가변

위치 인수로 깔끔하게 보이게 하자  072

Better way 19 키워드 Better way 20 동적

인수로 선택적인 동작을 제공하자  075

기본 인수를 지정하려면 None과

docstring을 사용하자  079 Better way 21 키워드

전용 인수로 명료성을 강요하자  083

파이썬 2의 키워드 전용 인수  086

3장 ▶ 클래스와 상속

089

Better way 22 딕셔너리와

튜플보다는 헬퍼 클래스로 관리하자  090

클래스 리팩토링  093 Better way 23 인터페이스가 Better way 24 객체를

간단하면 클래스 대신 함수를 받자  097

범용으로 생성하려면 @classmethod

다형성을 이용하자  102 Better way 25 super로 Better way 26 믹스인 Better way 27 공개

부모 클래스를 초기화하자  108

유틸리티 클래스에만 다중 상속을 사용하자  114

속성보다는 비공개 속성을 사용하자  119

Better way 28 커스텀

컨테이너 타입은 collections.abc의

클래스를 상속받게 만들자  126

4장 ▶ 메타클래스와 속성

133

Better way 29 게터와

세터 메서드 대신에 일반 속성을 사용하자  134

Better way 30 속성을

리팩토링하는 대신 @property를 고려하자  139

Better way 31 재사용

가능한 @property 메서드에는

디스크립터를 사용하자  144 Better way 32 지연

속성에는 _ _getattr_ _, _ _getattribute_ _,

_ _setattr_ _을 사용하자  151

파이썬(본문)4차.indd 11

Better way 33 메타클래스로

서브클래스를 검증하자  158

Better way 34 메타클래스로

클래스의 존재를 등록하자  161

Better way 35 메타클래스로

클래스 속성에 주석을 달자  167

2016-03-16 오후 4:18:48


5장 ▶ 병행성과 병렬성  Better way 36 자식

171

프로세스를 관리하려면 subprocess를 사용하자  172

Better way 37 스레드를

블로킹 I/O용으로 사용하고

병렬화용으로는 사용하지 말자  178 Better way 38 스레드에서 Better way 39 스레드

데이터 경쟁을 막으려면 Lock을 사용하자  183

간의 작업을 조율하려면 Queue를 사용하자  188

Queue로 문제 해결하기  192 Better way 40 많은

함수를 동시에 실행하려면 코루틴을 고려하자  197

생명 게임  200 파이썬 2의 코루틴  207 Better way 41 진정한

병렬성을 실현하려면 concurrent.futures를

고려하자  209

6장 ▶ 내장 모듈

215

Better way 42 functools.wraps로 Better way 43 재사용

함수 데코레이터를 정의하자  216

가능한 try/finally 동작을 만들려면

contextlib와 with 문을 고려하자  219 with 타깃 사용하기  221 Better way 44 copyreg로

pickle을 신뢰할 수 있게 만들자  223

기본 속성 값  226 클래스 버전 관리  228 안정적인 임포트 경로  229 Better way 45 지역

시간은 time이 아닌 datetime으로 표현하자  231

time 모듈  232 datetime 모듈  234 Better way 46 내장

알고리즘과 자료 구조를 사용하자  237

더블 엔디드 큐  237 정렬된 딕셔너리  238 기본 딕셔너리  239 힙 큐  240 바이섹션  241

파이썬(본문)4차.indd 12

2016-03-16 오후 4:18:48


이터레이터 도구  242 Better way 47 정밀도가

중요할 때는 decimal을 사용하자  243

Better way 48 커뮤니티에서 만든 모듈을 어디서 찾아야 하는지 알아두자  247

7장 ▶ 협력

249

Better way 49 모든

함수, 클래스, 모듈에 docstring을 작성하자  250

모듈 문서화  251 클래스 문서화  252 함수 문서화  253 Better way 50 모듈을

구성하고 안정적인 API를 제공하려면

패키지를 사용하자  255 네임스페이스  256 안정적인 API  258 Better way 51 루트 Exception을 정의해서 API로부터 호출자를 보호하자  262 Better way 52 순환

의존성을 없애는 방법을 알자  266

임포트 재정렬  268 임포트, 설정, 실행  269 동적 임포트  271 Better way 53 의존성을

분리하고 재현하려면 가상 환경을 사용하자  273

pyvenv 명령  275 의존성 재현  277

8장 ▶ 제품화

281

Better way 54 배포

환경을 구성하는 데는 모듈 스코프 코드를 고려하자  282

Better way 55 디버깅

출력용으로는 repr 문자열을 사용하자  285

Better way 56 unittest로 Better way 57 pdb를

모든 것을 테스트하자  289

이용한 대화식 디버깅을 고려하자  293

Better way 58 최적화하기

전에 프로파일하자  295

Better way 59 tracemalloc으로

메모리 사용 현황과 누수를 파악하자  301

찾아보기  305

파이썬(본문)4차.indd 13

2016-03-16 오후 4:18:48


Chapter

3 클래스와 상속

파이썬은 상속, 다형성, 캡슐화 같은 객체 지향 언어의 모든 기능을 제공한 다. 파이썬으로 작업을 처리하다 보면 새 클래스들을 작성하고 해당 클래스 들이 인터페이스와 상속 관계를 통해 상호 작용하는 방법을 정의해야 하는 상황을 자주 접하게 된다. 파이썬의 클래스와 상속을 이용하면 프로그램에서 의도한 동작을 객체들로 손쉽게 표현할 수 있다. 또한 프로그램의 기능을 점차 개선하고 확장할 수 있 다. 아울러 요구 사항이 바뀌는 환경에서도 유연하게 대처할 수 있다. 클래 스와 상속을 사용하는 방법을 잘 알아두면 유지보수가 용이한 코드를 작성할 수 있다.

3장 클래스와 상속

파이썬(본문)4차.indd 89

089

2016-03-16 오후 4:18:55


Better way

22  딕셔너리와 튜플보다는 헬퍼 클래스로 관리하자

파이썬에 내장되어 있는 딕셔너리 타입은 객체의 수명이 지속되는 동안 동적 인 내부 상태를 관리하는 용도로 아주 좋다. 여기서 ‘동적’이란 예상하지 못 한 식별자들을 관리해야 하는 상황을 뜻한다. 예를 들어 이름을 모르는 학생 집단의 성적을 기록하고 싶다고 해보자. 학생별로 미리 정의된 속성을 사용 하지 않고 딕셔너리에 이름을 저장하는 클래스를 정의할 수 있다. class SimpleGradebook(object): def _ _init_ _(self): self._grades = {} def add_student(self, name): self._grades[name] = [] def report_grade(self, name, score): self._grades[name].append(score) def average_grade(self, name): grades = self._grades[name] return sum(grades) / len(grades)

클래스를 사용하는 방법은 간단하다. book = SimpleGradebook() book.add_student(‘Isaac Newton’) book.report_grade(‘Isaac Newton’, 90) # ... print(book.average_grade(‘Isaac Newton’)) >>> 90.0

딕셔너리는 정말 사용하기 쉬워서 과도하게 쓰다가 코드를 취약하게 작성할 위험이 있다. 예를 들어 SimpleGradebook 클래스를 확장해서 모든 성적을 한 곳에 저장하지 않고 과목별로 저장한다고 하자. 이런 경우 _grades 딕셔너

090

파이썬(본문)4차.indd 90

2016-03-16 오후 4:18:55


리를 변경해서 학생 이름(키)을 또 다른 딕셔너리(값)에 매핑하면 된다. 가장 안쪽에 있는 딕셔너리는 과목(키)을 성적(값)에 매핑한다. class BySubjectGradebook(object): def _ _init_ _(self): self._grades = {} def add_student(self, name): self._grades[name] = {}

위의 코드는 충분히 직관적이다. report_grade와 average_grade 메서드는 여 러 단계의 딕셔너리를 처리하느라 약간 복잡해지지만 아직은 다룰 만하다. def report_grade(self, name, subject, grade): by_subject = self._grades[name] grade_list = by_subject.setdefault(subject, []) grade_list.append(grade) def average_grade(self, name): by_subject = self._grades[name] total, count = 0, 0 for grades in by_subject.values(): total += sum(grades) count += len(grades) return total / count

클래스를 사용하는 방법은 여전히 간단하다. book = BySubjectGradebook() book.add_student(‘Albert Einstein‘) book.report_grade(‘Albert Einstein‘, ‘Math’, 75) book.report_grade(‘Albert Einstein‘, ‘Math’, 65) book.report_grade(‘Albert Einstein‘, ‘Gym’, 90) book.report_grade(‘Albert Einstein‘, ‘Gym’, 95)

이제 요구 사항이 다시 바뀐다고 해보자. 수업의 최종 성적에서 각 점수가 차 지하는 비중을 매겨서 중간고사와 기말고사를 쪽지시험보다 중요하게 만들

3장 클래스와 상속

파이썬(본문)4차.indd 91

091

2016-03-16 오후 4:18:55


려고 한다. 이 기능을 구현하는 방법 중 하나는 가장 안쪽 딕셔너리를 변경해 서 과목(키)을 성적(값)에 매핑하지 않고, 성적과 비중을 담은 튜플 (score, weight)에 매핑하는 것이다. class WeightedGradebook(object): # ... def report_grade(self, name, subject, score, weight): by_subject = self._grades[name] grade_list = by_subject.setdefault(subject, []) grade_list.append((score, weight))

값을 튜플로 만든 것뿐이라서 report_grade를 수정한 내역은 간단해 보이지 만, average_grade 메서드는 루프 안에 루프가 생겨서 이해하기 어려워졌다. def average_grade(self, name): by_subject = self._grades[name] score_sum, score_count = 0, 0 for subject, scores in by_subject.items(): subject_avg, total_weight = 0, 0 for score, weight in scores: # ... return score_sum / score_count

클래스를 사용하는 방법도 더 어려워졌다. 위치 인수에 있는 숫자들이 무엇 을 의미하지도 명확하지 않다. book.report_grade(‘Albert Einstein‘, ‘Math’, 80, 0.10)

이렇게 복잡해지면 딕셔너리와 튜플 대신 클래스의 계층 구조를 사용할 때가 된 것이다. 처음엔 성적에 비중을 적용하게 될지 몰랐으니 복잡하게 헬퍼 클래스를 추 가할 필요까지는 없을 것 같았다. 파이썬의 내장 딕셔너리와 튜플 타입을 쓰 면 내부 관리용으로 층층이 타입을 추가하는 게 쉽다. 하지만 계층이 한 단계

092

파이썬(본문)4차.indd 92

2016-03-16 오후 4:18:56


가 넘는 중첩은 피해야 한다(즉, 딕셔너리를 담은 딕셔너리는 쓰지 말아야 한 다). 여러 계층으로 중첩하면 다른 프로그래머들이 코드를 이해하기 어려워 지고 유지보수의 악몽에 빠지게 된다. 관리하기 복잡하다고 느끼는 즉시 클래스로 옮겨가야 한다. 그러면 데이터를 더 잘 캡슐화한 잘 정의된 인터페이스를 제공할 수 있다. 또한 인터페이스와 실제 구현 사이에 추상화 계층을 만들 수 있다.

클래스 리팩토링 의존 관계에서 가장 아래에 있는 성적부터 클래스로 옮겨보자. 이렇게 간단 한 정보를 담기에 클래스는 너무 무거워 보인다. 성적은 변하지 않으니 튜플 을 사용하는 게 더 적절해 보인다. 다음 코드에서는 리스트 안에 성적을 기록 하려고 (score, weight) 튜플을 사용한다. grades = [] grades.append((95, 0.45)) # ... total = sum(score * weight for score, weight in grades) total_weight = sum(weight for _, weight in grades) average_grade = total / total_weight

문제는 일반 튜플은 위치에 의존한다는 점이다. 성적에 선생님의 의견 같은 더 많은 정보를 연관지으려면 이제 튜플을 사용하는 곳을 모두 찾아서 아이 템 두 개가 아니라 세 개를 쓰도록 수정해야 한다. 다음 코드에서는 튜플에 있는 세 번째 값을 _로 받아서 그냥 무시하도록 했다(파이썬에서는 관례적으 로 사용하지 않을 변수에 밑줄 변수 이름을 쓴다). grades = [] grades.append((95, 0.45, ‘Great job’)) # ...

3장 클래스와 상속

파이썬(본문)4차.indd 93

093

2016-03-16 오후 4:18:56


total = sum(score * weight for score, weight, _ in grades) total_weight = sum(weight for _, weight, _ in grades) average_grade = total / total_weight

튜플을 점점 더 길게 확장하는 패턴은 딕셔너리의 계층을 깊게 두는 방식과 비슷하다. 튜플의 아이템이 두 개를 넘어가면 다른 방법을 고려해야 한다. collections 모듈의 namedtuple 타입이 정확히 이런 요구에 부합한다. namedtuple을 이용하면 작은 불변 데이터 클래스(immutable data class)를 쉽게

정의할 수 있다. import collections Grade = collections.namedtuple(‘Grade’, (‘score’, ‘weight’))

불변 데이터 클래스는 위치 인수나 키워드 인수로 생성할 수 있다. 필드 는 이름이 붙은 속성으로 접근할 수 있다. 이름이 붙은 속성이 있으면 나중 에 요구 사항이 또 변해서 단순 데이터 컨테이너에 동작을 추가해야 할 때 namedtuple에서 직접 작성한 클래스로 쉽게 바꿀 수 있다.

namedtuple의

제약

namedtuple이 여러 상황에서 유용하긴 하지만 장점보다 단점을 만들어낼 수 있는 상황

도 이해해야 한다. •• namedtuple로 만들 클래스에 기본 인수 값을 설정할 수 없다. 그래서 데이터에 선택 적인 속성이 많으면 다루기 힘들어진다. 속성을 사용할 때는 클래스를 직접 정의하는 게 나을 수 있다. •• namedtuple 인스턴스의 속성 값을 여전히 숫자로 된 인덱스와 순회 방법으로 접근할 수 있다. 특히 외부 API로 노출한 경우에는 의도와 다르게 사용되어 나중에 실제 클래 스로 바꾸기 더 어려울 수도 있다. namedtuple 인스턴스를 사용하는 방식을 모두 제어 할 수 없다면 클래스를 직접 정의하는 게 낫다.

094

파이썬(본문)4차.indd 94

2016-03-16 오후 4:18:56


다음으로 성적들을 담은 단일 과목을 표현하는 클래스를 작성해보자. class Subject(object): def _ _init_ _(self): self._grades = [] def report_grade(self, score, weight): self._grades.append(Grade(score, weight)) def average_grade(self): total, total_weight = 0, 0 for grade in self._grades: total += grade.score * grade.weight total_weight += grade.weight return total / total_weight

이제 한 학생이 공부한 과목들을 표현하는 클래스를 작성해보자. class Student(object): def _ _init_ _(self): self._subjects = {} def subject(self, name): if name not in self._subjects: self._subjects[name] = Subject() return self._subjects[name] def average_grade(self): total, count = 0, 0 for subject in self._subjects.values(): total += subject.average_grade() count += 1 return total / count

마지막으로 학생의 이름을 키로 사용해 동적으로 모든 학생을 담을 컨테이너 를 작성한다.

3장 클래스와 상속

파이썬(본문)4차.indd 95

095

2016-03-16 오후 4:18:56


class Gradebook(object): def _ _init_ _(self): self._students = {} def student(self, name): if name not in self._students: self._students[name] = Student() return self._students[name]

이 세 클래스의 코드 줄 수는 이전에 구현한 코드의 두 배에 가깝다. 하지만 이 코드가 훨씬 이해하기 쉽다. 이 클래스들을 사용하는 예제도 더 명확하고 확장하기 쉽다. book = Gradebook() albert = book.student(‘Albert Einstein‘) math = albert.subject(‘Math’) math.report_grade(80, 0.10) # ... print(albert.average_grade()) >>> 81.5

필요하면 이전 형태의 API 스타일로 작성한 코드를 새로 만든 객체 계층 스 타일로 바꿔주는 하위 호환용 메서드를 작성해도 된다.

핵심 정리 •• 다른 딕셔너리나 긴 튜플을 값으로 담은 딕셔너리를 생성하지 말자. •• 정식 클래스의 유연성이 필요 없다면 가벼운 불변 데이터 컨테이너에는 namedtuple을 사용하자. •• 내부 상태를 관리하는 딕셔너리가 복잡해지면 여러 헬퍼 클래스를 사용하는 방식으로 관리 코드를 바꾸자.

096

파이썬(본문)4차.indd 96

2016-03-16 오후 4:18:56


Chapter

7 협력

파이썬은 명확한 인터페이스 경계로 잘 정의된 API를 만드는 데 도움을 주는 언어 기능을 갖췄다. 파이썬 커뮤니티는 시간이 지나도 코드의 유지보수성을 극대화할 수 있는 가장 좋은 방법을 확립했다. 또한 서로 다른 환경으로 일하 는 대규모 팀들이 협력할 수 있게 해주는 표준 도구들도 파이썬에 같이 실려 온다. 파이썬 프로그램을 다른 사람과 협력해서 작성할 때는 코드를 어떻게 작성 해야 하는지 깊이 생각해야 한다. 자신만의 프로그램을 작성하는 경우에도 다른 사람이 만든 표준 라이브러리나 오픈 소스 패키지를 사용할 수 있다. 따라서 다른 파이썬 프로그래머들과 수월하게 협력하는 방법을 이해해야 한다.

7장 협력

파이썬(본문)4차.indd 249

249

2016-03-16 오후 4:19:05


Better way

49  모든 함수, 클래스, 모듈에 docstring을 작성하자

파이썬에서 문서화는 언어의 동적 특성 때문에 극히 중요하다. 파이썬은 코 드 블록에 문서를 첨부하는 기능을 기본으로 지원한다. 대부분의 다른 언어 와는 다르게 프로그램 실행 중에 소스 코드에 첨부한 문서에 직접 접근할 수 있다. 예를 들어 함수의 def 문 바로 다음에 docstring(문서화 문자열)을 직접 작 성하여 문서를 추가할 수 있다. def palindrome(word): “””Return True if the given word is a palindrome.””” return word = = word[::-1]

함수의 _ _doc_ _이라는 특별한 속성에 접근하면 파이썬 프로그램 자체에 포 함된 docstring을 추출할 수 있다. print(repr(palindrome._ _doc_ _)) >>> ‘Return True if the given word is a palindrome.’

docstring은 함수, 클래스, 모듈에 붙일 수 있다. 이와 같은 연결은 파이썬 프로그램을 컴파일하고 실행하는 과정의 일부다. docstring과 _ _doc_ _ 속 성을 지원하는 덕분에 다음 세 가지 효과를 얻는다. ●●

문서의 접근성 덕분에 대화식으로 개발하기가 더 쉽다. 내장 함수 help로 함수, 클래스, 모듈을 조사하여 문서를 볼 수 있다. 따라서 파이썬 대화식 인터프리터(파이썬 ‘셸’)와 IPython 노트북(http://ipython.org)과 같은 도구에서 알고리즘 개발, API 테스트, 코드 작성을 즐겁게 할 수 있다.

250

파이썬(본문)4차.indd 250

2016-03-16 오후 4:19:05


●●

문서를 정의하는 표준 방법이 있으면 텍스트를 더 쉽게 이해할 수 있는 포 맷(HTML 같은)으로 변환하는 도구를 쉽게 만들 수 있다. 그래서 파이썬 커 뮤니티의 Sphinx(http://sphinxdoc.org)와 같은 훌륭한 문서 생성 도구 가 생겨났다. 또한 오픈 소스 파이썬 프로젝트의 멋진 문서를 무료로 제공 하는 Read the Docs(https://readthedocs.org)와 같은 커뮤니티 펀딩 사이트도 생겨났다.

●●

파이썬의 일급 클래스(first-class), 접근성, 잘 정리된 문서는 사람들이 문서 를 더 많이 작성할 수 있도록 북돋아준다. 파이썬 커뮤니티의 멤버들은 문 서화가 중요하다고 강하게 믿고 있다. 여기에는 ‘좋은 코드’는 문서화가 잘 된 코드라는 가정이 있다. 이는 대부분의 오픈 소스 파이썬 라이브러리가 잘 작성된 문서를 갖추고 있음을 의미한다.

이런 훌륭한 문서화 문화에 동참하려면 docstring을 작성할 때 몇 가지 지 침을 따라야 한다. 자세한 정보는 PEP 257(http://www.python.org/ dev/peps/pep-0257/)에서 온라인으로 참고할 수 있다. 또한 꼭 따라야 할 몇 가지 모범 사례가 있다.

모듈 문서화 각 모듈에는 최상위(top-level) docstring이 있어야 한다. 최상위 docstring 은 소스 파일에서 첫 번째 문장에 있는 문자열이다. 최상위 docstring에는 큰따옴표 세 개(""")를 사용해야 한다. 이 docstring의 목적은 모듈과 모듈 의 내용에 대한 소개다. docstring의 첫 번째 줄은 모듈의 목적을 기술하는 한 문장으로 구성해야 한다. 그 이후의 문단은 모듈의 모든 사용자가 알아야 하는 모듈의 동작을 자 세히 설명한 내용을 포함한다. 또한 모듈 docstring은 모듈 내의 중요 클래 스나 함수를 강조하는 중요한 부분이다.

7장 협력

파이썬(본문)4차.indd 251

251

2016-03-16 오후 4:19:05


다음은 모듈 docstring의 예다. # words.py #!/usr/bin/env python3 “””Library for testing words for various linguistic patterns. Testing how words relate to each other can be tricky sometimes! This module provides easy ways to determine when words you’ve found have special properties. Available functions: - palindrome: Determine if a word is a palindrome. - check_anagram: Determine if two words are anagrams. ... “”” # ...

모듈이 명령줄 유틸리티라면 모듈 docstring이야 말로 명령줄에서 도구를 실행하는 방법을 보여주기에 적합한 곳이다.

클래스 문서화 각 클래스에는 클래스 수준 docstring이 있어야 한다. 클래스 수준 docstring은 모듈 수준 docstring과 거의 같은 패턴을 따른다. 첫 번째 줄 은 클래스의 목적을 기술하는 한 문장으로 구성한다. 그 이후의 문단에는 클 래스의 동작과 관련해 중요한 내용을 기술한다. 클래스의 중요한 공개 속성과 메서드는 클래스 수준 docstring에서 강조해 야 한다. 또한 서브클래스가 보호 속성, 슈퍼클래스의 메서드와 올바르게 상 호 작용하는 방법을 안내해야 한다(Better way 27 “공개 속성보다는 비공 개 속성을 사용하자” 참고).

252

파이썬(본문)4차.indd 252

2016-03-16 오후 4:19:05


다음은 클래스 docstring의 예다. class Player(object): “””Represents a player of the game. Subclasses may override the ‘tick’ method to provide custom animations for the player’s movement depending on their power level, etc. Public attributes: - power: Unused power-ups (float between 0 and 1). - coins: Coins found during the level (integer). “”” # ...

함수 문서화 각 공개 함수와 메서드에는 docstring이 있어야 한다. 이 docstring도 모 듈이나 클래스와 같은 패턴을 따른다. 첫 번째 줄은 함수가 수행하는 일을 한 문장으로 설명한다. 그 다음의 문단에서는 함수의 특별한 동작이나 인수에 대해 설명한다. 반환 값도 언급해야 한다. 호출하는 쪽에서 함수 인터페이스 의 일부로 처리해야 하는 예외도 설명해야 한다. 다음은 함수 docstring의 예다. def find_anagrams(word, dictionary): “””Find all anagrams for a word. This function only runs as fast as the test for membership in the ‘dictionary’ container. It will be slow if the dictionary is a list and fast if it’s a set.

7장 협력

파이썬(본문)4차.indd 253

253

2016-03-16 오후 4:19:05


Args: word: String of the target word. dictionary: Container with all strings that are known to be actual words. Returns: List of anagrams that were found. Empty if none were found. “”” # ...

함수 docstring을 작성할 때 알아둬야 할 몇 가지 특별한 경우가 있다. ●●

함수가 인수는 받지 않고 간단한 값만 반환할 때는 한 줄짜리 설명으로 충 분하다.

●●

함수가 아무것도 반환하지 않는다면 return None이라고 하기보다는 반환 값을 언급하지 않는 게 낫다.

●●

함수가 일반적인 동작에서는 예외를 일으키지 않는다고 생각한다면 이에 대해서는 언급하지 말자.

●●

함수가 받는 인수의 개수가 가변적(Better way 18 “가변 위치 인수로 깔 끔하게 보이게 하자” 참고)이거나 키워드 인수(Better way 19 “키워드 인 수로 선택적인 동작을 제공하자” 참고)를 사용한다면 문서의 인수 목록에 *args와 **kwargs를 사용해서 그 목적을 설명하자.

●●

함수가 기본값이 있는 인수를 받는다면 해당 기본값을 설명해야 한다 (Better way 20 “동적 기본 인수를 지정하려면 None과 docstring을 사용 하자” 참고).

●●

함수가 제너레이터(Better way 16 “리스트를 반환하는 대신 제너레이터를 고려하자” 참고)라면 제너레이터가 순회될 때 무엇을 넘겨주는지 docstring 으로 설명해야 한다.

●●

함수가 코루틴(Better way 40 “많은 함수를 동시에 실행하려면 코루틴을 고려하자” 참고)이라면 코루틴이 무엇을 넘겨주는지, yield 표현식으로부터 무엇을 얻는지, 언제 순회가 끝나는지를 docstring으로 설명해야 한다.

254

파이썬(본문)4차.indd 254

2016-03-16 오후 4:19:05


Note

모듈의 docstring을 작성한 후에는 문서를 계속 업데이트하는 게 중요하다. 내장 모

듈 doctest는 docstring에 포함된 사용 예제를 실행하기 쉽게 해줘서 여러분이 작성한 소스 코 드와 문서가 시간이 지나면서 여러 버전으로 나뉘지 않게 해준다.

핵심 정리 •• 모든 모듈, 클래스, 함수를 docstring으로 문서화하자. 코드를 업데이트할 때마다 관련 문서도 업데이트하자. •• 모듈 : 모듈의 내용과 모든 사용자가 알아둬야 할 중요한 클래스와 함수를 설명한다. •• 클래스 : class 문 다음의 docstring에서 클래스의 동작, 중요한 속성, 서브클래스의 동 작을 설명한다. •• 함수와 메서드 : def 문 다음의 docstring에서 모든 인수, 반환 값, 일어나는 예외, 다른 동작들을 문서화한다.

Better way

50  모듈을 구성하고 안정적인 API를 제공하려면

패키지를 사용하자 프로그램의 코드가 커지면 자연히 코드의 구조를 재구성하기 마련이다. 예를 들면 큰 함수를 더 작은 함수로 분할한다. 자료 구조를 헬퍼 클래스로 리팩 토링한다(Better way 22 “딕셔너리와 튜플보다는 헬퍼 클래스로 관리하자” 참고). 또한 기능을 서로 의존적인 여러 모듈로 분할한다. 언젠가는 너무 많은 모듈이 있어서 모듈들을 이해하기 쉽게 하려고 프로그램 에 다른 계층을 추가해야 하는 시점이 온다. 파이썬은 이런 목적으로 패키지 (package)를

제공한다. 패키지는 다른 모듈을 포함하는 모듈을 말한다.

대부분은 디렉터리 안에 _ _init_ _.py라는 빈 파일을 넣는 방법으로 패키지 를 정의한다. _ _init_ _.py가 있으면 해당 디렉터리에 있는 다른 파이썬 파일

7장 협력

파이썬(본문)4차.indd 255

255

2016-03-16 오후 4:19:05


은 디렉터리에 상대적인 경로로 임포트할 수 있다. 예를 들어 프로그램의 디 렉터리 구조가 다음과 같다고 하자. main.py mypackage/_ _init_ _.py mypackage/models.py mypackage/utils.py

utils 모듈을 임포트하려면 패키지 디렉터리 이름을 포함하는 절대 모듈 이

름을 사용해야 한다. # main.py from mypackage import utils

패키지 디렉터리가 다른 패키지(예를 들면 mypackage.foo.bar) 안에 있을 때 는 계속 이런 패턴을 따른다.

Note

파이썬 3.4에서 도입한 네임스페이스 패키지(namespace package)는 패키지를

더 유연하게 정의하는 방법이다. 네임스페이스 패키지는 완전히 구분된 디렉터리, zip 아카이브, 심지어 원격 시스템으로부터 모듈을 구성할 수도 있다. 네임스페이스 패키지의 진보된 기능을 사 용하는 방법을 알고 싶다면 PEP 420(http://www.python.org/dev/peps/pep-0420/)을 확인하기 바란다.

패키지가 제공하는 기능은 파이썬 프로그램에서 두 가지 주요 목적이 있다.

네임스페이스 패키지의 첫 번째 용도는 분리된 네임스페이스(namespace)로 모듈들을 분할 하는 것이다. 이 기능은 파일 이름이 같은 여러 모듈이 서로 다른 절대 경로 를 갖게 해준다. 예를 들어 다음은 이름이 utils.py로 같은 두 모듈에서 속성 을 임포트하는 프로그램이다. 각 모듈을 각각의 절대 경로로 접근하므로 제 대로 동작한다.

256

파이썬(본문)4차.indd 256

2016-03-16 오후 4:19:05


# main.py from analysis.utils import log_base2_bucket from frontend.utils import stringify bucket = stringify(log_base2_bucket(33))

이 방법은 패키지에서 정의한 함수, 클래스, 서브모듈의 이름들이 같을 때 는 동작하지 않는다. 예를 들어 analysis.utils와 frontend.utils 모듈에 있 는 inspect 함수를 사용하고 싶다고 하자. 속성을 직접 임포트하면 두 번째 import 문이 현재 영역에 있는 inspect의 값을 덮어쓰기 때문에 제대로 동작

하지 않는다. # main2.py from analysis.utils import inspect from frontend.utils import inspect

# 덮어씀!

해결책은 import 문에 as 절을 사용하여 현재 영역에 임포트하는 대상의 이 름을 변경하는 것이다. # main3.py from analysis.utils import inspect as analysis_inspect from frontend.utils import inspect as frontend_inspect value = 33 if analysis_inspect(value) = = frontend_inspect(value): print(‘Inspection equal!’)

as 절은 import 문으로 임포트한 대상의 이름을 변경하는 데 사용한다(모듈

도 가능하다). as 절을 이용하면 네임스페이스가 붙은 코드에 접근하기 쉽고 대상을 사용할 때 실체를 명확하게 인식할 수 있다.

7장 협력

파이썬(본문)4차.indd 257

257

2016-03-16 오후 4:19:05


Note

임포트한 이름이 충돌하는 문제를 피하는 다른 방법은 항상 가장 상위의 고유한 모듈

이름으로 이름에 접근하는 것이다. 앞의 예제를 예로 들면 먼저 import analysis.utils, import frontend.utils로 임포트 한다. 다음으로 analysis.utils.inspect와 frontend.utils.inspect처럼 전체 경로로 inspect 함수에 접근한다. 이 방법을 사용하면 as 절을 아예 사용하지 않아도 된다. 또한 코드를 처음 보는 사람이 각 함수 가 어디에 정의되어 있는지 더 명확하게 이해할 수 있다.

안정적인 API 파이썬에서 패키지의 두 번째 용도는 외부 사용자에게 명확하고 안정적인 API를 제공하는 것이다. 오픈 소스 패키지처럼 다양하게 사용할 목적으로 API를 작성할 때(Better way 48 “커뮤니티에서 만든 모듈을 어디서 찾아야 하는지 알아두자” 참고) 릴리스 간의 변경 없이 안정적인 기능을 제공하고 싶다고 하자. 그러려면 외 부 사용자에게서 내부 코드 구조를 숨겨야 한다. 이렇게 해두면 기존 사용자 의 코드를 망가뜨리지 않고 패키지의 내부 모듈을 리팩토링하고 개선할 수 있다. 파이썬에서는 모듈이나 패키지에 _ _all_ _이라는 특별한 속성으로 API 사용 자에게 드러나는 외부 영역을 제한한다. _ _all_ _의 값은 공개 API의 일부로 외부에 제공하려는 모듈 이름을 모두 담은 리스트다. 패키지를 사용하는 코드 에서 from foo import *를 실행하면 foo._ _all_ _에 있는 속성만 임포트된다. foo에 _ _all_ _이 없으면 속성 이름이 밑줄로 시작하지 않는 공개 속성만 임포

트된다(Better way 27 “공개 속성보다는 비공개 속성을 사용하자” 참고). 예를 들어 움직이는 발사체 간의 충돌을 계산하는 패키지를 제공한다고 하 자. 다음은 mypackage의 models 모듈을 정의하여 발사체를 표현하는 코드다.

258

파이썬(본문)4차.indd 258

2016-03-16 오후 4:19:05


# models.py _ _all_ _ = [‘Projectile’] class Projectile(object): def _ _init_ _(self, mass, velocity): self.mass = mass self.velocity = velocity

또한 mypackage에 utils 모듈을 정의하여 Projectile 인스턴스로 발사체 간 의 충돌을 시뮬레이트하는 동작을 수행한다. # utils.py from . models import Projectile _ _all_ _ = [‘simulate_collision’] def _dot_product(a, b): # ... def simulate_collision(a, b): # ...

이제 이 API의 공개 부분을 모두 mypackage 모듈에서 사용 가능한 속성의 집 합으로 제공하려고 한다. 이렇게 하면 mypackage.models 혹은 mypackage. utils에서 임포트하는 대신 mypackage로부터 항상 직접 임포트할 수 있다.

이 방식은 mypackage의 내부 구조를 변경(예를 들면 models.py가 삭제되는 경우)하더라도 API를 사용하는 코드가 계속 동작함을 보장한다. 파이썬 패키지로 이렇게 동작하게 하려면 mypackage 디렉터리에 있는 _ _init_ _.py 파일을 수정해야 한다. 이 파일은 실제로 임포트될 때 mypackage 모듈의 내용이 된다. 따라서 _ _init_ _.py로 임포트하는 것을 제한

하여 mypackage의 API를 명시적으로 설정할 수 있다. 이미 내부 모듈은 모두 _ _all_ _을 명시하고 있으므로, 단순히 내부 모듈에서 모든 것을 임포트하고

7장 협력

파이썬(본문)4차.indd 259

259

2016-03-16 오후 4:19:05


_ _all_ _을 이에 맞춰 업데이트하면 mypackage의 공개 인터페이스를 외부에

드러낼 수 있다. # _ _init_ _.py _ _all_ _ = [] from . models import * _ _all_ _ += models._ _all_ _ from . utils import * _ _all_ _ += utils._ _all_ _

다음은 API의 사용자가 내부 모듈에 접근하지 않고 mypackage로부터 직접 임포트하는 코드다. # api_consumer.py from mypackage import * a = Projectile(1.5, 3) b = Projectile(4, 1.7) after_a, after_b = simulate_collision(a, b)

mypackage.utils._ _dot_product 같은 내부 전용 함수는 _ _all_ _에 포함되지

않으므로 mypackage API 사용자는 사용할 수 없다. _ _all_ _에서 빠진다는 것 은 from mypackage import * 문으로 임포트되지 않음을 의미한다. 따라서 내 부 전용 이름을 효과적으로 숨길 수 있다. 이런 모든 방법은 명시적이고 안정적인 API를 제공해야 할 때 잘 동작한다. 하지만 직접 만든 모듈 사이에서 사용하려고 API를 구축한다면 _ _all_ _의 기능이 필요 없을 것이므로 사용하지 말아야 한다. 패키지가 제공하는 네임 스페이스는 의미 있는 인터페이스를 유지하며 많은 양의 코드로 협업하는 개 발팀에도 일반적으로 충분하다.

260

파이썬(본문)4차.indd 260

2016-03-16 오후 4:19:05


import

*를 주의하자

from x import y 같은 임포트 문은 y의 출처가 명시적으로 x 패키지나 모듈이므로 명확

하다. from foo import * 같은 와일드카드 임포트도 특히 대화식 파이썬 세션에서 유용 하다. 하지만 와일드카드는 코드를 더 이해하기 어렵게 만든다. •• from foo import *는 코드를 새로 접하는 사용자들에게서 이름의 출처를 숨긴다. 모 듈에 여러 import * 문이 있을 때 이름이 정의되어 있는 장소를 알아내려면 모든 참조 모듈을 확인해야 한다. •• import * 문으로 가져온 이름은 이 문자를 포함하는 모듈 내에서 충돌하는 이름을 덮 어쓴다. 결국 여러분이 작성한 코드와 여러 import * 문으로 가져온 이름이 겹치면서 갑작스런 상호 작용이 일어나 이상한 버그가 생길 수 있다. 가장 안전한 방법은 코드에 서 import *를 피하고 명시적으로 from x import y 스타일로 이름을 임포트하는 것이다.

핵심 정리 •• 파이썬의 패키지는 다른 모듈을 포함하는 모듈이다. 패키지를 이용하면 고유한 절대 모 듈 이름으로 코드를 분리하고, 충돌하지 않는 네임스페이스를 구성할 수 있다. •• 간단한 패키지는 다른 소스 파일을 포함하는 디렉터리에 _ _init_ _.py 파일을 추가하 는 방법으로 정의한다. _ _init_ _.py를 제외한 파일들은 디렉터리 패키지의 자식 모듈 이 된다. 패키지 디렉터리는 다른 패키지를 포함할 수도 있다. •• _ _all_ _이라는 특별한 속성에 공개하려는 이름을 나열하여 모듈의 명시적인 API를 제 공할 수 있다. •• 공개할 이름만 패키지의 _ _init_ _.py 파일에서 임포트하거나 내부 전용 멤버의 이름 을 밑줄로 시작하게 만들면 패키지의 내부 구현을 숨길 수 있다. •• 단일 팀이나 단일 코드베이스로 협업할 때는 외부 API용으로 _ _all_ _을 사용할 필요 가 없을 것이다.

7장 협력

파이썬(본문)4차.indd 261

261

2016-03-16 오후 4:19:05



Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.