전문가를 위한 파이썬_맛보기

Page 1

파이썬의 잠재력을 끌어올리는 프로그래밍 기법 F

l

u

e

n

t

P

y

t

h

o

간단하고, 명료하고, 효율적인 파이썬 프로그래밍

n

이썬의 핵심 요소 및 주요 기본 라이브러리도 기본에 충실하게 소개한다. 이 책에서 더욱 간결하고, 읽기 좋 고, 빠른 코드를 작성하는 방법을 터득할 수 있을 것이다.

1. 파이썬 데이터 모델 : 일관성 있는 객체 행위의 핵심인 특별 메서드 이해하기 2. 자료구조 : 시퀀스·매핑·집합 등 내장형 자료구조 사용법과, 텍스트와 바이트의 차이점 이해하기 3. 객체로서의 함수 : 함수를 일급 객체로 다루는 파이썬의 철학이 주요 디자인 패턴에 미치는 영향 이해하기 4. 객체지향 관용 코드 : 참조, 가변성, 인터페이스, 연산자 오버로딩, 다중 상속을 익히고 클래스 구현하기 5. 제어 흐름 : 분기, 루프, 서브루틴 등의 순차적 제어 흐름을 뛰어넘는 파이썬의 기능과 라이브러리 이해하기 6. 메타프로그래밍 : 동적 속성을 가지는 클래스 생성 기법과 프로퍼티 메커니즘 이해하기

이 책은 자신의 지식 한계를 넘어서려는 파이썬 중급 및 고급 개발자에게 유용한 프로그래밍 기법이

Fluent Python

실용 안내서다. 특히 다른 언어에서는 찾아볼 수 없는 파이썬 고유의 기능을 중점적으로 살펴본다. 또한 파

전문가를 위한 파이썬

이 책은 초보자들이 놓치기 쉬운 파이썬 기능들을 활용하여, 효율적인 파이썬 코드 작성 방법을 제시하는

가득한 보물 창고다. 대니얼 로이 그린펠드, 오드리 로이 그린펠드, 『Two Scoops of Django』 저자

강권학 옮김

뇌를 자극하는 파이썬 3

처음 시작하는 파이썬

고성능 파이썬

루시아누 하말류 지음

관련 도서

Fluent

파이썬 3 버전 기준

Python 전문가를 위한 파이썬

프로그래밍 언어 / 파이썬

예제 소스 http://www.hanbit.co.kr/exam/2498

정가 55,000원

루시아누 하말류 지음 강권학 옮김


| 표지 설명 |

표지에 있는 동물은 나마쿠아사막도마뱀 (학명: Pedioplanis namaquensis )으로, 분홍색을 띤 갈색 긴 꼬리가 있는 날씬한 동 물이다. 검은 몸통에 네 개의 흰색 줄이 있고, 갈색 다리에 흰색 점이 있으며, 배도 흰색이다. 낮에 활동하며 가장 빠른 도마뱀 중 하나다. 모래자갈이 있는 평지에서 군집을 이 루지 않고 거주하며, 겨울에는 잡목 뿌리 밑으로 굴을 파고 들어가서 동면한다. 나미비아 전역의 건조한 초원지대와 준 사막 지역에서 볼 수 있다. 작은 곤충을 먹고 살며, 11월에 암 컷이 3개에서 5개의 알을 낳는다. 오라일리 책 표지에 나오는 대부분의 동물은 멸종 위기에 처한 동물이다. 이 동물들은 모두 이 세

상에 중요한 존재다. 이들을 도울 방법을 알고 싶다면 animals.oreilly.com을 참조하라. 표지 그림은 존 우드John G. Wood 의 『Natural History #3』에서 가져왔다.

전문가를 위한 파이썬 간단하고, 명료하고, 효율적인 파이썬 프로그래밍 초판발행 2016년 8월 12일 지은이 루시아누 하말류 / 옮긴이 강권학 / 펴낸이 김태헌 베타리더 김민선, 김연태, 문현일, 송종근, 양진우, 이정재, 최해성 펴낸곳 한빛미디어 (주) / 주소 서울시 마포구 양화로 7길 83 한빛미디어(주) IT출판부 전화 02 – 325 – 5544 / 팩스 02 – 336 – 7124 등록 1999년 6월 24일 제10 – 1779호 / ISBN 978 – 89 – 6848 – 498 – 8

93000

총괄 전태호 / 책임편집 김창수 / 기획·편집 박지영 / 교정·조판 김철수 디자인 표지 스튜디오 에이비, 내지 여동일 영업 김형진, 김진불, 조유미 / 마케팅 박상용, 송경석, 변지영 / 제작 박성우, 김정우 이 책에 대한 의견이나 오탈자 및 잘못된 내용에 대한 수정 정보는 한빛미디어(주)의 홈페이지나 아래 이메일로 알려주십시오. 잘못된 책은 구입하신 서점에서 교환해드립니다. 책값은 뒤표지에 표시되어 있습니다. 한빛미디어 홈페이지 www.hanbit.co.kr / 이메일 ask@hanbit.co.kr © 2016 Hanbit Media Inc.

Authorized Korean translation of the English edition of Fluent Python, ISBN 9781491946008 © 2015 Luciano Ramalho This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same. 이 책의 저작권은 오라일리와 한빛미디어(주)에 있습니다. 저작권법에 의해 한국내에서 보호를 받는 저작물이므로 무단전재와 복제를 금합니다.

지금 하지 않으면 할 수 없는 일이 있습니다. 책으로 펴내고 싶은 아이디어나 원고를 메일 ( writer@hanbit.co.kr ) 로 보내주세요. 한빛미디어(주)는 여러분의 소중한 경험과 지식을 기다리고 있습니다.


Fluent

Python 전문가를 위한 파이썬


지은이·옮긴이 소개

지은이

루시아누 하말류 Luciano Ramalho

1998년부터 파이썬으로 프로그래밍한 개발자. 파이썬 소프트웨어 재단의 특별 회원이자 브라 질 파이썬 교육 기업인 python.pro.br의 공동 소유자고, 브라질 최초의 해커 공간 ‘가로아 해 커 클럽’의 공동 설립자다. 브라질의 여러 공기관 및 도서관을 위해 파이썬으로 소프트웨어를 개발하고, 각종 매체 기업과 은행, 정부 관련 단체에서 파이썬 개발 과정을 가르쳐왔다.

옮긴이

강권학

중앙대학교 컴퓨터공학과에서 학사와 석사학위를 받았다. 국방과학연구소, 퓨쳐시스템, 안철 수연구소에서 13년간 개발자, 보안전문가, 프로젝트 관리자로 근무하였으며, 2009년 4월 호 주 멜버른에 iGonagi Pty. Ltd.를 설립하고 아이폰 애플리케이션을 개발하고 있다. 번역서로 는 『게임 디자인 레벨업 가이드(공역)』, 『Head First Python』, 『안드로이드 시큐리티 인터널』, 『Head First Java-Script Programming』, 『비즈니스를 위한 데이터 과학』, 『C++ AMP』, 『Head First C』, 『iPhone 3D Programming』, 『iPhone Programming (공역)』, 『Head

First iPhone Development』(이상 한빛미디어) 등이 있다.

4


추천의 글

사람들은 보통 파이썬을 누구나 배우기 쉬운 가벼운 스크립트 언어라고 생각합니다. 물론 이는 파이썬의 장점인 것은 확실합니다. 하지만 다른 언어에는 없는 파이썬만의 고급 기능을 접한 사 람이라면 파이썬이 결코 가볍지 않으며 강력한 언어라는 사실을 알게 될 것입니다. 이 책은 다른 책에서 잘 다루지 않는 파이썬만의 고급 기능들을 자세히 설명합니다. 초급 개발 자가 보기에는 어렵겠지만 중·고급 개발자가 이 책으로 공부한다면 날개를 단 듯 실력이 향상 될 것으로 생각합니다. 한글판도 영문판의 평판을 이어갈 수 있을 것 같아요! 김민선_아직은 배우고 싶은 게 많은 개발자

파이썬은 다른 언어에 비해 진입 장벽이 낮고 상대적으로 쉽게 개발할 수 있다. 그러나 그것이 언어의 전부를 말하지는 않는다. 언어가 가진 훌륭한 특징들을 잘 사용하려면 좋은 가이드라인 이 필요하다. 다행히 이 책은 파이썬의 특징을 활용할 수 있는 주옥같은 내용으로 가득하다. 타 도서와 비교하면 번역의 수준도 높은 편이다. 이제 파이썬을 조금 더 ‘고급지게’ 사용해보자. 김연태_8퍼센트 개발자

제대로 된 파이썬 중고급 책이 마침내 나왔다. 책의 두께에 걸맞게 파이썬 개발자라면 알아야 할 흥미로운 내용이 가득하다. 더 효율적이면서도 아름다운 코드를 작성하고 싶은 당신, 파이 썬 기본 문서가 지겨운 당신에게 추천하고 싶다. 문현일_프리랜서 개발자

5


지금까지 업무에 파이썬을 활용하면서 ‘나는 파이썬을 할 수 있다.’라고 생각했습니다. 그리고 이 책을 보면서 ‘이제 파이썬을 잘할 수 있겠구나!’라는 생각을 하게 되었습니다. 어떤 언어를 공부하더라도 그 언어를 잘 이해하고 있기는 어렵습니다. 흔히 얘기하는 초고수가 되기는 더더 욱 어렵죠. 이 책은 파이썬을 잘 이해하고 초고수가 될 수 있도록 깊은 내용들을 다루고 있습니 다. 파이썬을 사용하고 있는데 다음 방향을 고민하고 계신다면 이 책이 분명 그 방향을 제시해 줄 것입니다. 송종근_위시컴퍼니 IT총괄

파이썬으로 먹고 살기를 원하는 사람이라면 꼭 한번 도전해볼 만한 책. 그 이유는? 가위 바위 보 프로그램의 다른 방법을 알고 싶다면? 양진우_프리랜서 개발자

파이썬은 배우기 쉽고, 막강한 기능을 갖춘 언어입니다. 하지만 그로 인해 자신이 처음에 배웠 던 내용이나 이미 알고 있는 지식만을 사용하는 데 그치기도 합니다. 파이썬 외에 다른 언어로 프로그래밍한 경험이 있다면 파이썬을 더 쉽게 이해할 수 있지만, 파이썬 고유의 특징을 활용 하기보다 개개인의 경험에 따른 기존 방식을 적용하는 일이 더 많습니다. 이 책은 미처 몰랐던 파이썬만의 특징을 알아보고, 그 특징을 제대로 다루는 방법을 익히고자 할 때 도움이 될 것입 니다. 이정재_미디어캔버스 개발자

6


이 책은 파이썬 개발 경험이 있는 중급 개발자에게 적합합니다. 베타리딩을 하면서 매 페이지 를 읽을 때마다 새로운 내용을 배울 수 있었고, ‘파이썬스럽게pythonic ’ 개발한다는 것이 어떤 것 인지 깨닫게 해주는 좋은 책이었습니다. 파이썬 언어에 대한 저자의 깊은 이해를 느낄 수 있었 고, 그런 깊이 있는 내용을 어렵지 않게 설명한 저자의 실력에 감탄하며 읽었습니다. 각 장의 마지막에 실린 파이썬에 관한 일화들도 즐겁게 읽었고요. 파이썬이라는 언어를 ‘Fluent’하게 사용하는 저자가 글도 ‘Fluent’하게 썼다는 느낌입니다. 원서 제목을 참 잘 지은 것 같습니다. 최해성_티켓몬스터 개발자

7


이 책에 대하여

계획은 다음과 같다. 여러분이 이해하지 못하는 기능을 사용하는 개발자들을 총으로 쏴버리는 것이다. 이는 새로운 무언가를 배우는 것보다 쉽다. 그리고 머지않아 살아남은 프로그래머들은 쉽게 이해할 수 있는 파이썬 0.9.6의 일부 기능만 사용할 것이다.1 _ 팀 피터스Tim Peters 전설적인 개발자이자 ‘파이썬의 선Zen of Python ’의 저자

파이썬 공식 튜토리얼은 ‘파이썬은 배우기 쉽고 강력한 프로그래밍 언어’라는 설명으로 시작한 다(https://docs.python.org/3/tutorial/). 이 말은 사실이지만 명심할 부분이 있다. 파이썬 언어가 배우고 사용하기 쉽기 때문에 파이썬의 막강한 기능 중 일부만 사용하는 프로그래머가 많기 때문이다. 프로그래밍 경험자의 경우 몇 시간만 배우면 쓸 만한 파이썬 코드를 작성할 수 있다. 처음에 배 운 그 기능을 몇 주 혹은 몇 달씩 사용하면서 많은 개발자는 이전에 배운 다른 언어의 장점을 적 용해서 파이썬 코드를 작성한다. 파이썬으로 프로그래밍을 처음 시작하는 경우에도, 학교나 입 문서에서는 파이썬 고유의 특징을 사용하지 않으면서 프로그래밍을 설명한다. 다른 언어에 경험이 있는 프로그래머들에게 파이썬을 소개하는 강사 입장에서 보기에 또 다른 문제가 있다. 사람들은 자신이 알고 있는 것에만 집착한다는 것이다. 이 책에서는 이러한 문제 도 살펴볼 것이다. 다른 언어를 사용해본 개발자는 누구나 파이썬도 정규 표현식을 지원할 것 이라고 생각하고 문서를 살펴본다. 또한 이전에 튜플 언패킹이나 디스크립터에 대해 들어보지 못했다면 이런 것들은 찾아볼 생각도 할 수 없고, 파이썬에만 있는 이런 기능을 결코 사용하지 도 않을 것이다. 1 2002년 12월 23일 comp.lang.python 유즈넷 그룹에 올린 ‘C 언어 프로그래밍에 대한 통렬한 비판( Acrimony in c . l . p .)’( http:// bit.ly/1e8iABS )에서 발췌

8


이 책은 파이썬에 대한 단순 매뉴얼이 아니다. 이 책에서는 파이썬에만 있거나 다른 언어에서는 볼 수 없는 파이썬 언어 고유의 기능을 중점적으로 살펴본다. 그리고 파이썬 언어의 핵심 요소 와 일부 라이브러리를 주로 살펴본다. 파이썬 패키지 인덱스에는 6만 개 이상의 유용한 라이브 러리가 있지만 이 책에서는 기본 라이브러리 외에는 거의 다루지 않는다.

어떤 사람을 위한 책인가? 이 책은 파이썬 3를 능숙하게 다루고자 하는 파이썬 실무 개발자들을 위해 썼다. 파이썬 2를 알고 있으며 파이썬 3.4 이후 버전으로 옮겨가려는 경우에도 이 책이 도움이 될 것이다. 이 책 을 쓰고 있는 현재 대부분의 파이썬 프로그래머들은 파이썬 2를 사용하고 있으므로 파이썬 3 에 새로 추가된 기능을 강조하기 위해 특별히 주의를 기울였다. 이 책은 파이썬 3.4를 기준으로 작성하였으며 이전 버전의 파이썬에서 실행하기 위해 수정해야 하는 부분은 언급하지 않는다. 대부분의 예제는 약간 수정하거나 그대로 파이썬 2.7에서도 실행 되지만, 하위 버전으로 포팅하기 위해 상당히 많이 수정해야 하는 경우도 있다. 여러분이 파이썬 2.7을 벗어날 수 없는 경우에도 이 책은 도움이 된다. 핵심 개념은 여전히 유효 하기 때문이다. 파이썬 3는 새로운 언어가 아니며 반나절이면 이전 버전과의 차이점을 배울 수 있 다. ‘파이썬 3.0의 새로운 기능’ 문서(https://docs.python.org/3.0/whatsnew/3.0.html ) 를 참조하기 바란다. 물론 2009년에 나온 파이썬 3.0 버전 이후 변화가 있기는 하지만, 2.x에 서 3.0으로의 변화만큼 크지는 않다. 파이썬에 대한 이해가 부족한 독자라면 ‘파이썬 공식 튜토리얼’(https://docs.python.org/3/

tutorial/)을 참조하라. 튜토리얼에서 설명하는 내용 중 파이썬 3의 일부 고유 기능을 제외하고 는 이 책에서 설명하지 않는다.

9


이 책이 맞지 않는 독자 이제 막 파이썬 공부를 시작했다면 이 책의 내용을 이해하기 어려울 것이다. 그뿐 아니라 파이 썬을 제대로 알지 못하는 상태에서 이 책의 내용을 접하게 되면 모든 파이썬 스크립트에서 특 별 메서드 및 메타프로그래밍 기법을 사용해야 한다고 오해할 수도 있다. 성급한 추상화는 성 급한 최적화만큼 나쁘다.

이 책의 구성 이 책은 여섯 개의 부로 구성되어 있으며 순서대로 읽도록 내용을 구성했다. 하지만 어느 정도 실력을 갖춘 독자라면 이 책의 어느 장을 바로 읽더라도 문제가 없을 것이다. 이 책에서는 기능을 직접 구현하기 전에 제공되는 기능의 사용법을 강조한다. 예를 들어 2부 2 장에서 파이썬에서 제공되는 시퀀스형을 설명하면서 collections.deque 등의 기능을 사용하 지만, 4부에 가서야 사용자 정의 시퀀스를 만드는 방법을 설명한다. 4부에서는 추상 베이스 클 래스abstract base class (ABC )를 활용하는 방법도 설명하지만, 여러분 고유의 ABC를 만드는 방법은

4부 뒷부분에 가서야 설명한다. ABC를 직접 만들기 전에 ABC를 사용하는 데 익숙해져야 한 다고 생각하기 때문이다. 이 방식은 몇 가지 장점이 있다. 먼저, 기존에 제공되는 기능을 알고 있다면 그 기능을 구현하 는 수고를 덜 수 있다. 우리는 컬렉션 객체를 직접 구현하기보다는, 구현하는 방법에 대한 설명 은 뒤로 미루고 기존 컬렉션 클래스를 사용함으로써 제공되는 기능을 활용하는 고급 기술에 집 중할 수 있다. 그리고 ABC를 처음부터 새로 만들기보다는 기존 ABC를 상속해서 사용하는 것 이 좋다. 게다가 이런 추상화를 활용하는 방법을 본 후에는 추상화를 이해하기가 더 쉬워진다. 이 전략의 단점은 곳곳에서 책 뒷부분의 참조가 많이 나온다는 것이다. 이제는 필자가 이 방식을 선택한 이유를 잘 알 것이니, 이 문제점을 양해해줄 것이라 기대한다.

10


이 책의 각 부에서 설명하는 주제는 다음과 같다.

1부 들어가며 파이썬 데이터 모델에 대한 한 개의 장으로 구성되어 있으며, __repr__( ) 등의 특별 메서 드가 모든 형의 객체 행동을 일관성 있게 유지하는 데 핵심적인 역할을 하는 과정을 설명한 다. 파이썬은 일관성으로 존경받는 언어다. 데이터 모델의 다양한 측면을 이해하는 것이 이 책 대부분의 주제지만 1장에서는 특히 전체 개념을 다룬다.

2부 데이터 구조체 시퀀스, 매핑, 집합 등 컬렉션형의 사용, 문자열과 바이트의 차이점에 대해 설명한다. 문자열 과 바이트의 구분은 파이썬 3 개발자에게는 축복이지만, 아직 코드 기반을 파이썬 3로 옮기 지 못한 파이썬 2 개발자에게는 고통스러운 부분이다. 여기서는 기존에 제공되는 기능을 돌 아보고, 조회하지 않을 때 딕셔너리의 키를 재정렬하거나, 지역화된 유니코드 문자열을 정렬 할 때 주의해야 할 점 등 특이한 작동 방식에 대해 설명한다. 다양한 시퀀스와 매핑을 설명 하면서 폭넓은 상위 수준에서 문제를 바라보지만, dict와 set 형의 기반이 되는 해시 테이블 을 살펴볼 때는 깊게 파고들기도 한다.

3부 객체로서의 함수 일급 객체 함수first-class function 에 대해 설명한다. 일급 객체가 의미하는 것, 일급 객체가 디자 인 패턴에 미치는 영향, 클로저를 이용해서 함수 데커레이터를 구현하는 방법 등을 설명한 다. 또한 파이썬의 콜러블(호출 가능 속성), 함수 속성, 인트로스펙션(내부 조사), 매개변 수 애너테이션parameter annotation, 파이썬 3에 추가된 nonlocal 선언(상위 스코프 참조) 등의 개념을 설명한다.

4부 객체지향 상용구 클래스 생성에 대해 집중적으로 알아본다. 2부의 몇 가지 예제에서 클래스 선언을 미리 사용

11


하지만, 여기서는 클래스 선언이 더 많이 나온다. 다른 객체지향 언어와 마찬가지로 파이썬 은 여러분이 클래스 기반 프로그래밍에서 배웠던, 다른 언어에서 지원하는 기능은 물론이고 지원하지 않는 기능도 다수 제공한다. 여기서는 참조가 작동하는 방식, 가변성이 의미하는 것, 객체의 수명주기, 컬렉션이나 ABC를 직접 만드는 방법, 다중 상속을 처리하는 방법, 연 산자 오버로딩을 구현하는 방법 등을 설명한다.

5부 제어 흐름 분기, 루프, 서브루틴 등 순차 제어 흐름을 뛰어넘는 파이썬 언어 기능과 라이브러리에 대 해 설명한다. 제너레이터를 설명하고 나서, 콘텍스트 관리자와 코루틴에 대해 설명하면서 새로 추가된, 까다롭지만 강력한 yield from 구문도 설명한다. 그러고 나서 collections. futures를 사용하는 스레드와 프로세스를 이용해서 파이썬에서 동시성을 구현하는 방법과

코루틴과 yield from 기능을 이용하는 asyncio로 이벤트 주도 입출력을 구현하는 방법의 전반적인 구조를 설명한다.

6부 메타프로그래밍 JSON 데이터셋 등 어느 정도 구조를 갖춘 데이터를 처리하기 위해 동적으로 생성되는 속성 을 가진 클래스를 만드는 기법을 알아본다. 그러고 나서 우리에게 친숙한 프로퍼티 메커니 즘에 대해 설명한 후, 파이썬의 디스크립터를 이용해 낮은 레벨에서 객체 속성 접근이 어떻 게 이루어지는지 깊게 파고든다. 여기서는 필드 검증 라이브러리를 단계별로 구현하면서 마 지막 장에서 설명하는 고급 도구(클래스 데커레이터와 메타클래스)를 사용할 때 발생하는 미묘한 문제를 설명한다.

코드 위주의 설명 이 책에서는 대화형 파이썬 콘솔을 이용해서 언어와 라이브러리를 살펴본다. 언어를 공부할 때 대화형 도구는 강력한 학습 도구다. 특히 읽기-평가-출력 루프read-eval-print loop (REPL )를 제공

12


하지 않는, 정적으로 컴파일하는 언어에 더 익숙한 독자에게 많은 도움이 될 것이다. 파이썬 표준 테스트 패키지 중 하나인 doctest (https://docs.python.org/3/library/doctest.

html )는 콘솔 세션을 흉내 내서 표현식을 평가하고 응답을 보여준다. 이 책의 코드 대부분과 콘 솔 출력은 doctest를 이용해서 검사했다. 그러나 이 책의 예제를 실행하기 위해 doctest를 공 부하거나 사용할 필요는 없다. doctest의 핵심 기능은 대화형 파이썬 콘솔 세션과 비슷하게 보 이는 출력을 만들어주는 것이므로 코드를 직접 대화형 콘솔에서 하는 것처럼 따라할 수 있게 해 준다. 종종 코드를 보여주기 전에 doctest를 이용해서 우리가 하려는 일을 설명할 것이다. 어떻게 하 는지에 앞서 무엇을 하려는지 명확히 알고 있으면 코딩에 집중하는 데 도움이 된다. 테스트 코 드를 먼저 작성하는 것은 테스트 주도 개발test-driven development (TDD )의 기반이 되며 특히 강의 할 때 도움이 된다. doctest를 잘 모른다면 해당 문서(https://docs.python.org/3/library/

doctest.html )와 이 책의 소스 코드를 살펴보라. 이 책의 모든 스크립트와 소스 코드는 한빛 미디어 웹페이지에서 내려받을 수 있다.

http://www.hanbit.co.kr/exam/2498 컴퓨터의 명령행 창에서 python3 -m doctest example_script.py 명령을 입력하면 이 책에 서 제공하는 코드를 검증할 수 있다.

시간 측정을 위해 사용한 하드웨어 이 책에서 소개하는 일부 코드의 성능을 테스트하기 위해 랩톱 두 대를 사용했다. 하나는

2.7GHz 인텔 코어 i7 CPU, 8GB 램, 하드 디스크를 사용하는 2011년 맥북 프로 13인치고, 다른 하나는 1.4GHz 인텔 코어 i5 CPU, 4GB 램, SSD를 사용하는 2014년 맥북 에어 13인 치다. 맥북 에어의 CPU가 더 느리고 램도 적지만, 맥북 에어의 램 클록은 1600MHz로 맥북 프로의 1333MHz보다 빠르고, SSD가 하드 디스크보다 훨씬 빠르다. 일상적으로 사용할 때 두 컴퓨터의 성능 차이는 그리 크지 않다.

13


뒷이야기: 개인 견해 필자는 1998년부터 파이썬을 사용하고, 가르치고, 토론해왔다. 또한 프로그래밍 언어, 언어의 설계, 언어의 기반이 되는 이론에 대해 학습하고 비교하는 것을 좋아한다. 각 장의 끝에는 ‘뒷 이야기’가 있는데, 여기에는 파이썬과 여타 언어에 대한 필자의 개인 견해를 담고 있다. 이 설명 이 마음에 들지 않는다면 그냥 지나쳐도 무방하다. 뒷이야기의 내용은 책 내용을 이해하는 데 전 혀 영향을 미치지 않는다.

파이썬 용어 이 책에서는 파이썬뿐만 아니라 파이썬에 대한 문화도 담으려고 했다. 공개되고 20년 이상 사 용되어오면서 파이썬 커뮤니티는 고유한 용어와 약어를 만들어냈다. 부록 B ‘파이썬 용어’에서 는 파이썬 개발자들에게 특별한 의미가 있는 용어를 설명한다.

사용한 파이썬 버전 이 책의 예제 코드는 모두 CPython 3.5로 테스트했다. 대부분의 코드는 파이썬 3.2.5와 호 환되는 PyPy3 2.4.0 등 파이썬 3.x와 호환되는 모든 인터프리터에서도 작동할 것이다. 다만 yield from과 asyncio를 사용하는 예제는 파이썬 3.3 이후 버전에서만 작동한다.

대부분의 코드는 약간만 수정하면 파이썬 2.7에서도 작동하지만, 4장에서 설명하는 유니코드 관련 예제는 작동하지 않는다.

14


감사의 글

요제프 하르트비히Josef Hartwig 의 바우하우스 체스 세트Bauhaus chess set 는 뛰어난 디자인의 좋은 사 례다. 아름답고, 단순하고, 명료하다. 건축가의 아들이자 최고의 폰트 디자이너의 형인 귀도 반 로섬Guido van Rossum 은 고급 프로그래밍 언어를 디자인하여 발표했다. 필자는 파이썬 가르치는 일 을 좋아한다. 아름답고, 단순하고, 명료하기 때문이다. 알렉스 마르텔리Alex Martelli 와 안나 레이븐스크로프트Anna Ravenscroft 는 이 책의 윤곽을 잡아주고 오라일리에서 출간을 할 수 있게 용기를 북돋워주었다. 그들의 책에서 파이썬의 관용적인 표현 을 배웠으며, 명료하고, 정확하고, 깊이 있는 책의 모델을 발견했다. 스택 오버플로에 5천 개가 넘는 글을 올린 알렉스( http://stackoverflow.com/users/95810/alex-martelli )는 파이 썬 및 파이썬의 적절한 사용에 대한 통찰력이 넘친다. 또한 마르텔리와 레이븐스크로프트는 레나르트 레제브로 Lennart Regebro와 레오나르도 로챌 Leonardo Rochael

과 함께 이 책의 테크니컬 리뷰어이기도 하다. 테크니컬 리뷰어 팀에 속한 모든 사

람은 파이썬을 15년 이상 사용했으며, 커뮤니티 내의 다른 개발자와 긴밀한 관계를 유지하면서 파이썬 프로젝트에 커다란 영향을 미쳤다. 이와 함께 이 책의 초고에 대해 수백 개의 수정, 제안, 질문, 의견을 보내주어 이 책의 가치를 엄청나게 올려주었다. 빅터 스티너Victor Stinner 는 18장을 친절히 검토하고 asyncio의 유지보수자로서의 경험을 보태주었다. 지난 수개월간 이렇게 훌륭 한 사람들과 작업을 할 수 있었던 것은 커다란 특권이자 기쁨이었다. 편집자인 메간 블란쳇Meghan Blanchette 은 뛰어난 멘토로서, 책이 지루해지는 부분을 알려주었고, 원고가 지연되지 않도록 지원해주었으며, 이 책의 구성과 흐름을 개선하는 데 커다란 도움을 주었다. 메간이 휴가간 사이 브라이언 맥도널드Brian MacDonald 가 3부 원고의 편집을 맡아주었다. 이 두 사람과 아틀라스Atlas (아틀라스는 오라일리의 출판 플랫폼으로서, 필자는 이 책을 쓰기 위 해 이 시스템을 사용하는 행운을 누렸다) 개발 및 지원팀을 포함하여 오라일리에서 접촉한 모든 사람과 일하는 것이 정말 즐거웠다. 마리오 도메네크 굴아트Mario Domenech Goulart 는 이 책의 초기 원고부터 상세한 제안을 수없이 해 주었다. 그리고 데이브 포슨Dave Pawson, 일라이어스 도넬레스Elias Dorneles, 레오나르도 알렉산드

15


레 페레이라 레이트Leonardo Alexandre Ferreira Leite, 브루스 에켈Bruce Eckel, J. S. 부에노J. S. Bueno, 라 파엘 곤잘레스Rafael Goncalves, 알렉스 치아란다Alex Chiaranda, 구토 마이아Guto Maia, 루카스 비도 Lucas Vido

, 루카스 브루니알티Lucas Brunialti 에게서도 소중한 의견을 받았다.

지난 몇 년간 책을 써보라고 제안한 사람은 많았지만, 그중 루벤스 프라테스Rubens Prates, 오렐리 오 자르가스Aurelio Jargas, 루다 모라Rudá Moura, 루벤스 알티마리Rubens Altimari 가 적극적으로 제안해 주었다. 모리치오 부삽Mauricio Bussab 은 필자가 첫 번째 책을 쓸 수 있는 기회를 포함해서 많은 기회 를 제공해주었다. 렌조 누치텔리Renzo Nuccitelli 는 우리가 함께 운영하는 python.pro.br에 대한 필자의 참여가 부족해질 것을 알면서도 이 책을 쓰는 내내 지원해주었다. 브라질 파이썬 커뮤니티는 박식하고, 여유롭고, 재미있다. 파이썬 브라질 그룹(https://groups.

google.com/group/python-brasil )은 수천 명의 개발자가 참여하며, 우리 콘퍼런스에 수백 명의 사람이 몰려든다. 그중에서도 레오나르도 로챌Leonardo Rochael, 아드리아노 페트리치Adriano Petrich

, 대니얼 바인센처Daniel Vainsencher, 로드리고 RBP 피멘텔Rodrigo RBP Pimentel, 브루노 골라

Bruno Gola

, 레오나르도 산타가다Leonardo Santagada, 장 페리Jean Ferri, 로드리고 센라Rodrigo Senra, J.

S. 부에노J. S. Bueno, 데이비드 크와스트David Kwast, 루이즈 어버Luiz Irber, 오스발도 산타나Osvaldo Santana

, 페르난도 마사노리Fernando Masanori, 헨리크 바스토스Henrique Bastos, 구스타프 니마이어

Gustavo Niemayer

, 프레도 워넥Pedro Werneck, 구스타보 바비에리Gustavo Barbieri, 랄로 마르틴스Lalo

Martins

, 다닐로 벨리니Danilo Bellini, 페드로 크로거Pedro Kroger 는 파이썬주의자Pythonista 로서의 필자

에게 가장 큰 영향을 주었다. 도넬레스 트레미아Dorneles Tremea 는 놀라운 해커로서 엄청난 시간과 지식을 제공해주었으며 브 라질 파이썬 연합의 가장 적극적인 리더였다. 그는 너무 빨리 우리 곁을 떠났다. 지난 수년간 필자의 학생들은 질문, 통찰력, 의견, 문제에 대한 창조적인 해법을 가르쳐주었다. 에리코 안드레이Érico Andrei 와 심플레스 콘술토리아Simples Consultoria 는 필자가 파이썬 강사로 집중 할 수 있게 해주었다.

16


마르틴 파센Martijn Faassen 은 Grok에 대한 필자의 멘토로서 파이썬과 네안데르탈에 대한 소중 한 영감을 불어넣어주었다. Zope, Plone, Pyramid와 같은 파이썬 라이브러리 행성에서 폴 에버리트Paul Everitt, 크리스 맥도너우Chris McDonough, 트레스 시버Tres Seaver, 짐 펄톤Jim Fulton, 셰인 헤서웨이Shane Hathaway, 레나르트 레제브로Lennart Regebro, 앨런 루이안Alan Runyan, 알렉산더 리미 Alexander Limi

, 마르틴 피터스Martijn Pieters, 고드프로이드 채펠Godefroid Chapelle 등은 필자의 경력에

결정적인 영향을 미쳤다. Zope와 웹 시대 덕분에 1998년부터 필자는 파이썬으로 생계를 유지 해갈 수 있었다. 호세 옥타비오 카스트로 네베스José Octavio Castro Neves 는 필자가 브라질에서 처음 으로 시작한 파이썬 중심의 소프트웨어 하우스 공동 설립자였다. 다양한 파이썬 커뮤니티에서 함께 해온 전문가가 너무도 많지만, 이미 언급한 사람들 외에 파 이썬을 가르치는 새롭고 더 좋은 방법을 필자에게 가르쳐준 스티브 홀덴Steve Holden, 레이몬드 헤팅거Raymond Hettinger, 앤드류 커츨링A. M. Kuchling, 데이비드 비즐리David Beazley, 프레드릭 룬드 Fredrik Lundh

, 더그 헬먼Doug Hellmann , 닉 코글란Nick Coghlan, 마크 필그림Mark Pilgrim, 마르틴 피터스

Martijn Pieters

, 브루스 에켈Bruce Eckel, 미셸 시미오나토Michele Simionato, 웨슬리 천Wesley Chun, 브랜든

크레이그 로즈Brandon Craig Rhodes, 필립 구오Philip Guo, 대니얼 그린펠드Daniel Greenfeld, 오드리 로이 Audrey Roy

, 브렛 슬랫킨Brett Slatkin 에게 감사드린다.

이 페이지는 필자의 홈 오피스와 두 개의 연구실인 커피랩과 가로아 해커 클럽에서 작성되었다. 커피랩(http://coffeelab.com.br/)은 브라질 상파울루의 빌라 마달레나에 있는 카페인 괴짜 들의 본부며, 가로아 해커 클럽(https://garoa.net.br/)은 누구나 자유롭게 새로운 아이디어를 시험해볼 수 있는 공개된 해커 공간이다. 가로아 커뮤니티는 영감, 기반 구조, 편안함을 제공한다. 알레프Aleph 가 이 책을 좋아할 것이라고 생각한다. 어머니 마리아 루시아Maria Lucia 와 아버지 자이로 하말류Jairo Ramalho 는 늘 모든 측면에서 나를 지원 해주셨다. 아버지와 함께 이 책을 보고 싶었지만, 어머니와 함께 할 수 있는 것만으로도 기쁘다.

17


아내 마타 멜로Marta Mello 는 늘 일만 하는 남편을 15개월 동안 참아주었을 뿐만 아니라, 필자가 이 긴 프로젝트에서 포기하고 싶은 생각이 드는 중요한 순간을 이겨낼 수 있도록 지원하고 조언 을 해주었다. 모두에게 감사드린다! 루시아누 하말류

18


CONTENTS

지은이·옮긴이 소개 ��������������������������������������������������������������������������������������������������������� 4

추천의 글 ����������������������������������������������������������������������������������������������������������������������� 5

이 책에 대하여 ���������������������������������������������������������������������������������������������������������������� 8

감사의 글 ��������������������������������������������������������������������������������������������������������������������� 15

Part

1

CHAPTER

들어가며

1 파이썬 데이터 모델

1.1 파이썬 카드 한 벌 �������������������������������������������������������������������������������������������������������� 39

1.2 특별 메서드는 어떻게 사용되나? ����������������������������������������������������������������������������������� 43

1.2.1 수치형 흉내 내기 ��������������������������������������������������������������������������������������������� 44

1.2.2 문자열 표현 ���������������������������������������������������������������������������������������������������� 46

1.2.3 산술 연산자 ���������������������������������������������������������������������������������������������������� 47

1.2.4 사용자 정의형의 불리언 값 �������������������������������������������������������������������������������� 48

1.3 특별 메서드 개요 ��������������������������������������������������������������������������������������������������������� 48

1.4 왜 len( )은 메서드가 아닐까? ���������������������������������������������������������������������������������������� 50

1.5 요약 ������������������������������������������������������������������������������������������������������������������������� 50

1.6 읽을거리 ������������������������������������������������������������������������������������������������������������������� 51

Part

2

CHAPTER

데이터 구조체

2 시퀀스 2.1 내장 시퀀스 개요 ��������������������������������������������������������������������������������������������������������� 58

19


CONTENTS

2.2 지능형 리스트와 제너레이터 표현식 �������������������������������������������������������������������������������� 59

2.2.1 지능형 리스트와 가독성 ������������������������������������������������������������������������������������ 60

2.2.2 지능형 리스트와 map( )/filter( ) 비교 ������������������������������������������������������������������� 62

2.2.3 데카르트 곱 ���������������������������������������������������������������������������������������������������� 62

2.2.4 제너레이터 표현식 ������������������������������������������������������������������������������������������� 64

2.3 튜플은 단순한 불변 리스트가 아니다 ������������������������������������������������������������������������������� 66

2.3.1 레코드로서의 튜플 ������������������������������������������������������������������������������������������� 66

2.3.2 튜플 언패킹 ���������������������������������������������������������������������������������������������������� 67

2.3.3 내포된 튜플 언패킹 ������������������������������������������������������������������������������������������ 69

2.3.4 명명된 튜플 ���������������������������������������������������������������������������������������������������� 71

2.3.5 불변 리스트로서의 튜플 ������������������������������������������������������������������������������������ 72

2.4 슬라이싱 ������������������������������������������������������������������������������������������������������������������� 74

2.4.1 슬라이스와 범위 지정시에 마지막 항목이 포함되지 않는 이유 ����������������������������������� 74

2.4.2 슬라이스 객체 ������������������������������������������������������������������������������������������������� 75

2.4.3 다차원 슬라이싱과 생략 기호 ����������������������������������������������������������������������������� 76

2.4.4 슬라이스에 할당하기 ���������������������������������������������������������������������������������������� 77

2.5 시퀀스에 덧셈과 곱셈 연산자 사용하기 ���������������������������������������������������������������������������� 78

2.5.1 리스트의 리스트 만들기 ������������������������������������������������������������������������������������ 79

2.6 시퀀스의 복합 할당 ������������������������������������������������������������������������������������������������������ 81

2.6.1 += 복합 할당 퀴즈 ������������������������������������������������������������������������������������������� 82

2.7 list.sort( )와 sorted( ) 내장 함수 ������������������������������������������������������������������������������������ 85

2.8 정렬된 시퀀스를 bisect로 관리하기 �������������������������������������������������������������������������������� 87

2.8.1 bisect( )로 검색하기 ���������������������������������������������������������������������������������������� 87

2.8.2 bisect.insort( )로 삽입하기 ������������������������������������������������������������������������������� 90

2.9 리스트가 답이 아닐 때 �������������������������������������������������������������������������������������������������� 91

2.9.1 배열 �������������������������������������������������������������������������������������������������������������� 91

2.9.2 메모리 뷰 ������������������������������������������������������������������������������������������������������� 95

2.9.3 NumPy와 SciPy �������������������������������������������������������������������������������������������� 96

20


2.9.4 덱 및 기타 큐 �������������������������������������������������������������������������������������������������� 99

2.10 요약 ����������������������������������������������������������������������������������������������������������������������� 102

2.11 읽을거리 ����������������������������������������������������������������������������������������������������������������� 103

CHAPTER

3 딕셔너리와 집합

3.1 일반적인 매핑형 �������������������������������������������������������������������������������������������������������� 110

3.2 지능형 딕셔너리 �������������������������������������������������������������������������������������������������������� 112

3.3 공통적인 매핑 메서드 ������������������������������������������������������������������������������������������������� 113

3.3.1 존재하지 않는 키를 setdefault( )로 처리하기 ������������������������������������������������������ 115

3.4 융통성 있게 키를 조회하는 매핑 ����������������������������������������������������������������������������������� 118

3.4.1 defaultdict : 존재하지 않는 키에 대한 또 다른 처리 ��������������������������������������������� 118

3.4.2 __missing__( ) 메서드 ����������������������������������������������������������������������������������� 120

3.5 그 외 매핑형 ������������������������������������������������������������������������������������������������������������ 123

3.6 UserDict 상속하기 ��������������������������������������������������������������������������������������������������� 124

3.7 불변 매핑 ���������������������������������������������������������������������������������������������������������������� 126

3.8 집합 이론 ���������������������������������������������������������������������������������������������������������������� 127

3.8.1 집합 리터럴 �������������������������������������������������������������������������������������������������� 129

3.8.2 지능형 집합 �������������������������������������������������������������������������������������������������� 131

3.8.3 집합 연산 ����������������������������������������������������������������������������������������������������� 132

3.9 dict와 set의 내부 구조 ���������������������������������������������������������������������������������������������� 134

3.9.1 성능 실험 ����������������������������������������������������������������������������������������������������� 135

3.9.2 딕셔너리 안의 해시 테이블 ������������������������������������������������������������������������������ 137

3.9.3 dict 작동 방식에 의한 영향 ������������������������������������������������������������������������������ 140

3.9.4 집합의 작동 방식 - 현실적으로 미치는 영향 �������������������������������������������������������� 143

3.10 요약 ����������������������������������������������������������������������������������������������������������������������� 144

3.11 읽을거리 ����������������������������������������������������������������������������������������������������������������� 145

21


CONTENTS

CHAPTER

4 텍스트와 바이트

4.1 문자 문제 ���������������������������������������������������������������������������������������������������������������� 148

4.2 바이트에 대한 기본 지식 ��������������������������������������������������������������������������������������������� 149

4.2.1 구조체와 메모리 뷰 ���������������������������������������������������������������������������������������� 152

4.3 기본 인코더/디코더 ��������������������������������������������������������������������������������������������������� 154

4.4 인코딩/디코딩 문제 이해하기 �������������������������������������������������������������������������������������� 156

4.4.1 UnicodeEncodeError 처리하기 ��������������������������������������������������������������������� 156

4.4.2 UnicodeDecodeError 처리하기 ��������������������������������������������������������������������� 157

4.4.3 예상과 달리 인코딩된 모듈을 로딩할 때 발생하는 SyntaxError ������������������������������ 158

4.4.4 바이트 시퀀스의 인코딩 방식을 알아내는 방법 ����������������������������������������������������� 160

4.4.5 BOM : 유용한 깨진 문자 ��������������������������������������������������������������������������������� 161

4.5 텍스트 파일 다루기 ���������������������������������������������������������������������������������������������������� 163

4.5.1 기본 인코딩 설정: 정신 나간 거 아냐? ��������������������������������������������������������������� 166

4.6 제대로 비교하기 위해 유니코드 정규화하기 �������������������������������������������������������������������� 169

4.6.1 케이스 폴딩 �������������������������������������������������������������������������������������������������� 172

4.6.2 정규화된 텍스트 매칭을 위한 유틸리티 함수 ������������������������������������������������������� 173

4.6.3 극단적인 ‘정규화’: 발음 구별 기호 제거하기 �������������������������������������������������������� 174

4.7 유니코드 텍스트 정렬하기 ������������������������������������������������������������������������������������������� 178

4.7.1 유니코드 대조 알고리즘을 이용한 정렬 �������������������������������������������������������������� 181

4.8 유니코드 데이터베이스 ����������������������������������������������������������������������������������������������� 181

4.9 이중 모드 str 및 bytes API ����������������������������������������������������������������������������������������� 184

4.9.1 정규 표현식에서의 str과 bytes ������������������������������������������������������������������������ 184

4.9.2 os 모듈 함수에서 str과 bytes ������������������������������������������������������������������������� 185

4.10 요약 ����������������������������������������������������������������������������������������������������������������������� 188

4.11 읽을거리 ����������������������������������������������������������������������������������������������������������������� 189

22


Part

3

CHAPTER

객체로서의 함수

5 일급 함수

5.1 함수를 객체처럼 다루기 ���������������������������������������������������������������������������������������������� 198

5.2 고위 함수 ���������������������������������������������������������������������������������������������������������������� 199

5.2.1 map( ), filter( ), reduce( )의 대안 ��������������������������������������������������������������������� 200

5.3 익명 함수 ���������������������������������������������������������������������������������������������������������������� 202

5.4 일곱 가지 맛의 콜러블 객체 ����������������������������������������������������������������������������������������� 203

5.5 사용자 정의 콜러블형 ������������������������������������������������������������������������������������������������� 204

5.6 함수 인트로스펙션 ����������������������������������������������������������������������������������������������������� 206

5.7 위치 매개변수에서 키워드 전용 매개변수까지 ����������������������������������������������������������������� 208

5.8 매개변수에 대한 정보 읽기 ������������������������������������������������������������������������������������������ 210

5.9 함수 애너테이션 �������������������������������������������������������������������������������������������������������� 215

5.10 함수형 프로그래밍을 위한 패키지 ��������������������������������������������������������������������������������� 218

5.10.1 operator 모듈 ���������������������������������������������������������������������������������������������� 218

5.10.2 functools.partial( )로 인수 고정하기 ����������������������������������������������������������������� 222

5.11 요약 ����������������������������������������������������������������������������������������������������������������������� 225

5.12 읽을거리 ����������������������������������������������������������������������������������������������������������������� 225

CHAPTER

6 일급 함수 디자인 패턴 6.1 사례: 전략 패턴의 리팩토링 ���������������������������������������������������������������������������������������� 232

6.1.1 고전적인 전략 ����������������������������������������������������������������������������������������������� 232

6.1.2 함수지향 전략 ����������������������������������������������������������������������������������������������� 236

6.1.3 최선의 전략 선택하기: 단순한 접근법 ���������������������������������������������������������������� 240

23


CONTENTS

6.1.4 모듈에서 전략 찾기 ���������������������������������������������������������������������������������������� 241

6.2 명령 ����������������������������������������������������������������������������������������������������������������������� 243

6.3 요약 ����������������������������������������������������������������������������������������������������������������������� 245

6.4 읽을거리 ����������������������������������������������������������������������������������������������������������������� 245

CHAPTER

7 함수 데커레이터와 클로저

7.1 데커레이터 기본 지식 ������������������������������������������������������������������������������������������������� 250

7.2 파이썬이 데커레이터를 실행하는 시점 ��������������������������������������������������������������������������� 251

7.3 데커레이터로 개선한 전략 패턴 ������������������������������������������������������������������������������������ 254

7.4 변수 범위 규칙 ���������������������������������������������������������������������������������������������������������� 256

7.5 클로저 �������������������������������������������������������������������������������������������������������������������� 259

7.6 nonlocal 선언 ��������������������������������������������������������������������������������������������������������� 263

7.7 간단한 데커레이터 구현하기 ���������������������������������������������������������������������������������������� 265

7.7.1 작동 과정 ����������������������������������������������������������������������������������������������������� 266

7.8 표준 라이브러리에서 제공하는 데커레이터 ��������������������������������������������������������������������� 269

7.8.1 functools.lru_cache( )를 이용한 메모이제이션 �������������������������������������������������� 269

7.8.2 단일 디스패치를 이용한 범용 함수 �������������������������������������������������������������������� 272

7.9 누적된 데커레이터 ����������������������������������������������������������������������������������������������������� 275

7.10 매개변수화된 데커레이터 �������������������������������������������������������������������������������������������� 276

7.10.1 매개변수화된 등록 데커레이터 ������������������������������������������������������������������������� 277

7.10.2 매개변수화된 clock 데커레이터 ����������������������������������������������������������������������� 279

7.11 요약 ����������������������������������������������������������������������������������������������������������������������� 282

7.12 읽을거리 ����������������������������������������������������������������������������������������������������������������� 283

24


Part

4

CHAPTER

객체지향 상용구

8 객체 참조, 가변성, 재활용

8.1 변수는 상자가 아니다 ������������������������������������������������������������������������������������������������� 292

8.2 정체성, 동질성, 별명 �������������������������������������������������������������������������������������������������� 294

8.2.1 == 연산자와 is 연산자 간의 선택 ���������������������������������������������������������������������� 296

8.2.2 튜플의 상대적 불변성 ������������������������������������������������������������������������������������� 296

8.3 기본 복사는 얕은 복사 ������������������������������������������������������������������������������������������������ 298

8.3.1 객체의 깊은 복사와 얕은 복사 �������������������������������������������������������������������������� 301

8.4 참조로서의 함수 매개변수 ������������������������������������������������������������������������������������������� 303

8.4.1 가변형을 매개변수 기본값으로 사용하기: 좋지 않은 생각 �������������������������������������� 304

8.4.2 가변 매개변수에 대한 방어적 프로그래밍 ����������������������������������������������������������� 306

8.5 del과 가비지 컬렉션 �������������������������������������������������������������������������������������������������� 309

8.6 약한 참조 ���������������������������������������������������������������������������������������������������������������� 311

8.6.1 WeakValueDictionary 촌극 ��������������������������������������������������������������������������� 312

8.6.2 약한 참조의 한계 ������������������������������������������������������������������������������������������� 314

8.7 파이썬의 특이한 불변형 처리법 ������������������������������������������������������������������������������������ 315

8.8 요약 ����������������������������������������������������������������������������������������������������������������������� 317

8.9 읽을거리 ����������������������������������������������������������������������������������������������������������������� 318

CHAPTER

9 파이썬스러운 객체

9.1 객체 표현 ���������������������������������������������������������������������������������������������������������������� 324

9.2 벡터 클래스의 부활 ���������������������������������������������������������������������������������������������������� 325

9.3 대안 생성자 ������������������������������������������������������������������������������������������������������������� 327

9.4 @classmethod와 @staticmethod ��������������������������������������������������������������������������� 328

25


CONTENTS

9.5 포맷된 출력 ������������������������������������������������������������������������������������������������������������� 330

9.6 해시 가능한 Vector2d ����������������������������������������������������������������������������������������������� 334

9.7 파이썬에서의 비공개 속성과 보호된 속성 ����������������������������������������������������������������������� 340

9.8 __slots__ 클래스 속성으로 공간 절약하기 �������������������������������������������������������������������� 343

9.8.1 __slots__를 사용할 때 주의할 점 ��������������������������������������������������������������������� 346

9.9 클래스 속성 오버라이드 ����������������������������������������������������������������������������������������������� 346

9.10 요약 ����������������������������������������������������������������������������������������������������������������������� 349

9.11 읽을거리 ����������������������������������������������������������������������������������������������������������������� 350

CHAPTER

10 시퀀스 해킹, 해시, 슬라이스

10.1 Vector : 사용자 정의 시퀀스형 ����������������������������������������������������������������������������������� 356

10.2 Vector 버전 #1: Vector2d 호환 �������������������������������������������������������������������������������� 356

10.3 프로토콜과 덕 타이핑 ����������������������������������������������������������������������������������������������� 360

10.4 Vector 버전 #2 : 슬라이스 가능한 시퀀스 �������������������������������������������������������������������� 361

10.4.1 슬라이싱 작동 방식 ������������������������������������������������������������������������������������ 362

10.4.2 슬라이스를 인식하는 __getitem__( ) ������������������������������������������������������������ 364

10.5 Vector 버전 #3 : 동적 속성 접근 �������������������������������������������������������������������������������� 366

10.6 Vector 버전 #4 : 해싱 및 더 빠른 == �������������������������������������������������������������������������� 370

10.7 Vector 버전 #5 : 포매팅 ������������������������������������������������������������������������������������������� 376

10.8 요약 ��������������������������������������������������������������������������������������������������������������������� 384

10.9 읽을거리 ��������������������������������������������������������������������������������������������������������������� 385

CHAPTER

11 인터페이스: 프로토콜에서 ABC까지

11.1 파이썬 문화에서의 인터페이스와 프로토콜 ������������������������������������������������������������������� 392

11.2 파이썬은 시퀀스를 찾아낸다 �������������������������������������������������������������������������������������� 394

26


11.3 런타임에 프로토콜을 구현하는 멍키 패칭 ��������������������������������������������������������������������� 397

11.4 알렉스 마르텔리의 물새 �������������������������������������������������������������������������������������������� 399

11.5 ABC 상속하기 ������������������������������������������������������������������������������������������������������� 405

11.6 표준 라이브러리의 ABC ������������������������������������������������������������������������������������������ 407

11.6.1 collections.abc의 ABC ���������������������������������������������������������������������������� 407

11.6.2 ABC의 숫자탑 ������������������������������������������������������������������������������������������ 409

11.7 ABC의 정의와 사용 ������������������������������������������������������������������������������������������������ 410

11.7.1 ABC 상세 구문 ����������������������������������������������������������������������������������������� 415

11.7.2 Tombola ABC 상속하기 ���������������������������������������������������������������������������� 416

11.7.3 Tombola의 가상 서브클래스 ����������������������������������������������������������������������� 419

11.8 Tombola 서브클래스 테스트 방법 ����������������������������������������������������������������������������� 422

11.9 register( )의 실제 용법 ��������������������������������������������������������������������������������������������� 426

11.10 오리처럼 행동할 수 있는 거위 ������������������������������������������������������������������������������������ 426

11.11 요약 ��������������������������������������������������������������������������������������������������������������������� 428

11.12 읽을거리 ��������������������������������������������������������������������������������������������������������������� 431

CHAPTER

12 내장 자료형 상속과 다중 상속

12.1 내장 자료형의 상속은 까다롭다 ���������������������������������������������������������������������������������� 440

12.2 다중 상속과 메서드 결정 순서 ������������������������������������������������������������������������������������ 443

12.3 실세계에서의 다중 상속 �������������������������������������������������������������������������������������������� 449

12.4 다중 상속 다루기 ����������������������������������������������������������������������������������������������������� 451

12.4.1 Tkinter : 장점, 단점, 그리고 보기 흉함 ����������������������������������������������������������� 455

12.5 최신 사례: 장고 제너릭 뷰의 믹스인 ���������������������������������������������������������������������������� 456

12.6 요약 ��������������������������������������������������������������������������������������������������������������������� 460

12.7 읽을거리 ��������������������������������������������������������������������������������������������������������������� 461

27


CONTENTS

CHAPTER

13 연산자 오버로딩: 제대로 하기

13.1 연산자 오버로딩 기본 지식 ���������������������������������������������������������������������������������������� 466

13.2 단항 연산자 ������������������������������������������������������������������������������������������������������������ 466

13.3 벡터를 더하기 위해 + 오버로딩하기 ���������������������������������������������������������������������������� 469

13.4 벡터를 스칼라와 곱하기 위해 * 오버로딩하기 ���������������������������������������������������������������� 476

13.5 향상된 비교 연산자 �������������������������������������������������������������������������������������������������� 480

13.6 복합 할당 할당자 ����������������������������������������������������������������������������������������������������� 485

13.7 요약 ��������������������������������������������������������������������������������������������������������������������� 490

13.8 읽을거리 ��������������������������������������������������������������������������������������������������������������� 491

Part

5

CHAPTER

제어 흐름

14 반복형, 반복자, 제너레이터 14.1 Sentence 버전 #1: 단어 시퀀스 �������������������������������������������������������������������������������� 501

14.1.1 Sequence가 반복 가능한 이유: iter( ) 함수 ��������������������������������������������������� 503

14.2 반복형과 반복자 ������������������������������������������������������������������������������������������������������ 504

14.3 Sentence 버전 #2 : 고전적인 반복자 �������������������������������������������������������������������������� 509

14.3.1 Sentence를 반복자로 만들기: 좋지 않은 생각 ����������������������������������������������� 511

14.4 Sentence 버전 #3 : 제너레이터 함수 �������������������������������������������������������������������������� 512

14.4.1 제너레이터 함수의 작동 방식 ����������������������������������������������������������������������� 513

14.5 Sentence 버전 #4 : 느긋한 구현 �������������������������������������������������������������������������������� 516

14.6 Sentence 버전 #5 : 제너레이터 표현식 ����������������������������������������������������������������������� 517

14.7 제너레이터 표현식: 언제 사용하나? ���������������������������������������������������������������������������� 519

28


14.8 또 다른 예제: 등차수열 제너레이터 ����������������������������������������������������������������������������� 520

14.8.1 itertools를 이용한 등차수열 ������������������������������������������������������������������������ 523

14.9 표준 라이브러리의 제너레이터 함수 ���������������������������������������������������������������������������� 525

14.10 파이썬 3.3의 새로운 구문: yield from ������������������������������������������������������������������������ 535

14.11 반복형을 리듀스하는 함수 ����������������������������������������������������������������������������������������� 537

14.12 iter( ) 함수 들여다보기 ��������������������������������������������������������������������������������������������� 538

14.13 사례 연구: 데이터베이스 변환 유틸리티 안의 제너레이터 ������������������������������������������������ 540

14.14 코루틴으로서의 제너레이터 ��������������������������������������������������������������������������������������� 542

14.15 요약 ��������������������������������������������������������������������������������������������������������������������� 543

14.16 읽을거리 ��������������������������������������������������������������������������������������������������������������� 544

CHAPTER

15 콘텍스트 관리자와 else 블록

15.1 이것 다음에 저것: if 문 이외에서의 else 블록 ��������������������������������������������������������������� 552

15.2 콘텍스트 관리자와 with 블록 ������������������������������������������������������������������������������������ 554

15.3 contextlib 유틸리티 ������������������������������������������������������������������������������������������������ 559

15.4 @contextmanager 사용하기 ���������������������������������������������������������������������������������� 560

15.5 요약 ��������������������������������������������������������������������������������������������������������������������� 564

15.6 읽을거리 ��������������������������������������������������������������������������������������������������������������� 565

CHAPTER

16 코루틴

16.1 코루틴은 제너레이터에서 어떻게 진화했는가? �������������������������������������������������������������� 570

16.2 코루틴으로 사용되는 제너레이터의 기본 동작 ��������������������������������������������������������������� 571

16.3 예제: 이동 평균을 계산하는 코루틴 ����������������������������������������������������������������������������� 575

16.4 코루틴을 기동하기 위한 데커레이터 ���������������������������������������������������������������������������� 576

29


CONTENTS

16.5 코루틴 종료와 예외 처리 ������������������������������������������������������������������������������������������� 578

16.6 코루틴에서 값 반환하기 �������������������������������������������������������������������������������������������� 583

16.7 yield from 사용하기 ������������������������������������������������������������������������������������������������ 585

16.8 yield from의 의미 ��������������������������������������������������������������������������������������������������� 592

16.9 사용 사례: 이산 이벤트 시뮬레이션을 위한 코루틴 ��������������������������������������������������������� 598

16.9.1 이산 이벤트 시뮬레이션에 대해 �������������������������������������������������������������������� 599

16.9.2 택시 집단 시뮬레이션 ��������������������������������������������������������������������������������� 600

16.10 요약 ��������������������������������������������������������������������������������������������������������������������� 609

16.11 읽을거리 ��������������������������������������������������������������������������������������������������������������� 610

CHAPTER

17 Future를 이용한 동시성 17.1 예제: 세 가지 스타일의 웹 내려받기 ���������������������������������������������������������������������������� 618

17.1.1 순차 내려받기 스크립트 ������������������������������������������������������������������������������ 620

17.1.2 concurrent.futures로 내려받기 ������������������������������������������������������������������ 622

17.1.3 Future는 어디에 있나? ������������������������������������������������������������������������������ 623

17.2 블로킹 I/O와 GIL ��������������������������������������������������������������������������������������������������� 627

17.3 concurrent.futures로 프로세스 실행하기 ������������������������������������������������������������������� 628

17.4 Executor.map( ) 실험 ��������������������������������������������������������������������������������������������� 631

17.5 진행 상황 출력하고 에러를 처리하며 내려받기 �������������������������������������������������������������� 634

17.5.1 flags2 예제에서의 에러 처리 ����������������������������������������������������������������������� 639

17.5.2 futures.as_completed( ) 사용하기 �������������������������������������������������������������� 641

17.5.3 스레드 및 멀티프로세스의 대안 �������������������������������������������������������������������� 644

17.6 요약 ��������������������������������������������������������������������������������������������������������������������� 645

17.7 읽을거리 ��������������������������������������������������������������������������������������������������������������� 646

30


CHAPTER

18 asyncio를 이용한 동시성 18.1 스레드와 코루틴 비교 ����������������������������������������������������������������������������������������������� 653

18.1.1 asyncio.Future : 논블로킹 설계 ������������������������������������������������������������������� 660

18.1.2 Future, Task , 코루틴에서 생성하기 �������������������������������������������������������������� 661

18.2 asyncio와 aiohttp로 내려받기 ��������������������������������������������������������������������������������� 663

18.3 블로킹 호출을 에둘러 실행하기 ���������������������������������������������������������������������������������� 668

18.4 asyncio 내려받기 스크립트 개선 ������������������������������������������������������������������������������� 670

18.4.1 asyncio.as_completed( ) 사용하기 ������������������������������������������������������������ 671

18.4.2 Executor를 이용해서 이벤트 루프 블로킹 피하기 ������������������������������������������� 677

18.5 콜백에서 Future와 코루틴으로 ���������������������������������������������������������������������������������� 678

18.5.1 한 번 내려받을 때 여러 가지 요청하기 ����������������������������������������������������������� 681

18.6 asyncio 서버 작성 �������������������������������������������������������������������������������������������������� 684

18.6.1 asyncio TCP 서버 ����������������������������������������������������������������������������������� 685

18.6.2 aiohttp 웹 서버 ���������������������������������������������������������������������������������������� 690

18.6.3 동시성을 향상시키는 똑똑한 클라이언트 �������������������������������������������������������� 694

18.7 요약 ��������������������������������������������������������������������������������������������������������������������� 696

18.8 읽을거리 ��������������������������������������������������������������������������������������������������������������� 697

Part

6

CHAPTER

메타프로그래밍

19 동적 속성과 프로퍼티 19.1 동적 속성을 이용한 데이터 랭글링 ������������������������������������������������������������������������������ 706

19.1.1 동적 속성을 이용해서 JSON과 유사한 데이터 둘러보기 ����������������������������������� 709

19.1.2 잘못된 속성명 문제 ������������������������������������������������������������������������������������ 712

31


CONTENTS

19.1.3 __new__( )를 이용한 융통성 있는 객체 생성 �������������������������������������������������� 714

19.1.4 shelve를 이용해서 OSCON 피드 구조 변경하기 �������������������������������������������� 716

19.1.5 프로퍼티를 이용해서 연결된 레코드 읽기 ������������������������������������������������������� 720

19.2 속성을 검증하기 위해 프로퍼티 사용하기 ��������������������������������������������������������������������� 727

19.2.1 LineItem 버전 #1: 주문 항목 클래스 ������������������������������������������������������������� 727

19.2.2 LineItem 버전 #2 : 검증하는 프로퍼티 ����������������������������������������������������������� 729

19.3 프로퍼티 제대로 알아보기 ����������������������������������������������������������������������������������������� 730

19.3.1 객체 속성을 가리는 프로퍼티 ����������������������������������������������������������������������� 732

19.3.2 프로퍼티 문서화 ���������������������������������������������������������������������������������������� 735

19.4 프로퍼티 팩토리 구현하기 ����������������������������������������������������������������������������������������� 736

19.5 속성 제거 처리하기 �������������������������������������������������������������������������������������������������� 739

19.6 속성을 처리하는 핵심 속성 및 함수 ����������������������������������������������������������������������������� 741

19.6.1 속성 처리에 영향을 주는 특별 속성 ��������������������������������������������������������������� 741

19.6.2 속성을 처리하는 내장 함수 �������������������������������������������������������������������������� 742

19.6.3 속성을 처리하는 특별 메서드 ����������������������������������������������������������������������� 742

19.7 요약 ��������������������������������������������������������������������������������������������������������������������� 744

19.8 읽을거리 ��������������������������������������������������������������������������������������������������������������� 745

CHAPTER

20 속성 디스크립터 20.1 디스크립터 예: 속성 검증 ����������������������������������������������������������������������������������������� 752

20.1.1 LineItem 버전 #3 : 간단한 디스크립터 ����������������������������������������������������������� 752

20.1.2 LineItem 버전 #4: 자동 저장소 속성명 ���������������������������������������������������������� 758

20.1.3 LineItem 버전 #5: 새로운 디스크립터형 �������������������������������������������������������� 764

20.2 오버라이딩 디스크립터와 논오버라이딩 디스크립터 ������������������������������������������������������� 768

20.2.1 오버라이딩 디스크립터 ������������������������������������������������������������������������������� 770

20.2.2 __get__( )이 없는 오버라이딩 디스크립터 ������������������������������������������������������ 771

32


20.2.3 논오버라이딩 디스크립터 ���������������������������������������������������������������������������� 772

20.2.4 클래스 안에서 디스크립터 덮어쓰기 �������������������������������������������������������������� 773

20.3 메서드는 디스크립터 ������������������������������������������������������������������������������������������������ 774

20.4 디스크립터 사용에 대한 조언 ������������������������������������������������������������������������������������� 777

20.5 디스크립터의 문서화 문자열과 관리 대상 속성의 삭제 ���������������������������������������������������� 778

20.6 요약 ��������������������������������������������������������������������������������������������������������������������� 779

20.7 읽을거리 ��������������������������������������������������������������������������������������������������������������� 780

CHAPTER

21 클래스 메타프로그래밍

21.1 클래스 팩토리 ��������������������������������������������������������������������������������������������������������� 784

21.2 디스크립터를 커스터마이즈하기 위한 클래스 데커레이터 ������������������������������������������������ 788

21.3 임포트 타임과 런타임 ����������������������������������������������������������������������������������������������� 791

21.3.1 코드 평가 시점 연습문제 ����������������������������������������������������������������������������� 792

21.4 메타클래스 기본 지식 ����������������������������������������������������������������������������������������������� 796

21.4.1 메타클래스 평가 시점 연습문제 �������������������������������������������������������������������� 799

21.5 디스크립터를 커스터마이즈하기 위한 메타클래스 ���������������������������������������������������������� 803

21.6 메타클래스 __prepare__( ) 특별 메서드 ��������������������������������������������������������������������� 805

21.7 객체로서의 클래스 ��������������������������������������������������������������������������������������������������� 808

21.8 요약 ��������������������������������������������������������������������������������������������������������������������� 809

21.9 읽을거리 ��������������������������������������������������������������������������������������������������������������� 810

부록 A 지원 스크립트 �������������������������������������������������������������������������������������������������������������������������������

815

파이썬 용어 ����������������������������������������������������������������������������������������������������������������������������������

847

책을 마치며 �����������������������������������������������������������������������������������������������������������������������������������������������

863

찾아보기 ���������������������������������������������������������������������������������������������������������������������������������������������������

868

부록 B

33



Part

I

들어가며

파이썬 데이터 모델에 대한 한 개의 장으로 구성되어 있으며, __repr__( ) 등의 특별 메서드가 모든 형의 객체 행동을 일관성 있게 유지하는 데 핵심적인 역할을 하는 과정을 설명한다. 파이썬은 일관성으로 존경받는 언어 다. 데이터 모델의 다양한 측면을 이해하는 것이 이 책 대부분의 주제지만 1장에서는 특히 전체 개념을 다룬다.


Part I

들어가며

1장

파이썬 데이터 모델


CHAPTER

1

파이썬 데이터 모델

언어 설계 미학에 대한 귀도의 감각은 놀라울 정도다. 아무도 사용하지 않을 이론적으로 아름다운 언어를 설계할 능력이 있는 훌륭한 언어 설계자를 많이 만났지만, 귀도는 이론적으로는 약간 덜 아름답지만 그렇기 때문에 프로그래밍하기 즐거운 언어를 설계할 수 있는 유례없는 능력자 중 한 사람이다.1 _ 짐 허구닌Jim Hugunin

Jython의 창시자, AspectJ의 공동 설계자, .Net DLR 아키텍트

파이썬의 최고 장점 중 하나는 일관성이다. 파이썬으로 어느 정도 작업을 해본 후에는 새로운 기능에 대해서도 제대로 예측할 수 있다. 그러나 파이썬을 배우기 전에 다른 객체지향object-oriented 언어를 배웠다면 collection.len ( ) 대신 len (collection )을 사용하는 점을 이상하게 생각할 수도 있다. 이런 괴상함은 빙산의 일각이며, 적절히 이해하면 소위 파이썬스러움pythonic 이라고 부르는 것의 핵심을 간파할 수 있 다. 이 빙산을 ‘파이썬 데이터 모델’이라고 하며, 파이썬 데이터 모델이 제공하는 API를 이용해 서 여러분 고유의 객체를 정의하면 대부분의 파이썬 상용구를 적용할 수 있다. 데이터 모델은 일종의 프레임워크로서, 파이썬을 설명하는 것이라고 생각할 수 있으며, 시퀀 스, 반복자, 함수, 클래스, 콘텍스트 관리자 등 언어 자체의 구성단위에 대한 인터페이스를 공 식적으로 정의한다.

1 사무엘 페드로니(Samuele Pedroni )와 노엘 래핀( Noel Rappin )의 『Jython Essentials』(O’Reilly, 2002)의 서문으로 작성된 ‘Story of Jython’(http://hugunin.net/story_of_jython.html )에서 발췌

37


프레임워크를 이용해서 코딩할 때는 프레임워크에 의해 호출되는 메서드를 구현하는 데 많은 시 간을 소비한다. 파이썬 데이터 모델을 사용할 때도 마찬가지다. 파이썬 인터프리터는 특별 메서 드를 호출해서 기본적인 객체 연산을 수행하는데, 종종 특별한 구문에 의해 호출된다. 특별 메서 드는 __getitem__( )처럼 언제나 앞뒤에 이중 언더바를 갖고 있다. 예를 들어 obj[key] 형태의 구문은 __getitem__( ) 특별 메서드가 지원한다. my_collection[key]를 평가하기 위해 인터 프리터는 my_collection.__getitem__(key )를 호출한다. 이러한 특별 메서드는 여러분이 구현한 객체가 다음과 같은 기본적인 언어 구조체를 구현하고 지원하고 함께 사용할 수 있게 해준다. 반복

컬렉션

속성 접근

연산자 오버로딩

함수 및 메서드 호출

객체 생성 및 제거

문자열 표현 및 포맷

블록 등 콘텍스트 관리

TIP

특별 메서드는 마술 메서드 magic method 라고도 하는데, 구체적으로 __getitem__()과 같은 메서드를 언급할 때 ‘언더바-언더바-getitem’과 같은 형태로 부르는 개발자도 있다. 이렇게 부르면 모호한 일이 발생할 수 있 다. __x는 또 다른 특별한 의미가 있기 때문이다.2 그렇다고 엄격하게 ‘언더바-언더바-getitem-언더바-언 더바’라고 발음하는 것은 성가시다. 그래서 필자는 저자이자 강사인 스티브 홀덴Steve Holden 의 발음을 따라서 ‘던더dunder -getitem’이라고 부르는 것을 선호한다. ‘던더’는 ‘더블 언더바double under ’를 줄인 말이다. 파이썬 개발자들은 이렇게 부르는 방식을 잘 알고 있다. 그래서 특별 메서드를 던더 메서드라고도 한다.3

2 9.7절 ‘파이썬에서의 비공개 속성과 보호된 속성’ 참조 3 개인적으로 필자는 ‘던더’라는 말을 스티브 홀덴에게 처음 들었다. 위키백과(http://bit.ly/1Vm72Mf )에서는 ‘__(더블 언더스코어)를 어 떻게 발음합니까?’라는 질문에 대해 2002년 9월 26일 파이썬 메일링 리스트에 올린 마크 존슨( Mark Johnson )과 팀 호크버그( Tim Hochberg )의 메시지를 기준으로 그들이 ‘던더’라는 용어를 처음 만들어냈다고 설명한다. 호크버그가 메시지를 11분 늦게 올렸다.

38

1부 들어가며


1.1 파이썬 카드 한 벌 다음 [예제 1-1]은 아주 간단한 코드지만, 특별 메서드 __getitem__( )과 __len__( )만으로도 강력한 기능을 구현할 수 있다는 것을 보여준다. 이 코드는 카드놀이에 사용할 카드 한 벌을 나타내는 클래스다. 예제 1-1 일련의 카드로 구성한 카드 한 벌

import collections Card = collections.namedtuple('Card', ['rank', 'suit']) class FrenchDeck: ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split() def __init__(self): self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position]

이 코드에서는 먼저 collections.namedtuple ( )을 이용해서 개별 카드를 나타내는 클래스를 구현한다는 점에 주의하라. 파이썬 2.6부터는 namedtuple을 이용해서 데이터베이스의 레코드 처럼 메서드를 가지지 않는 일련의 속성으로 구성된 클래스를 만들 수 있다. 이 클래스를 이용 하면 다음 콘솔 세션에서 보는 것처럼 카드 한 장을 멋지게 표현할 수 있다. beer_card = Card('7', 'diamonds') beer_card Card(rank='7', suit='diamonds')

>>> >>>

그러나 이 코드의 핵심은 FrenchDeck 클래스다. 이 코드는 간단하지만 아주 많은 기능을 구현 한다. 먼저 일반적인 파이썬 컬렉션과 마찬가지로 len ( ) 함수를 통해 자신이 갖고 있는 카드의 수를 반환한다. 1장 파이썬 데이터 모델

39


>>> >>>

deck = FrenchDeck() len(deck)

52

카드 한 벌(deck )에서 특정 카드를 읽을 수 있다. 예를 들어 deck[0]은 첫 번째 카드, deck[-1] 은 마지막 카드를 가져온다. 이 기능은 __getitem__( ) 메서드가 제공한다. deck[0] Card(rank='2', suit='spades') >>> deck[-1] Card(rank='A', suit='hearts') >>>

임의의 카드를 골라내는 메서드를 정의해야 할까? 그럴 필요 없다. 파이썬은 시퀀스에서 항목을 무작위로 골라내는 random.choice ( )라는 메서드를 제공한다. 단지 deck 객체에 다음과 같이 적용하면 된다. from random import choice choice(deck) Card(rank='3', suit='hearts') >>> choice(deck) Card(rank='K', suit='spades') >>> choice(deck) Card(rank='2', suit='clubs') >>> >>>

여기서 우리는 특별 메서드를 통해 파이썬 데이터 모델을 사용할 때의 두 가지 장점을 알게 되 었다. 사용자가 표준 연산을 수행하기 위해 클래스 자체에서 구현한 임의 메서드명을 암기할 필요가 없다(“항목

수를 알려면 어떻게 해야 하나? size ( )를 사용해야 하나? length ( )? 아니면 다른 메서드?”). 파이썬 표준 라이브러리에서 제공하는 풍부한 기능을 별도로 구현할 필요 없이 바로 사용할 수 있다(random.

choice ( ) 함수처럼).

그러나 이것이 전부는 아니다. __getitem__( ) 메서드는 self._cards의 [] 연산자에 작업을 위임하므로 deck 객체는 슬라이

싱slicing 도 자동으로 지원한다. 새로 생성한 deck 객체에서 앞의 카드 세 장을 가져오고, 12번 인

40

1부 들어가며


덱스에서 시작해서 13개씩 건너뛰어 에이스만 가져오는 방법은 다음과 같다. deck[:3] [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')] >>> deck[12::13] [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] >>>

__getitem__( ) 특별 메서드를 구현함으로써 deck을 반복할 수도 있다.

for card in deck: # doctest: +ELLIPSIS print(card) ... Card(rank='2', suit='spades') Card(rank='3', suit='spades') Card(rank='4', suit='spades') ...

>>>

deck을 뒤에서부터 반복할 수도 있다.

for card in reversed(deck): print(card) ... Card(rank='A', suit='hearts') Card(rank='K', suit='hearts') Card(rank='Q', suit='hearts') ...

>>>

# doctest: +ELLIPSIS

NOTE_ doctest에서의 생략 기호

가능한 경우 이 책의 파이썬 콘솔 출력은 정확성을 보장하기 위해 doctest에서 가져왔다. 출력이 너무 긴 경우에는 위 코드의 제일 마지막 줄처럼 생략 기호(...)로 생략한 부분을 표시했다. 이때는 # doctest: +ELLIPSIS 지시자를 이용해서 doctest를 통과하게 만들었다. 이 코드를 대화형 콘솔에서 실행할 때는 doctest 지시자를 생략하면 된다.

반복은 암묵적으로 수행되는 경우도 많다. 컬렉션에 __contains__( ) 메서드가 없다면 in 연산 자는 차례대로 검색한다. 예를 들어 FrenchDeck 클래스의 경우 반복할 수 있으므로 in이 작동 한다. 다음과 같이 확인해보자.

1장 파이썬 데이터 모델

41


Card('Q', 'hearts') in deck

>>>

True Card('7', 'beasts') in deck False >>>

정렬은 어떨까? 일반적으로 카드는 숫자 (rank )로 순위를 정하고(에이스가 제일 높다), 숫자 가 같은 경우에는 스페이드, 하트, 다이아몬드, 클로버(제일 낮음) 순으로 정한다. 이 규칙대로 카드 순위를 정하는 함수는 다음과 같다. 클로버 2의 경우 0, 스페이드 에이스의 경우 51을 반환 한다. suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) def spades_high(card): rank_value = FrenchDeck.ranks.index(card.rank) return rank_value * len(suit_values) + suit_values[card.suit]

이제 카드 한 벌을 다음과 같이 오름차순으로 나열할 수 있다. for card in sorted(deck, key=spades_high): print(card) ... Card(rank='2', suit='clubs') Card(rank='2', suit='diamonds') Card(rank='2', suit='hearts') ... (중간의 카드 46장 생략) Card(rank='A', suit='diamonds') Card(rank='A', suit='hearts') Card(rank='A', suit='spades') >>>

# doctest: +ELLIPSIS

FrenchDeck이 암묵적으로 object를 상속받지만,4 상속 대신 데이터 모델과 구성을 이용해서 기

능을 가져온다. __len__( )과 __getitem__( ) 특별 메서드를 구현함으로써 FrenchDeck은 표준 파이썬 시퀀스처럼 작동하므로 반복 및 슬라이싱 등의 핵심 언어 기능 및 random의 choice ( ), reversed ( ), sorted ( ) 함수를 사용한 예제에서 본 것처럼 표준 라이브러리를 사용할 수 있다.

구성 덕분에 __len__( )과 __getitem__( ) 메서드는 모든 작업을 list 객체인 self._cards에 떠넘길 수 있다. 4 파이썬 2에서는 FrenchDeck(object)와 같이 명시적으로 작성해야 했지만, 파이썬 3에서는 기본적으로 object를 상속받는다.

42

1부 들어가며


NOTE_ 카드 셔플링을 할 수 있을까?

지금까지 구현한 것으로는 FrenchDeck을 셔플링할 수 없다. 불변 객체기 때문이다. 캡슐화를 어기고 _cards 속성을 직접 조작하지 않는 한 카드의 값과 위치를 바꿀 수 없다. 11장에서는 __setitem__()이라 는 한 줄짜리 메서드를 추가해서 이 문제를 해결한다.

1.2 특별 메서드는 어떻게 사용되나? 특별 메서드에 대해 먼저 알아두어야 할 점은, 이 메서드는 여러분이 아니라 파이썬 인터프리터 가 호출하기 위한 것이라는 점이다. 여러분 소스 코드에서는 my_object.__len__( )으로 직접 호출하지 않고, len (my_object ) 형태로 호출한다. 만약 my_object가 사용자 정의 클래스의 객체면 파이썬은 여러분이 구현한 __len__( ) 객체 메서드를 호출한다. 그러나 list, str, bytearray 등과 같은 내장 자료형의 경우 파이썬 인터프리터는 손쉬운 방법 을 선택한다. 실제로 CPython의 경우 len ( ) 메서드는 메모리에 있는 모든 가변 크기 내장 객체 를 나타내는 PyVarObject C 구조체의 ob_size 필드의 값을 반환한다. 이 방법이 메서드를 호 출하는 방법보다 빠르다. 종종 특별 메서드가 암묵적으로 호출된다. 예를 들어 for i in x: 문의 경우 실제로는 iter (x ) 를 호출하며, 이 함수는 다시 x.__iter__( )를 호출한다. 일반적으로 사용자 코드에서 특별 메서드를 직접 호출하는 경우는 그리 많지 않다. 메타프로 그래밍을 하는 경우가 아니라면 특별 메서드를 직접 호출하는 것보다, 그것을 구현하는 횟수가 더 많을 것이다. 사용자 코드에서 특별 메서드를 자주 호출하는 경우는 __init__( ) 메서드가 유일하다. 사용자가 구현한 __init__( ) 메서드 안에서 슈퍼클래스의 __init__( ) 메서드를 호출하기 때문이다. 특별 메서드를 호출해야 하는 경우에는 일반적으로 len ( ), iter ( ), str ( ) 등 관련된 내장 함수를 호출하는 것이 좋다. 이들 내장 함수가 해당 특별 메서드를 호출한다. 하지만 내장 데이 터형의 경우 특별 메서드를 호출하지 않는 경우도 있으며 메서드 호출보다 빠르다. 자세한 내용 은 14.12절 ‘iter ( ) 함수 들여다보기’를 참조하라.

1장 파이썬 데이터 모델

43


사용자 정의 속성을 만들 때 앞뒤로 이중 언더바를 가진 __foo__와 같은 형태의 속성명은 피하 라. 현재 이런 속성명이 사용되고 있지 않더라도 나중에 특별한 의미를 갖도록 정의될 수 있기 때문이다.

1.2.1 수치형 흉내 내기 +와 같은 연산자에 사용자 정의 객체가 응답할 수 있게 해주는 몇몇 특별 메서드가 있다. 자세

한 내용은 13장에서 다루기로 하고, 여기서는 간단한 예제를 통해 특별 메서드를 사용하는 방 법을 알아보자. 수학이나 물리학에서 사용되는 2차원 유클리드 벡터를 나타내는 클래스를 구현한다고 생각해 보자(그림 1-1 ). 그림 1-1 2차원 벡터 덧셈 예제. Vector(2, 4) + Vector(2, 1)은 Vector(4, 5)가 된다.

TIP

내장된 complex 형을 이용해서 2차원 벡터를 표현할 수도 있다. 하지만 우리가 정의한 클래스는 n차원으로 쉽게 확장할 수 있다. 이에 대해서는 14장에서 알아본다.

먼저 콘솔 환경에서 이런 클래스에 대한 API를 설계해서 나중에 doctest로 사용할 수 있게 한 다. 다음은 [그림 1-1]에 표현한 벡터 덧셈을 테스트하는 코드다.

44

1부 들어가며


v1 = Vector(2, 4) v2 = Vector(2, 1) >>> v1 + v2 Vector(4, 5) >>> >>>

+ 연산자의 결과로 Vector 형이 나온다는 점에 주의하자. Vector 형은 콘솔에서 Vector로 표

현된다. 내장된 abs ( ) 함수는 정수나 실수의 절댓값을 반환하며, complex 형의 경우에도 값을 한 개만 반환하므로, 우리 API 역시 벡터의 크기를 계산하는 데 abs ( ) 함수를 사용한다. >>> >>>

v = Vector(3, 4) abs(v)

5.0

또한 * 연산자를 사용해서 스칼라곱을 수행할 수 있다(즉, 벡터에 어떤 숫자를 곱하면 동일한 방향으로 크기만 커진 벡터를 만든다). v * 3 Vector(9, 12) >>> abs(v * 3) 15.0 >>>

[예제 1-2]는 __repr__( ), __abs__( ), __add__( ), __mul__( ) 특별 메서드를 이용해서 방 금 설명한 연산을 구현하는 Vector 클래스다. 예제 1-2 간단한 2차원 벡터 클래스

from math import hypot class Vector: def __init__(self, x=0, y=0): self.x = x self.y = y

1장 파이썬 데이터 모델

45


def __repr__(self): return 'Vector(%r, %r)' % (self.x, self.y) def __abs__(self): return hypot(self.x, self.y) def __bool__(self): return bool(abs(self)) def __add__(self, other): x = self.x + other.x y = self.y + other.y return Vector(x, y) def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar)

__init__( )을 제외하고 5개의 특별 메서드를 구현했지만, 이 메서드들은 클래스 내부나 콘솔

의 테스트 코드에서 직접 호출하지 않는다는 점을 주의하라. 앞에서 설명한 것처럼 특별 메서 드는 주로 파이썬 인터프리터가 호출한다. 뒤에서 각 특별 메서드에 대해 자세히 설명한다.

1.2.2 문자열 표현 __repr__( ) 특별 메서드는 객체를 문자열로 표현하기 위해 repr ( ) 내장 메서드에 의해 호출

된다. 만일 __repr__( ) 메서드를 구현하지 않으면 Vector 객체는 콘솔에 <Vector object at 0x10e100070>과 같은 형태로 출력된다. % 연산자를 사용하는 고전적인 포맷 문자열에서의 %r 플레이스홀더처럼, 또는 str.format ( )

메서드에서 사용하는 새로운 포맷 문자열 구문(http://bit.ly/1Vm7gD1 )의 !r 변환 필드처 럼, 대화형 콘솔과 디버거는 평가된 표현식의 결과에 repr ( )을 호출한다. NOTE_ % 연산자와 str.format() 메서드 얘기가 나왔으니 한마디 하면, 파이썬 커뮤니티에서와 마찬가

지로 이 책에서도 두 방식을 모두 사용한다. 필자는 더욱 강력한 str.format() 메서드가 좀 더 마음에 들지 만, 더 간단한 % 연산자를 선호하는 파이썬 개발자도 많으므로 당분간은 파이썬 소스 코드에서 두 가지 표현 을 모두 볼 수 있을 것이다.

46

1부 들어가며


우리가 구현한 __repr__( ) 메서드에서는 출력할 속성의 표준 표현을 가져오기 위해 %r을 사 용했다는 점에 주의하자. 이는 좋은 습관이다. Vector (1, 2 )와 Vector ('1', '2')의 중요한 차이점을 보여주기 때문이다. 우리 예제의 생성자는 문자열이 아니라 숫자를 인수로 받으므로 Vector ('1', '2')는 작동하지 않는다. __repr__( ) 메서드가 반환한 문자열은 명확해야 하며, 가능하면 표현된 객체를 재생성하는 데

필요한 소스 코드와 일치해야 한다. 그렇기 때문에 우리가 선택한 표현은 Vector (3, 4 )처럼 클래스 생성자를 호출하는 모습과 동일하다. __repr__( )과 __str__( )을 비교해보자. __str__( ) 메서드는 str ( ) 생성자에 의해 호출되

며 print ( ) 함수에 의해 암묵적으로 사용된다. __str__( )은 사용자에게 보여주기 적당한 형 태의 문자열을 반환해야 한다. 이 두 특별 메서드 중 하나만 구현해야 한다면 __repr__( ) 메서드를 구현하라. 파이썬 인터프 리터는 __str__( ) 메서드가 구현되어 있지 않을 때의 대책으로 __repr__( ) 메서드를 호출하 기 때문이다. TIP

스택 오버플로(http://bit.ly/1Vm7j1N)에서 __str__()과 __repr__()의 차이점에 대한 알렉스 마르텔리 와 마르틴 피터스의 훌륭한 답변을 볼 수 있다.

1.2.3 산술 연산자 [예제 1-2]는 __add__( )와 __mul__( )의 기본 사용법을 보여주기 위해 +와 * 연산자를 구현한 다. 두 경우 모두 메서드는 Vector 객체를 새로 만들어서 반환하며 두 개의 피연산자는 변경하 지 않는다. 중위 연산자는 의례적으로 피연산자를 변경하지 않고 객체를 새로 만든다. 이에 대 해서는 13장에서 자세히 설명한다. CAUTION_ [예제 1-2]의 코드는 Vector에 숫자를 곱할 수는 있지만, 숫자에 Vector를 곱할 수는 없다. 수

학에서의 교환 법칙을 어겼기 때문이다. 이 문제는 13장에서 __rmul__() 특별 메서드를 이용해서 수정한다.

1장 파이썬 데이터 모델

47


1.2.4 사용자 정의형의 불리언 값 파이썬에도 bool 형은 있지만, if나 while 문, 혹은 and, or, not에 대한 피연산자로서 불리언 형이 필요한 곳에는 어떠한 객체라도 사용할 수 있다. x가 참된truthy 값인지 거짓된falsy 값인지 판 단하기 위해 파이썬은 bool (x )를 적용하며, 이 함수는 항상 True나 False를 반환한다. __bool__( )이나 __len__( )을 구현하지 않은 경우, 기본적으로 사용자 정의 클래스의 객체는

참된 값이라고 간주된다. bool (x )는 x.__bool__( )을 호출한 결과를 이용한다. __bool__( ) 이 구현되어 있지 않으면 파이썬은 x.__len__( )을 호출하며, 이 특별 메서드가 0을 반환하면 bool ( )은 False를, 그렇지 않으면 True를 반환한다.

우리가 구현할 __bool__( )은 개념적으로 간단하다. 벡터의 크기가 0이면 False를 반환하고 그 렇지 않으면 True를 반환한다. __bool__( )은 불리언형을 반환해야 하므로 bool (abs (self ) ) 를 이용해서 크기를 불리언형으로 변환한다. 여러분이 정의한 객체를 __bool__( ) 특별 메서드가 어떻게 파이썬 표준 라이브러리 문서의 ‘내장 자료형Built-in Types ’(http://docs.python.org/3/library/stdtypes.html#truth )에 정의 된 참값 검사 규칙을 적용하는지 주의 깊게 살펴보라. NOTE_ Vector.__bool__()의 고속 버전은 다음과 같다.

def __bool__(self): return bool(self.x or self.y) 이 코드는 가독성은 떨어지지만, abs(), __abs__(), 제곱, 제곱근 연산을 수행하지 않는다. __bool__()이 불리언형을 반환해야 하므로 bool()을 이용해서 명시적으로 자료형을 변환해야 하며, or 연산자는 두 피연 산자 중 하나를 반환한다. 즉 x or y 문은 x가 참된 값인 경우에는 x, 아닌 경우에는 y를 반환한다.

1.3 특별 메서드 개요 파이썬 언어 참조 문서의 ‘데이터 모델Data Model ’ 장(http://docs.python.org/3/reference/

datamodel.html )에서는 83개 특별 메서드 이름을 나열하는데, 그중 47개는 산술, 비트, 비 교 연산자를 구현하기 위해 사용된다. 사용되는 메서드명은 [표 1-1]과 [표 1-2]를 참조하라.

48

1부 들어가며


NOTE_ 아래 표에서 그룹화한 것은 편의상 구분한 것이며 공식 문서의 그룹과 정확히 일치하지는 않는다.

표 1-1 특별 메서드명(연산자 제외) 범주

메서드명

문자열/바이트 표현

__repr__, __str__, __format__, __bytes__

숫자로 변환

__abs__, __bool__, __complex__, __int__, __float__, __hash__, __index__

컬렉션 에뮬레이션

__len__, __getitem__, __setitem__, __delitem__, __contains__

반복

__iter__, __reversed__, __next__

콜러블 에뮬레이션

__call__

콘텍스트 관리

__enter__, __exit__

객체 생성 및 소멸

__new__, __init__, __del__

속성 관리

__getattr__, __getattribute__, __setattr__, __delattr__, __dir__

속성 디스크립터

__get__, __set__, __delete__

클래스 서비스

__prepare__, __instancecheck__, __subclasscheck__

표 1-2 연산자에 대한 특별 메서드명 범주

메서드명 및 관련 연산자

단항 수치 연산자

__neg__ -, __pos__ +, __abs__ abs()

향상된 비교 연산자

__lt__ <, __le__ <=, __eq__ ==, __ne__ !=, __gt__ >, __ge__ >=

산술 연산자

__add__ +, __sub__ -, __mul__ *, __truediv__ /, __floordiv__ //, __mod__ %, __divmod__ divmod(), __pow__ **이나 pow(), __round__ round()

역순 산술 연산자

__radd__, __rsub__, __rmul__, __rtruediv__, __rfloordiv__, __rmod__, __rdivmod__, __rpow__

복합 할당 산술 연산자

__iadd__, __isub__, __imul__, __itruediv__, __ifloordivv_, __imod__, __ipow__

비트 연산자

__invert__ ~, __lshift__ __xor__ ^

역순 비트 연산자

__rlshift__, __rrshift__, __rand__, __rxor__, __ror__

복합 할당 비트 연산자

__ilshift__, __irshift__, __iand__, __ixor__, __ior__

TIP

<<,

__rshift__

>>,

__and__ &, __or__ |,

피연산자의 순서가 바뀌었을 때는(a * b 대신 b * a 사용) 역순 연산자reversed operator 가 사용되는 반면, 복합 할당augmented assignment 은 중위 연산자와 변수 할당을 간략히 표현한다(a = a * b를 a *= b로 표현). 13장 에서 역순 연산자와 복합 할당 연산자에 대해 자세히 설명한다.

1장 파이썬 데이터 모델

49


1.4 왜 len( )은 메서드가 아닐까? 필자가 2013년도에 핵심 개발자 레이몬드 헤팅거Raymond Hettinger 에게 이 질문을 던졌을 때 돌아 온 답의 핵심은 ‘파이썬의 선The Zen of Python ’(https://www.python.org/doc/humor/#the-

zen-of-python )을 인용한 ‘실용성이 순수성에 우선한다practicality beats purity ’였다. 1.2절의 ‘특 별 메서드는 어떻게 사용되나?’에서 len (x )는 x가 내장형의 객체일 때 아주 빨리 실행된다고 설명했다. CPython의 내장 객체에 대해서는 메서드를 호출하지 않고 단지 C 언어 구조체의 필드를 읽어올 뿐이다. 컬렉션에 들어 있는 항목 수를 가져오는 연산은 자주 발생하므로 str, list, memoryview 등의 다양한 기본형 객체에 대해 효율적으로 작동해야 한다.

다시 말해, len ( )은 abs ( )와 마찬가지로 파이썬 데이터 모델에서 특별한 대우를 받으므로 메서 드라고 부르지 않는다. 그러나 __len__( ) 특별 메서드 덕분에 여러분이 정의한 객체에서 len ( ) 메서드를 직접 정의할 수 있다. 이것은 내장형 객체의 효율성과 언어의 일관성 간의 타협점을 어 느 정도 찾은 것이다. 게다가 ‘파이썬의 선’에서는 ‘특별한 경우는 규칙을 어길 만큼 특별하지 않다special cases aren’t special enough to break the rules ’고 설명하고 있다. NOTE_ abs()나 len()을 단항 연산자로 생각한다면, 객체지향 언어에서 볼 수 있는 메서드 호출 구문 대

신 함수처럼 구현한 이유를 이해할 수 있을 것이다. 사실 이런 기능의 상당 부분을 개척한 파이썬의 선조 언 어인 ABC에서는 len()에 대응되는 # 연산자가 있다(#s 형태로 사용한다). x#s처럼 중위 연산자로 사용하 면 s 안에서 x가 나타난 횟수를 계산하는데, 파이썬에서는 s.count(x)로 호출한다.

1.5 요약 특별 메서드를 구현하면 사용자 정의 객체도 내장형 객체처럼 작동하게 되어, 파이썬스러운pythonic 표현력 있는 코딩 스타일을 구사할 수 있다. 파이썬 객체는 기본적으로 자신을 문자열 형태로 제공해야 하는데, 디버깅 및 로그에 사용하는 형태와 사용자에게 보여주기 위한 형태가 있다. 그렇기 때문에 데이터 모델에 __repr__( )과 __str__( ) 특별 메서드가 있는 것이다. FrenchDeck 예제에서 본 것처럼 시퀀스를 흉내 내기 위해 특별 메서드가 널리 사용된다. 시퀀스

50

1부 들어가며


형을 최대한 활용하는 방법은 2장에서 설명하며, 10장에서는 Vector 클래스를 다차원으로 확 장하면서 자신만의 시퀀스 객체를 구현하는 방법을 설명한다. 연산자 오버로딩 덕분에 파이썬은 내장형에서 decimal.Decimal과 fractions.Fraction에 이 르기까지 중위 산술 연산을 지원하는 다양한 수치형을 제공한다. 역순 연산자 및 복합 할당 연산 자를 포함하여 다양한 연산자 구현 방법은 13장에서 Vector 예제를 개선하면서 설명한다. 파이썬 데이터 모델에서 제공하는 나머지 특별 메서드의 사용과 구현은 이 책 모든 부분에서 설 명한다.

1.6 읽을거리 이 장과 이 책의 많은 내용은 파이썬 언어 참조 문서The Python Language Reference 의 ‘데이터 모델Data Model

’ 장(http://docs.python.org/3/reference/datamodel.html )을 기준으로 설명했다.

알렉스 마르텔리Alex Martelli 의 『Python in a Nutshell, 2E』(O’Reilly, 2006 )는 데이터 모델을 훌륭하게 설명하고 있다. 그 책은 2006년에 출간되어 파이썬 2.5를 집중적으로 다루고 있지 만, 그 후 데이터 모델에서의 변화는 거의 없었다. 속성 접근 메커니즘에 대한 마르텔리의 설명 은 CPython을 구현한 실제 C 소스 코드 외에 필자가 본 가장 정통한 설명이다. 마르텔리는 스 택 오버플로에서 5천 번 이상의 답변을 한 왕성한 공헌자다. 스택 오버플로에서 마르텔리의 프 로필(http://stackoverflow.com/users/95810/alex-martelli )을 살펴보라. 데이비드 비즐리 David Beazley 는 『Python Essential Reference , 4E 』( Addison -Wesley

Professional, 2009 )5 및 브라이언 K. 존스Brian K. Jones 와 공저한 『Python Cookbook, 3E』 (O’Reilly, 2013 )6 책에서 파이썬 3의 데이터 모델을 자세히 설명하고 있다. 그레고르 킥잘레스Gregor Kiczales, 짐 데스 리비어스Jim des Rivieres, 대니얼 G. 보브로우Daniel G. Bobrow

토콜

의 『The Art of the Metaobject Protocol』(MIT Press, 1991 )에서는 메타객체 프로

metaobject protocol

(MOP ) 개념을 설명하고 있는데, 파이썬 데이터 모델도 MOP에 속한다.

5 옮긴이_ 『파이썬 완벽 가이드』(인사이트, 2012 ) 6 옮긴이_ 『Python Cookbook (개정 3판)』(인피니티북스, 2014 )

1장 파이썬 데이터 모델

51


- 뒷이야기 데이터 모델 또는 객체 모델?

파이썬 문서에서 ‘파이썬 데이터 모델’이라고 부르는 것을 다른 저자들은 ‘파이썬 객체 모델’이 라고 부른다. 알렉스 마르텔리의 『Python in a Nutshell, 2E』와 데이비드 비즐리의 『Python

Essential Reference, 4E』는 ‘파이썬 데이터 모델’을 설명하고 있지만, 언제나 ‘객체 모델’이 라고 부른다. ‘객체 모델’에 대한 위키백과(https://en.wikipedia.org/wiki/Object_model) 의 첫 번째 설명을 보면 ‘특정 컴퓨터 프로그래밍 언어에서 일반적인 객체의 속성’이라고 되어 있다. 이것이 바로 ‘파이썬 데이터 모델’에 대한 설명이다. 이 책에서는 파이썬 객체 모델을 언 급할 때 문서를 존중하는 의미에서 ‘데이터 모델’이라는 용어를 사용한다. 이는 파이썬 언어 참 조 문서에서 이 내용과 가장 밀접한 연관이 있는 장(https://docs.python.org/3/reference/

datamodel.html)의 제목이기도 하기 때문이다. 마술 메서드

루비 커뮤니티에서는 특별 메서드를 마술 메서드라고도 부른다. 파이썬 커뮤니티에서도 이 용 어를 많이 사용한다. 그러나 필자는 특별 메서드가 마술 메서드와 정반대라고 생각한다. 파이썬 과 루비는 마술이 아니라 풍부한 메타객체 프로토콜을 강화하고 있으며 핵심 사용자가 사용하는 도구를 일반 개발자도 사용할 수 있게 해준다. 이와 반대되는 자바스크립트를 보자. 사용자가 정의한 객체가 내장된 객체를 그대로 흉내 낼 수 없다는 점에서, 내장 객체는 마술을 부린다. 예를 들어 자바스크립트 1.8.5 이전에는 객체 에 읽기 전용 속성을 정의할 수 없었지만, 일부 내장 객체는 읽기 전용 속성을 갖고 있었다. 자 바스크립트에서 읽기 전용은 ‘마술’이었으며, 2009년 ECMAScript 5.1 표준이 나오기 전까지 는 일반 개발자들이 가질 수 없는 초능력이 필요했다. 자바스크립트의 메타객체 프로토콜이 진 화는 했지만, 역사적으로 파이썬이나 루비에 비해 제한이 많았다. 메타객체

『The Art of the Metaobject Protocol(AMOP)』은 필자가 좋아하는 책이다. 객관적으로 봐 도 메타객체 프로토콜이라는 용어는 파이썬 데이터 모델 및 다른 언어에서 유사 기능을 공부 할 때 유용한 개념이다. 메타객체는 언어 자체를 구현하는 객체를 말한다. 그리고 여기에서 말 하는 프로토콜은 인터페이스와 같은 의미다. 따라서 메타객체 프로토콜은 객체 모델(핵심 언어 구조체에 대한 API)의 멋진 동의어다. 메타객체 프로토콜이 풍부하면 새로운 프로그래밍 패러다임을 지원하기 위해 언어를 확장할

52

1부 들어가며


수 있다. 『The Art of the Metaobject Protocol』의 첫 번째 저자인 그레고르 킥잘레스는 나 중에 관점지향 프로그래밍aspect-oriented programming을 개척하고, 이 패러다임을 구현한 자바 확장인 초기 AspectJ를 공동 설계했다. 관점지향 프로그래밍은 파이썬과 같은 동적 언어에서 구현하 기 훨씬 더 쉽고 구현된 프레임워크도 여럿 있지만, 가장 중요한 것은 zope.interface(http://

docs.zope.org/zope.interface/)다. zope.interface에 대해서는 11장의 ‘읽을거리’에서 간 략히 다룬다.

1장 파이썬 데이터 모델

53



Part

II

데이터 구조체

시퀀스, 매핑, 집합 등 컬렉션형의 사용 및 문자열과 바이트의 차이점에 대해 설명한다. 문자열과 바이트의 구 분은 파이썬 3 개발자에게는 축복이지만, 아직 코드 기반을 파이썬 3로 옮기지 못한 파이썬 2 개발자에게는 고통스러운 부분이다. 여기서는 기존에 제공되는 기능을 돌아보고, 조회하지 않을 때 딕셔너리의 키를 재정렬 하거나, 지역화된 유니코드 문자열을 정렬할 때 주의해야 할 점 등 특이한 작동 방식에 대해 설명한다. 다양한 시퀀스와 매핑을 설명하면서 폭넓은 상위 수준에서 문제를 바라보지만, dict와 set 형의 기반이 되는 해시 테 이블을 살펴볼 때는 깊게 파고들기도 한다.


Part II

데이터 구조체

2장

시퀀스

3장

딕셔너리와 집합

4장

텍스트와 바이트


CHAPTER

2

시퀀스

코드에서 감을 잡았겠지만, 여기서 설명한 연산들은 텍스트, 리스트, 테이블에도 동일하게 적용된다. 텍스트, 리스트, 테이블을 합쳐서 ‘기차’라고 부른다. [중략] 일반적으로 FOR 명령도 기차에 사용할 수 있다.1 _ 거츠, 미어텐즈, 펨버튼 『ABC Programmer’s Handbook』

파이썬을 만들기 전에 귀도는 ABC 언어에 참여하고 있었다. ABC 언어는 초보자를 위한 프로 그래밍 환경을 개발하기 위해 10년간 진행한 연구 프로젝트다. ABC는 시퀀스에 대한 범용 연 산, 내장된 튜플과 매핑 자료형, 들여쓰기를 이용한 구문 구조, 변수를 선언하지 않고 강력하게 자료형 검사하기 등 우리가 ‘파이썬스러운’ 것이라고 생각하는 여러 개념을 소개했다. 파이썬이 사용자 친화적인 것은 다 이유가 있다. 파이썬은 시퀀스를 단일하게 처리하는 ABC의 특징을 물려받았다. 문자열, 리스트, 바이트 시 퀀스, 배열, XML 요소, 데이터베이스 결과에는 모두 반복, 슬라이싱, 정렬, 연결 등 공통된 연 산을 적용할 수 있다. 파이썬에서 제공하는 다양한 시퀀스를 이해하면 코드를 새로 구현할 필요가 없으며, 시퀀스의 공통 인터페이스를 따라 기존 혹은 향후에 구현될 시퀀스 자료형을 적절히 지원하고 활용할 수 있게 API를 정의할 수 있다. 이 장에서는 리스트부터 문자열 및 파이썬 3에 새로 소개된 바이트 자료형에 이르기까지 주로 1 레오 거츠(Leo Geurts), 램버트 미어텐즈(Lambert Meertens), 스티븐 펨버튼(Steven Pemberton)의 『ABC Programmer’s Handbook』(Bosko Books, 2005), 8쪽

57


시퀀스에 해당하는 내용을 설명한다. 리스트, 튜플, 배열, 큐에 대해서도 구체적으로 설명하지 만, 유니코드 문자열에 대한 자세한 설명은 4장에서 다룬다. 그리고 여기서는 표준 라이브러리 에서 제공하는 시퀀스를 사용하며, 시퀀스형을 구현하는 방법은 10장에서 설명한다.

2.1 내장 시퀀스 개요 파이썬 표준 라이브러리는 C로 구현된 다음과 같은 시퀀스형을 제공한다.

컨테이너 시퀀스 서로 다른 자료형의 항목들을 담을 수 있는 list, tuple, collections.deque 형 균일 시퀀스 단 하나의 자료형만 담을 수 있는 str, bytes, bytearray, memoryview, array.array 형

컨테이너 시퀀스container sequence 는 객체에 대한 참조를 담고 있으며 객체는 어떠한 자료형도 될 수 있지만, 균일 시퀀스flat sequence 는 객체에 대한 참조 대신 자신의 메모리 공간에 각 항목의 값 을 직접 담는다. 따라서 균일 시퀀스가 메모리를 더 적게 사용하지만, 문자, 바이트, 숫자 등 기 본적인 자료형만 저장할 수 있다. 시퀀스형은 다음과 같이 가변성에 따라 분류할 수도 있다.

가변 시퀀스 list, bytearray, array.array, collections.deque, memoryview 형

불변 시퀀스 tuple, str, bytes 형

[그림 2-1]을 보면 가변 시퀀스가 불변 시퀀스와 어떻게 다른지와 어느 메서드를 상속하는 지 알 수 있다. 내장된 구체적인 시퀀스형들이 그림에서 보는 것처럼 실제로 Sequence나 MutableSequence 추상 베이스 클래스abstract base class ( ABC )를 상속하는 것은 아니지만, 추상

베이스 클래스를 이용하면 실제 시퀀스형에서 어느 기능을 제공할지 예측하는 데 도움이 된다.

58

2부 데이터 구조체


그림 2-1 collections.abc의 일부 클래스에 대한 UML 다이어그램. 슈퍼클래스는 왼쪽에 있으며, 상속 관계를 나타내 는 화살표는 서브클래스에서 슈퍼클래스를 향한다. 이탤릭체로 표시된 이름은 추상 클래스와 추상 메서드를 나타낸다.

‘가변과 불변’ 그리고 ‘컨테이너와 균일’에 대한 이런 일반적인 성질을 기억해두면 앞으로 나올 구체적인 시퀀스형의 기능을 가늠하는 데 도움이 된다. 가장 기본적인 시퀀스형인 list는 가변적이며 혼합된 자료형을 담을 수 있다. 리스트형에 대해 서는 잘 알고 있을 거라고 가정하고, 이제 바로 지능형 리스트list comprehension 로 넘어가자. 지능 형 리스트는 낯선 구문 때문에 그리 많이 사용되지 않는다. 지능형 리스트를 제대로 알고 있으 면 제너레이터 표현식generator expression 도 쉽게 이해할 수 있다. 제너레이터를 사용하면 어떤 자 료형의 시퀀스도 쉽게 채울 수 있다. 지능형 리스트와 제너레이터 표현식은 다음 절에서 설명 한다.

2.2 지능형 리스트와 제너레이터 표현식 지능형 리스트(리스트형의 경우)나 제너레이터 표현식(그 외 시퀀스의 경우)을 사용하면 시 퀀스를 간단히 생성할 수 있다. 이런 구문을 사용하고 있지 않다면, 가독성이 좋고 때로는 실행 속도도 빠른 코드를 만들 기회를 허비하고 있는 것이다. ‘가독성이 좋다’는 필자의 주장에 의구심이 생긴다면 책을 계속해서 읽어나가길 바란다. 필자의 주장이 옳다는 것을 알게 될 것이다. TIP

파이썬 프로그래머들은 종종 지능형 리스트를 listcomp, 제너레이터 표현식을 genexp로 간략히 표현하기 도 한다. 이 책에서는 이 용어들도 사용한다.

2장 시퀀스

59


2.2.1 지능형 리스트와 가독성 문제를 하나 내보겠다. 다음 [예제 2-1]과 [예제 2-2] 중 어느 코드가 읽기 쉬운가? 예제 2-1 문자열에서 유니코드 코드포인트 리스트 만들기(버전 1)

symbols = '$¢£¥€¤' codes = [] >>> for symbol in symbols: codes.append(ord(symbol)) ... ... >>> codes [36, 162, 163, 165, 8364, 164] >>> >>>

예제 2-2 문자열에서 유니코드 코드포인트 리스트 만들기(버전 2)

symbols = '$¢£¥€¤' codes = [ord(symbol) for symbol in symbols] >>> codes [36, 162, 163, 165, 8364, 164] >>> >>>

파이썬에 대해 조금이라도 아는 사람이면 [예제 2-1]을 읽을 수 있다. 그러나 지능형 리스트를 배운 후에는 [예제 2-2]가 더 읽기 좋다는 생각이 들 것이다. 의도를 명확히 보여주기 때문이다. 시퀀스를 읽어서 개수를 세거나 어떤 항목을 골라내거나 합계를 구하는 등 for 루프는 아주 다 양한 일에 사용할 수 있다. [예제 2-1]에서는 for 루프를 이용해서 리스트를 만든다. 이와 대조 적으로 지능형 리스트는 오로지 새로운 리스트를 만드는 일만 한다. 물론 지능형 리스트를 남용해서 정말 이해하기 어려운 코드를 만들 수도 있다. 지능형 리스트 의 부작용을 이용해서 일련의 코드를 반복 수행하는 파이썬 코드도 본 적이 있다. 생성된 리스 트를 사용하지 않을 거라면 지능형 리스트 구문을 사용하지 말아야 한다. 그리고 코드를 짧게 만들어야 한다. 지능형 리스트 구문이 두 줄 이상 넘어가는 경우에는 코드를 분할하거나 for 문을 이용해서 작성하는 것이 더 낫다. 경우에 맞게 상식적으로 판단하라. 영어와 마찬가지로 파이썬에서도 이 둘 중 하나를 선택하는 왕도가 따로 없다. TIP

파이썬에서는 [], {}, () 안에서의 개행이 무시된다. 따라서 줄을 넘기기 위해 역슬래시(\)를 사용하지 않고도 여러 줄에 걸쳐 리스트, 지능형 리스트, 제너레이터 표현식, 딕셔너리를 작성할 수 있다.

60

2부 데이터 구조체


_ 지능형 리스트는 더 이상 메모리를 누수하지 않는다 _ 파이썬 2.x의 경우 지능형 리스트 안의 for 문에서 할당한 변수는 주변 범위에서 다른 변수와 함께 설정되므로 때로는 비참한 결과가 일어나기도 했다. 다음의 파이썬 2.7 콘솔 세션을 보자. Python 2.7.6 (default, Mar 22 2014, 22:59:38) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> x = 'my precious' >>> dummy = [x for x in 'ABC'] >>> x 'C'

여기서 볼 수 있는 것처럼 x에 처음 설정되었던 값이 사라졌다. 파이썬 3에서는 이 문제가 발생 하지 않는다. 지능형 리스트, 제너레이터 표현식, 그리고 이와 동급인 지능형 집합set comprehension과 지능형 딕 셔너리dict comprehension는 함수처럼 고유한 지역 범위를 가진다. 표현식 안에서 할당된 변수는 지 역 변수지만, 주변 범위의 변수를 여전히 참조할 수 있다. 게다가 지역 변수가 주변 범위의 변 수를 가리지 않는다. x = 'ABC' dummy = [ord(x) for x in x] >>> x ➊ 'ABC' >>> dummy ➋ [65, 66, 67] >>> >>>

➊ x의 값이 유지된다. ➋ 지능형 리스트가 기대했던 리스트를 만든다.

지능형 리스트는 항목을 필터링 및 변환함으로써 시퀀스나 기타 반복 가능한 자료형으로부터 리스트를 만든다. 다음 절에서 설명하는 것처럼 내장된 filter ( )와 map ( ) 함수를 사용해서 이와 동일한 작업을 수행할 수는 있지만 가독성은 떨어진다.

2장 시퀀스

61


2.2.2 지능형 리스트와 map( )/filter( ) 비교 map ( )과 filter ( ) 함수를 이용해서 수행할 수 있는 작업은 기능적으로 문제가 있는 파이썬 람

다lambda 를 억지로 끼워 넣지 않고도 지능형 리스트를 이용해서 모두 구현할 수 있다. [예제 2-3] 을 보자. 예제 2-3 지능형 리스트와 맵/필터 구성으로 만든 동일 리스트

symbols = '$¢£¥€¤' beyond_ascii = [ord(s) for s in symbols if ord(s) > 127] >>> beyond_ascii [162, 163, 165, 8364, 164] >>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols))) >>> beyond_ascii [162, 163, 165, 8364, 164] >>> >>>

필자는 map ( )/filter ( )를 조합한 방법이 지능형 리스트보다 빠르다고 생각하고 있었지만, 알 렉스 마르텔리의 지적에 따라 그렇지 않다는 것을 알게 되었다(적어도 앞에 나온 예제에서는 그렇다). 이 책의 예제 코드에 있는 02-array-seq/listcomp_speed.py 스크립트는 지능형 리스트와 map ( )/filter ( ) 조합의 속도를 간단히 비교한다. map ( )/filter ( ) 조합에 대해서는 5장에서 자세히 설명한다. 이제 지능형 리스트를 이용해서

데카르트 곱을 계산해보자. 데카르트 곱은 두 개 이상의 리스트에 있는 모든 항목을 이용해서 만든 튜플로 구성된 리스트다.

2.2.3 데카르트 곱 지능형 리스트는 두 개 이상의 반복 가능한 자료형의 데카르트 곱을 나타내는 일련의 리스트를 만들 수 있다. 데카르트 곱 안에 들어 있는 각 항목은 입력으로 받은 반복 가능한 데이터의 각 요소에서 만들어진 튜플로 구성된다. [그림 2-2]에서 보는 것처럼 생성된 리스트의 길이는 입 력으로 받은 반복 가능한 데이터의 길이와 동일하다.

62

2부 데이터 구조체


CHAPTER

3

딕셔너리와 집합

프로그램 코드 안에서 명시적으로 딕셔너리를 사용하고 있지 않더라도, 모든 파이썬 프로그램에서는 여러 딕셔너리가 동시에 활동하고 있다. _ A . M. 커츨링 『Beautiful Code』(한빛미디어, 2007 ) 18장 ‘파이썬의 딕셔너리 구현’ 중

dict 형은 애플리케이션에서 널리 사용될 뿐만 아니라 파이썬 구현의 핵심 부분이기도 하다.

모듈 네임스페이스, 클래스 및 인스턴스 속성, 함수의 키워드 인수 등 핵심 부분에 딕셔너리가 사용되고 있다. 내장 함수들은 __builtins__.__dict__에 들어 있다. 중요한 역할을 맡고 있으므로 파이썬 dict 클래스는 상당히 최적화되어 있다. 파이썬의 고성능 딕셔너리 뒤에는 해시 테이블이라는 엔진이 있다. 집합도 해시 테이블을 이용해서 구현하므로, 이 장에서는 집합도 다룬다. 해시 테이블이 작동하 는 방식을 알아야 딕셔너리와 집합을 최대로 활용할 수 있다. 이 장에서는 다음과 같은 내용을 설명한다. 공통적으로 사용되는 딕셔너리 메서드

없는 키에 대한 특별 처리

표준 라이브러리에서 제공하는 다양한 딕셔너리 클래스

set과 frozenset 형

해시 테이블의 작동 방식

해시 테이블의 의미(키 자료형 제한, 예측할 수 없는 순서 등)

109


3.1 일반적인 매핑형 collections.abc 모듈은 dict 및 이와 유사한 자료형의 인터페이스를 정의하기 위해 Mapping

및 MutableMapping 추상 베이스 클래스(ABC )를 제공한다(파이썬 2.6에서 3.2까지는 이 클 래스들이 collections.abc가 아닌 collections 모듈에 들어 있다). [그림 3-1]을 참조하라. 그림 3-1 collections.abc의 MutableMapping과 그 슈퍼클래스들의 UML 클래스 다이어그램. 상속 관계를 나타내 는 화살표는 서브클래스에서 슈퍼클래스를 가리키며, 추상 클래스와 추상 메서드는 이탤릭체로 표시했다.

특화된 매핑은 여기에 나온 추상 베이스 클래스 대신 dict나 collections.UserDict 클래스를 상속하기도 한다. 추상 베이스 클래스는 매핑이 제공해야 하는 최소한의 인터페이스를 정의하고 문서화하기 위한 것이며, 넓은 의미의 매핑을 지원해야 하는 코드에서 isinstance ( ) 테스트를 하기 위한 기준으로 사용된다. my_dict = {} import collections isinstance(my_dict, collections.abc.Mapping)

>>> >>> >>>

True

함수 인수가 dict 형인지 검사하는 것보다 isinstance ( ) 함수를 사용하는 것이 좋다. 다른 매 핑형이 사용될 수도 있기 때문이다. 표준 라이브러리에서 제공하는 매핑형은 모두 dict를 이용해서 구현하므로, 키가 해시 가능해 야 한다는 제한을 갖고 있다(값은 해시 가능할 필요 없고, 키만 해시 가능하면 된다).

110 2부 데이터 구조체


_ 해시 가능하다는 말의 의미는? _ 파이썬 용어집(http://bit.ly/1K4qjwE)에서는 ‘해시 가능하다hashable ’는 용어를 다음과 같이 정의하고 있다. 수명 주기 동안 결코 변하지 않는 해시값을 갖고 있고(__hash__( ) 메서드가 필요하다) 다른 객 체와 비교할 수 있으면(__eq__( ) 메서드가 필요하다), 객체를 해시 가능하다고 한다. 동일하다 고 판단되는 객체는 반드시 해시값이 동일해야 한다. [후략]

원자적 불변형(str, byte, 수치형)은 모두 해시 가능하다. frozenset은 언제나 해시 가능하다. 모든 요소가 해시 가능하도록 정의되어 있기 때문이다. 튜플은 들어 있는 항목들이 모두 해시 가능해야 해시 가능하다. 다음 코드에서 tt, tl, tf 튜플을 보자. tt = (1, 2, (30, 40)) hash(tt) 8027212646858338501 >>> tl = (1, 2, [30, 40]) >>> hash(tl) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' >>> tf = (1, 2, frozenset([30, 40])) >>> hash(tf) -4118419923444501110 >>> >>>

CAUTION_ 이 책을 쓰고 있는 현재 파이썬 용어집(http://bit.ly/1K4qjwE )에서는 ‘파이썬이 제공

하는 불변 내장 객체는 모두 해시 가능하다’고 설명하지만, 이 설명은 정확하지 않다. 튜플은 불변형 이긴 하지만, 해시 불가능한 객체를 참조할 수 있기 때문이다.

사용자 정의 자료형은 기본적으로 해시 가능하다. 기본적으로 객체의 해시값은 id()를 이용해서 구하므로 모든 객체가 서로 다르기 때문이다. 객체가 자신의 내부 상태를 평가해서 __eq__() 메 서드를 직접 구현하는 경우에는 해시값 계산에 사용되는 속성이 모두 불변형일 때만 해시 가능 하다.

3장 딕셔너리와 집합 111


기본적인 규칙을 익혔으니, 여러분은 다양한 방식으로 딕셔너리를 구현할 수 있다. 파이썬 라이 브러리 참조문서 사이트의 ‘내장된 자료형Built-in Types ’ 페이지( http://bit.ly/1QS9Ong )에는 dict를 구현하는 다양한 방법을 보여주는 다음과 같은 예제를 담고 있다.

a b >>> c >>> d >>> e >>> a True

>>>

>>>

= dict(one=1, two=2, three=3) = {'one': 1, 'two': 2, 'three': 3} = dict(zip(['one', 'two', 'three'], [1, 2, 3])) = dict([('two', 2), ('one', 1), ('three', 3)]) = dict({'three': 3, 'one': 1, 'two': 2}) == b == c == d == e

리터럴 구문이나 dict ( ) 생성자 외에도, 지능형 딕셔너리dict comprehensions 를 이용해서 딕셔너리 객체를 만들 수 있다. 다음 절로 넘어가자.

3.2 지능형 딕셔너리 파이썬 2.7부터는 지능형 리스트listcomp 와 제너레이터 표현식genexps 구문이 지능형 딕셔너리dict comprehension

에도 적용된다(지능형 집합도 마찬가지다. 이에 대해서는 잠시 후에 설명한다). 지

능형 딕셔너리dictcomp 는 모든 반복형 객체에서 키-값 쌍을 생성함으로써 딕셔너리 객체를 만들 수 있다. [예제 3-1]은 지능형 딕셔너리를 이용해서 동일한 튜플 리스트에서 딕셔너리 두 개를 생성하는 과정을 보여준다. 예제 3-1 지능형 딕셔너리 예제 >>>

... ... ... ... ... ... ... ... ...

DIAL_CODES = [ ➊ (86, 'China'), (91, 'India'), (1, 'United States'), (62, 'Indonesia'), (55, 'Brazil'), (92, 'Pakistan'), (880, 'Bangladesh'), (234, 'Nigeria'), (7, 'Russia'),

112 2부 데이터 구조체


(81, 'Japan'), ] >>> country_code = {country: code for code, country in DIAL_CODES} ➋ >>> country_code {'China': 86, 'India': 91, 'Bangladesh': 880, 'United States': 1, 'Pakistan': 92, 'Japan': 81, 'Russia': 7, 'Brazil': 55, 'Nigeria': 234, 'Indonesia': 62} >>> {code: country.upper() for country, code in country_code.items() ➌ ... if code < 66} {1: 'UNITED STATES', 55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA'} ... ...

➊ dict 생성자에 키-값 쌍의 리스트를 바로 사용할 수 있다. ➋ 쌍을 뒤바꿔서 country는 키, code는 값이 된다. ➌ 쌍을 한 번 더 뒤바꿔서 값을 대문자로 바꾸고, code가 66보다 작은 항목만 걸러낸다.

listcomp에 익숙해지면 자연스럽게 dictcomp에도 익숙해진다. 이제부터 listcomp를 확장 한 구문이 많이 나오므로, 지금이라도 listcomp를 익혀두는 것이 좋다. 이제 매핑이 제공하는 API를 전체적으로 둘러보자.

3.3 공통적인 매핑 메서드 매핑이 제공하는 기본 API는 아주 많다. [표 3-1]은 dict와 dict의 변형 중 가장 널리 사용되 는 defaultdict와 OrderedDict 클래스(이 두 클래스는 모두 collections 모듈에 정의되어 있다)가 구현하는 메서드를 보여준다.12 표 3-1 dict, collections.defaultdict, collections.OrderedDict 매핑형의 메서드. 표를 짧게 유지하기 위해 object 클래스가 정의하는 메서드는 생략했으며, 선택적 인수는 대괄호로 표시했다. 메서드

dict

defaultdict

OrderedDict 설명

d.clear()

모든 항목을 제거한다.

d.__contains__(k)

k in d

d.copy()

얕게 복사한다.

d.__copy__()

copy.copy()를 지원한다.

3장 딕셔너리와 집합 113


메서드

dict

d.default_factory

defaultdict

OrderedDict 설명

빠진 값을 설정하기 위해

__missing__() 메서드에 호출되는 콜러블1

d.__delitem__(k)

del d[k] — 키가 k인 항목을 제거한 다.

d.fromkeys(it, [initial])

선택적인 초깃값(기본값은 None )을 받아, 반복 가능한 객체의 키들을 이용 해서 새로 매핑한다.

d.get(k, [default])

k 키를 가진 항목을 반환한다. 해당 항 목이 없으면 default나 None을 반

d.__getitem__(k)

d[k] — k 키를 가진 항목을 반환한다.

d.items()

(키, 값) 쌍으로 구성된 항목들의 뷰를

d.__iter__()

키에 대한 반복자를 가져온다.

d.keys()

키에 대한 뷰를 가져온다.

d.__len__()

len(d) — 항목 수를 반환한다.

환한다.

가져온다.

d.__missing__(k)

__getitem__()이 k 키를 찾을 수 없을 때 호출된다.

d.move_to_end(k, [last])

앞이나 뒤에서 k 개의 항목을 이동한 다(last의 기본값은 True다).

d.pop(k, [default])

k 키 항목을 제거하고 반환한다. 항목 이 없으면 default나 None을 반환

d.popitem()

처음이나 마지막 (키, 값) 항목을 제거

한다. 하고 반환한다.2

d.__reversed__()

키에 대한 역순 반복자를 가져온다.

d.setdefault(k, [default]) ●

k in d가 참이면 d[k]를 반환하고, 아니면 d[k] = default로 설정하고

d.__setitem__(k, v)

이 값을 반환한다. ●

d[k] = v — k 키를 가진 항목의 값 을 v로 설정한다.

d.update(m, [**kargs])

(키, 값) 쌍의 매핑이나 반복형 객체에 서 가져온 항목들로 d를 갱신한다.

d.values()

값들에 대한 뷰를 가져온다.

1 default_factory는 메서드가 아니며, defaultdict가 초기화될 때 사용자가 설정한 콜러블 객체 속성이다. 2 OrderedDict.popitem()은 최근에 삽입된 항목을 제거한다(LIFO). 선택적 인수 last를 False로 설정하면 처음에 삽입된 항목을 제거한다(FIFO).

114 2부 데이터 구조체


update ( ) 메서드가 첫 번째 인수 m을 다루는 방식은 덕 타이핑duck typing 의 대표적인 사례다.

먼저 m이 keys ( ) 메서드를 갖고 있는지 확인한 후, 만약 메서드를 갖고 있으면 매핑이라고 간 주한다. keys ( ) 메서드가 없으면, update ( ) 메서드는 m의 항목들이 (키, 값) 쌍으로 되어 있 다고 간주하고 m을 반복한다. 대부분의 파이썬 매핑은 update ( ) 메서드와 같은 논리를 내부 적으로 구현한다. 따라서 매핑은 다른 매핑으로 초기화하거나, (키, 값) 쌍을 생성할 수 있는 반복형 객체로 초기화할 수 있다. 매핑의 setdefault ( ) 메서드는 신비롭다. 이 메서드가 늘 필요한 것은 아니지만, 이 메서드 가 필요할 때는 똑같은 키를 여러 번 조회하지 않게 해줌으로써 속도를 엄청나게 향상시킨다. 다음 절에서는 예제를 통해 setdefault ( ) 메서드의 사용법을 설명한다.

3.3.1 존재하지 않는 키를 setdefault( )로 처리하기 조기 실패fail-fast 철학에 따라, 존재하지 않는 키 k로 d[k]를 접근하면 dict는 오류를 발생시킨다. KeyError를 처리하는 것보다 기본값을 사용하는 것이 더 편리한 경우에는 d[k] 대신 d.get (k, default )를 사용한다는 것은 파이썬 개발자라면 누구나 알고 있다. 그렇지만 발견한 값을 갱신

할 때, 해당 객체가 가변 객체면 __getitem__( )이나 get ( ) 메서드는 보기 어색하며, 효율성 도 떨어진다. [예제 3-2]는 잘 만든 코드는 아니지만, 존재하지 않는 키를 처리할 때 dict.get ( ) 이 좋지 않은 사례를 보여준다. [예제 3-2]는 알렉스 마르텔리3의 예제에서 발췌한 것으로서, 실행하면 [예제 3-3]과 같은 인덱 스를 생성한다. 예제 3-2 dict.get( )을 이용해서 인덱스에서 발생한 단어 목록을 가져와서 갱신하는 index0.py. [예제 3-4]에 더 나은 해결책이 있다.

"""단어가 나타나는 위치를 가리키는 인덱스를 만든다.""" import sys import re WORD_RE = re.compile(r'\w+') 3 원래 코드는 마르텔리의 ‘파이썬 다시 배우기(Re -learning Python)’ 발표 자료( http://bit.ly/1QmmPFj )의 41번 슬라이드에 나온다. 실제 이 스크립트는 [예제 3-4]에 나온 dict.setdefault()를 보여주고 있다.

3장 딕셔너리와 집합 115


index = {} with open(sys.argv[1], encoding='utf-8') as fp: for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() column_no = match.start()+1 location = (line_no, column_no) # 보기 좋은 코드는 아니지만, 설명하기 위해 이렇게 구현했다. occurrences = index.get(word, []) ➊ occurrences.append(location) ➋ index[word] = occurrences ➌ # 알파벳순으로 출력한다. for word in sorted(index, key=str.upper): print(word, index[word])

➊ 단어(word )에 대한 occurrences 리스트를 가져오거나, 단어가 없으면 빈 배열([])을 가져온다. ➋ 새로 만든 location을 occurrences에 추가한다. ➌ 변경된 occurrences를 index 딕셔너리에 넣는다. 그러면 index를 한 번 더 검색한다. ➍ sorted ( ) 함수의 key 인수 안에서 str.upper ( )를 호출하지 않고, 단지 str.upper ( ) 함수에 대한

참조를 전달해서 sorted ( ) 함수가 이 함수를 이용해서 정렬할 단어를 정규화게 만든다.4

예제 3-3 파이썬의 선을 처리하는 [예제 3-2]의 출력 일부. 각 단어 다음에는 단어가 나타난 위치를 (행 번호, 열 번호) 쌍으로 보여준다.

$ python3 index0.py ../../data/zen.txt a [(19, 48), (20, 53)] Although [(11, 1), (16, 1), (18, 1)] ambiguity [(14, 16)] and [(15, 23)] are [(21, 12)] aren [(10, 15)] at [(16, 38)] bad [(19, 50)] be [(15, 14), (16, 27), (20, 50)] beats [(11, 23)] Beautiful [(3, 1)]

4 이것은 메서드를 일급 함수로 사용하는 하나의 예며, 일급 함수는 5장에서 다룬다.

116 2부 데이터 구조체


better [(3, 14), (4, 13), (5, 11), (6, 12), (7, 9), (8, 11), (17, 8), (18, 25)] ...

[예제 3-2]에서 단어 발생(occurrences )을 처리하는 코드 세 줄은 dict.setdefault ( )를 사 용하면 한 줄로 바꿀 수 있다. [예제 3-4]는 알렉스 마르텔리의 원래 예제에 더 가까운 코드다. 예제 3-4 인덱스에서 발생한 단어 목록을 가져와서 갱신하는 index.py. dict.setdefault( )를 사용해서 단 한 줄로 구현 했다.

"""단어가 나타나는 위치를 가리키는 인덱스를 만든다.""" import sys import re WORD_RE = re.compile(r'\w+') index = {} with open(sys.argv[1], encoding='utf-8') as fp: for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() column_no = match.start()+1 location = (line_no, column_no) index.setdefault(word, []).append(location) ➊ # 알파벳순으로 출력한다. for word in sorted(index, key=str.upper): print(word, index[word])

➊ 단어에 대한 occurrences 리스트를 가져오거나, 단어가 없을 때는 빈 배열([])을 가져온다. setdefault ( )

가 값을 반환하므로 한 번 더 검색할 필요 없이 갱신할 수 있다.

변경된 부분을 보자. my_dict.setdefault(key, []).append(new_value)

위 코드를 실행한 결과는 다음 코드를 실행한 결과와 동일하다.

3장 딕셔너리와 집합 117


if key not in my_dict: my_dict[key] = [] my_dict[key].append(new_value)

다만 위 코드는 키를 두 번 검색하는 반면(단어가 없을 때는 세 번 검색한다), setdefault ( ) 코드는 단 한 번만 검색해서 이 모든 과정을 수행한다. 이와 관련해서 항목을 삽입할 때뿐만 아니라 어떤 방식으로든 조회할 때, 키가 없는 경우를 처 리하는 문제는 다음 절에서 설명한다.

3.4 융통성 있게 키를 조회하는 매핑 검색할 때 키가 존재하지 않으면 어떤 특별한 값을 반환하는 매핑이 있으면 편리한 때가 종종 있 다. 이런 딕셔너리를 만드는 방법은 크게 두 가지다. 하나는 평범한 dict 대신 defaultdict를 사용하는 방법이고, 다른 하나는 dict 등의 매핑형을 상속해서 __missing__( ) 메서드를 추가 하는 방법이다. 이 두 방법에 대해 알아보자.

3.4.1 defaultdict : 존재하지 않는 키에 대한 또 다른 처리 [예제 3-5]는 collections.defaultdict를 이용해서 [예제 3-4]의 문제를 멋지게 해결하는 또 다른 방법이다. defaultdict는 존재하지 않는 키로 검색할 때 요청에 따라 항목을 생성하 도록 설정되어 있다. 작동하는 방식은, defaultdict 객체를 생성할 때 존재하지 않는 키 인수로 __getitem__( ) 메서드를 호출할 때마다 기본값을 생성하기 위해 사용되는 콜러블을 제공하는 것이다. 예를 들어 dd = defaultdict (list ) 코드로 기본 defaultdict 객체를 생성한 후, dd에 존재 하지 않는 키인 'new-key'로 dd['new-key'] 표현식을 실행하면 다음과 같이 처리된다. 1 리스트를 새로 생성하기 위해 list ( )를 호출한다. 2 'new -key'를 키로 사용해서 새로운 리스트를 dd에 삽입한다. 3 리스트에 대한 참조를 반환한다.

118 2부 데이터 구조체


기본값을 생성하는 콜러블은 default_factory라는 객체 속성에 저장된다. 예제 3-5 index_default.py : setdefault( ) 메서드 대신 defaultdict 객체 사용하기

"""단어가 나타나는 위치를 가리키는 인덱스를 만든다.""" import sys import re import collections WORD_RE = re.compile(r'\w+') index = collections.defaultdict(list) ➊ with open(sys.argv[1], encoding='utf-8') as fp: for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() column_no = match.start()+1 location = (line_no, column_no) index[word].append(location) ➋ # 알파벳순으로 출력한다. for word in sorted(index, key=str.upper): print(word, index[word])

➊ default_factory에 list 생성자를 갖고 있는 defaultdict를 생성한다. ➋ word가 index에 들어 있지 않으면 default_factory를 호출해서 없는 값에 대한 항목을 생성하는데, 여

기서는 빈 리스트를 생성해서 index[word]에 할당한 후 반환하므로, append (location ) 연산은 언제 나 성공한다.

default_factory가 설정되어 있지 않으면, 키가 없을 때 흔히 볼 수 있는 KeyError가 발생한다. CAUTION_ defaultdict의 default_factory는 __getitem__() 호출에 대한 기본값을 제공하기 위

해 호출되며, 다른 메서드를 통해서는 호출되지 않는다. 예를 들어 dd가 defaultdict 형이며, k가 존재하지 않는 키면, dd[k]는 default_factory를 호출해서 기본값을 생성하지만, dd.get(k)는 단지 None을 반환 할 뿐이다.

실제 defaultdict가 default_factory를 호출하게 만드는 메커니즘은 __missing__( ) 특수 메서드에 의존하며, 다음 절에서 설명하는 것처럼 표준 매핑형은 모두 이 기능을 지원한다.

3장 딕셔너리와 집합 119


3.4.2 __missing__( ) 메서드 매핑형은 이름으로도 쉽게 추측할 수 있는 __missing__( ) 메서드를 이용해서 존재하지 않 는 키를 처리한다. 이 특수 메서드는 기본 클래스인 dict에는 정의되어 있지 않지만, dict는 이 메서드를 알고 있다. 따라서 dict 클래스를 상속하고 __missing__( ) 메서드를 정의하면, dict.__getitem__( ) 표준 메서드가 키를 발견할 수 없을 때 KeyError를 발생시키지 않고 __missing__( ) 메서드를 호출한다. CAUTION_ __missing__() 메서드는 d[k] 연산자를 사용하는 경우 등 __getitem__() 메서드를 사

용할 때만 호출된다. in 연산자를 구현하는 get()이나 __contains__() 메서드 등 키를 검색하는 다 른 메서드에는 __missing__() 메서드가 영향을 미치지 않는다. 그렇기 때문에 3 .4 .1 절 마지막 부분 의 ‘CAUTION’ 글상자에서 설명한 것처럼 __getitem__() 메서드를 사용할 때만 defaultdict의 default_factory가 작동한다.

검색할 때 키를 str 형으로 변환하는 매핑을 생각해보자. Pingo.io (http://www.pingo.io/

docs/) 프로젝트 등에서 이런 매핑을 사용한다. Pingo.io 프로젝트는 범용입출력(GPIO ) 핀 을 가진 프로그래밍 가능한 보드인 라즈베리 파이나 아두이노에서 물리적인 핀을 board.pins 속성에 담고 있는 board 객체로 표현한다. board.pins는 물리적인 핀 위치와 pin 객체의 매 핑으로서, 물리적인 위치는 단지 숫자거나 혹은 ‘A0’나 ‘P9_12’ 등의 문자열이다. 일관성을 위해 board.pins의 모든 키를 문자열로 표현하는 것이 바람직하지만, 아두이노의 13번 핀에 연결된

LED를 깜박거리게 하고자 할 때 초보자들이 실수하지 않도록 my_arduino.pins[13]처럼 조회 할 수 있게 해주면 편리하다. [예제 3-6]은 키를 문자열로 변환하는 매핑이 어떻게 작동하는지 보여준다. 예제 3-6 비문자열 키를 검색할 때 키를 발견하지 못하면 키를 문자열로 변환하는 StrKeyDict0

`d[key]` 표기법을 이용해서 항목을 가져오는 테스트:: d = StrKeyDict0([('2', 'two'), ('4', 'four')]) d['2'] 'two' >>> d[4] 'four' >>> d[1] Traceback (most recent call last): >>> >>>

120 2부 데이터 구조체


... KeyError: '1' `d.get(key)` 표기법을 이용해서 항목을 가져오는 테스트:: d.get('2') 'two' >>> d.get(4) 'four' >>> d.get(1, 'N/A') 'N/A' >>>

`in` 연산자 테스트:: 2 in d

>>>

True 1 in d False

>>>

[예제 3-7]에서는 [예제 3-6]의 테스트를 통과하는 StrKeyDict0 클래스를 구현한다. NOTE_ 사용자 정의 매핑형을 만들 때는 dict보다 collections.UserDict 클래스를 상속하는 것이 더

낫다(예제 3-8). 여기서는 dict.__getitem__() 내장 메서드가 __missing__() 메서드를 지원한다는 것 을 보여주기 위해 dict 클래스를 상속한다.

예제 3-7 조회할 때 키를 문자열로 변환하는 StrKeyDict0

class StrKeyDict0(dict):

def __missing__(self, key): if isinstance(key, str): ➋ raise KeyError(key) return self[str(key)] ➌ def get(self, key, default=None): try: return self[key] ➍ except KeyError: return default ➎ def __contains__(self, key): return key in self.keys() or str(key) in self.keys()

3장 딕셔너리와 집합 121


➊ StrKeyDict0이 dict를 상속한다. ➋ 키가 문자열인지 확인한다. 키가 문자열이고 존재하지 않으면 KeyError가 발생한다. ➌ 키에서 문자열을 만들고 조회한다. ➍ get ( ) 메서드는 self[key] 표기법을 이용해서 __getitem__( ) 메서드에 위임한다. 이렇게 함으로써

__missing__( ) 메서드가 작동할 수 있는 기회를 준다. ➎ KeyError가 발생하면 __missing__( ) 메서드가 이미 실패한 것이므로

default를 반환한다.

➏ 수정하지 않은 (문자열이 아닐 수 있는) 키를 검색하고 나서, 키에서 만든 문자열로 검색한다.

잠시 쉬면서 __missing__( ) 메서드 안에 isinstance (key, str ) 코드가 필요한 이유를 생각 해보자. 이렇게 검사하는 부분이 없다면, __missing__( ) 메서드는 문자열이든 비문자열이든 str (k ) 키가 존재할 때는 키 k에 대해 아무런 문제없이 작동한다. 그렇지만 str (k ) 키가 존재하 지 않으면 무한히 재귀적으로 호출된다. 마지막 줄의 self[str (key )]가 str 키를 이용해서 __getitem__( ) 메서드를 호출하고, 이때 키가 없으면 __missing__( ) 메서드를 다시 호출하

기 때문이다. 이 예제가 일관성 있게 동작하려면 __contains__( ) 메서드도 필요하다. k in d 연산이 __contains__( ) 메서드를 호출하지만, dict 에서 상속받은 __contains__( ) 메서드가 __missing__( )을 호출하지 않기 때문이다. 앞에서 구현한 __contains__( ) 메서드를 주의

해서 살펴볼 필요가 있다. 키를 검색할 때 일반적으로 파이썬스러운 스타일인 k in my_dict로 조회하지 않는다. str (key ) in self 표현식을 사용하면 재귀적으로 __contains__( ) 메서 드를 호출하기 때문이다. 재귀적 호출 문제를 피하기 위해 여기서는 key in self.keys ( )와 같이 명시적으로 키를 조회한다. NOTE_ 파이썬 3에서는 아주 큰 매핑의 경우에도 k in my_dict.keys() 형태의 검색이 효율적이다.

dict.keys()는 집합과 비슷한 뷰를 반환하는데, 집합에 포함되었는지 여부를 검사하는 것은 딕셔너리만큼 빠 르기 때문이다. 자세한 설명은 파이썬 문서의 ‘딕셔너리 뷰 객체’ 절(http://bit.ly/1Vm7E4q)을 참조하라. 파이 썬 2에서 dict.keys()는 리스트를 반환하므로, 여기에서 설명하는 코드가 작동은 하지만, k in my_list 연산이 리스트를 검색해야 하므로 딕셔너리가 커지면 효율이 떨어진다.

앞에 나온 코드는 key in self.keys ( )를 이용해서 수정하지 않은 키에 대한 검사를 해야 제대 로 작동한다. StrKeyDict0는 딕셔너리에 들어 있는 키가 모두 str 형이어야 한다고 요구하지

122 2부 데이터 구조체


않기 때문이다. 이 간단한 예제는 엄격하게 자료형에 따르도록 하는 게 아니라, ‘친절하게’ 검색 할 수 있게 해주는 방법을 보여주기 위한 것이다. 지금까지 dict와 defaultdict 매핑형에 대해 설명했지만, 표준 라이브러리에서는 다음 절에서 설명하는 매핑형도 제공한다.

3.5 그 외 매핑형 이 절에서는 defaultdict 이외에 표준 라이브러리의 collections 모듈에서 제공하는 여러 매핑형을 간단히 살펴본다.

collections.OrderedDict 키를 삽입한 순서대로 유지함으로써 항목을 반복하는 순서를 예측할 수 있다. OrderedDict의 popitem() 메서드는 기본적으로 최근에 삽입한 항목을 꺼내지만, my_odict.popitem(last=True)

형태로 호출하면 처음 삽입한 항목을 꺼낸다.

collections.ChainMap 매핑들의 목록을 담고 있으며 한꺼번에 모두 검색할 수 있다. 각 매핑을 차례대로 검색하고, 그중 하나에서라도 키가 검색되면 성공한다. 이 클래스는 내포된 범위를 지원하는 언어에서 각 범위를 하나의 매핑으로 표현함으로써 인터프리터를 구현하는 데 유용하게 사용할 수 있다. 파이썬 문서 의 collections 절에서 ‘ChainMap 객체’ 부분(http://bit.ly/1Vm7I4c:)을 보면 ChainMap을 사 용하는 여러 예제를 볼 수 있는데, 그중 다음 코드는 파이썬에서 변수를 조회하는 기본 규칙을 표 현하고 있다. import builtins pylookup = ChainMap(locals(), globals(), vars(builtins))

collections.Counter 모든 키에 정수형 카운터를 갖고 있는 매핑. 기존 키를 갱신하면 카운터가 늘어난다. 이 카운터 는 해시 가능한 객체(키)나 한 항목이 여러 번 들어갈 수 있는 다중 집합multiset에서 객체의 수를 세 기 위해 사용할 수 있다. Counter 클래스는 합계를 구하기 위한 +와 - 연산자를 구현하며, n개의 가장 널리 사용된 항목과 그들의 카운터로 구성된 튜플의 리스트를 반환하는 most_common([n]) 등의 메서드를 제공한다. 자세한 설명은 문서(http://bit.ly/1JHVi2E)를 참조하라. 다음 코드는

3장 딕셔너리와 집합 123


Counter를 이용해서 단어 안에서 각 글자의 횟수를 계산하는 예를 보여준다.

ct = collections.Counter('abracadabra') ct Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) >>> ct.update('aaaaazzz') >>> ct Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) >>> ct.most_common(2) [('a', 10), ('z', 3)] >>>

>>>

collections.UserDict 표준 dict처럼 작동하는 매핑을 순수 파이썬으로 구현한 클래스

OrderedDict, ChainMap, Counter 클래스는 바로 사용할 수 있지만, UserDict는 다음 절에서

설명하는 것처럼 상속해서 사용하도록 설계되어 있다.

3.6 UserDict 상속하기 dict보다는 UserDict를 상속해서 매핑형을 만드는 것이 쉽다. 매핑에 추가한 키를 문자열로

저장하기 위해 [예제 3-7]에서 StrKeyDict0을 확장했던 것처럼 UserDict의 값을 평가할 수 있다. 내장형에서는 아무런 문제없이 상속할 수 있는 메서드들을 오버라이드해야 하는 구현의 특이 성 때문에 dict보다는 UserDict를 상속하는 것이 낫다.5 UserDict는 dict를 상속하지 않고 내부에 실제 항목을 담고 있는 data라고 하는 dict 객체를

갖고 있다. 이렇게 구현함으로써 __setitem__( ) 등의 특수 메서드를 구현할 때 발생하는 원치 않는 재귀적 호출을 피할 수 있으며, __contains__( ) 메서드를 간단히 구현할 수 있다. [예제

3-7]과 비교해보자. UserDict 덕분에 StrKeyDict (예제 3-8 )는 StrKeyDict0 (예제 3-7 )보다 간단하지만 더 많 5 dict 등의 내장 클래스를 상속할 때 발생하는 문제는 12.1절 ‘내장 자료형의 상속은 까다롭다’에서 구체적으로 설명한다.

124 2부 데이터 구조체


은 일을 하도록 구현할 수 있다. StrKeyDict는 모든 키를 str 형으로 저장함으로써 비문자열 키로 객체를 생성하거나 갱신할 때 발생할 수 있는 예기치 못한 문제를 피하게 해준다. 예제 3-8 삽입, 갱신, 조회할 때 비문자열 키를 항상 문자열로 변환하는 StrKeyDict

import collections class StrKeyDict(collections.UserDict):

def __missing__(self, key): ➋ if isinstance(key, str): raise KeyError(key) return self[str(key)] def __contains__(self, key): return str(key) in self.data

def __setitem__(self, key, item): self.data[str(key)] = item ➍

➊ StrKeyDict는 UserDict를 상속한다. ➋ __missing__( ) 메서드는 [예제

3 -7]과 완전히 똑같다.

➌ _ _contains__( ) 메서드는 더 간단하다. 저장된 키가 모두 str 형이므로 StrKeyDict0에서 self.

keys ( )를 호출하는 방법과 달리 self.data에서 바로 조회할 수 있다. ➍ __setitem__( ) 메서드는 모든 키를 str 형으로 변환하므로, 연산을 self.data에 위임할 때 더 간단히

작성할 수 있다.

UserDict 클래스가 MutableMapping 을 상속하므로 StrKeyDict 는 결국 UserDict, MutableMapping, 또는 Mapping을 상속하게 되어 매핑의 모든 기능을 가지게 된다. Mapping

은 추상 베이스 클래스(ABC )임에도 불구하고 유용한 구상concrete (구체적인) 메서드를 다수 제 공한다. 특히 다음과 같은 메서드는 상당히 유용하다.

MutableMapping.update( ) 이 강력한 메서드는 직접 호출할 수도 있지만, 다른 매핑이나 (키, 값) 쌍의 반복형 및 키워드 인수 에서 객체를 로딩하기 위해 __init__()에 의해 사용될 수도 있다. 이 메서드는 항목을 추가하기 위해 ‘self[키] = 값’ 구문을 사용하므로 결국 서브클래스에서 구현한 __setitem__() 메서드를 호 출하게 된다.

3장 딕셔너리와 집합 125


Mapping.get( ) StrKeyDict0(예제 3-7)에서는 __getitem__()과 일치하는 결과를 가져오기 위해 get() 메서

드를 직접 구현해야 했지만, [예제 3-8]에서는 StrKeyDict0.get()과 완전히 동일하게 구현된 Mapping.get()을 상속받는다. 파이썬 소스 코드(http://bit.ly/1FEOPPB)를 참조하라.

TIP

StrKeyDict 클래스를 작성한 후 필자는 안토인 피트루Antoine Pitrou 가 작성한 ‘PEP 455 - 컬렉션에 키-변 환 딕셔너리의 추가Adding a key-transforming dictionary to collections ’( https://www.python.org/dev/peps/pep-

0455/) 및 TransformDict로 개선한 collections 모듈 패치를 발견했다. 이 패치는 issue18986 문서 ( http://bugs.python.org/issue18986)에 첨부되었으며, 파이썬 3.5에 포함될 것이다. TransformDict 로 실험해보기 위해 필자는 이 책 예제 코드( https://github.com/fluentpython/example-code)의 표 준 모듈( 03-dict-set/transformdict.py, http://bit.ly/1Vm7OJ5)에 추출해 넣었다. TransformDict는 StrKeyDict보다 범용성이 뛰어나며 원래 삽입한 키를 유지하느라 코드가 조금 더 복잡하다.

불변 시퀀스형이 여러 종류 있는 것은 알고 있을 것이다. 그렇다면 불변 딕셔너리는 어떨까? 표준 라이브러리에는 실제로 불변 딕셔너리가 들어 있지 않지만, 추가할 수는 있다. 다음 절에 서 불변 매핑에 대해 알아보자.

3.7 불변 매핑 표준 라이브러리에서 제공하는 매핑형은 모두 가변형이지만, 사용자가 실수로 매핑을 변경하지 못하도록 보장하고 싶은 경우가 있을 것이다. 3.4.2절 ‘__missing__( ) 메서드’에서 Pingo.io 프로젝트를 설명할 때 물리적인 범용입출력(GPIO ) 핀을 나타내는 board.pins라는 불변형 매핑을 사용하는 구체적인 사례를 볼 수 있다. 하드웨어를 소프트웨어로 변경할 수 없기 때문 에 물리적인 디바이스 특성을 반영해서 사용자가 실수로 board.pins를 변경하지 못하도록 막 는 것이 좋다. 파이썬 3.3 이후 types 모듈은 MappingProxyType이라는 래퍼 클래스를 제공해서, 원래 매핑 의 동적인 뷰를 제공하지만 읽기 전용의 mappingproxy 객체를 반환한다. 따라서 원래 매핑을 변경하면 mappingproxy에 반영되지만, mappingproxy를 직접 변경할 수는 없다. [예제 3-9] 는 mappingproxy를 사용하는 방법을 간략히 보여준다.

126 2부 데이터 구조체


예제 3-9 dict에서 읽기 전용 mappingproxy 객체를 생성하는 MappingProxyType

from types import MappingProxyType d = {1: 'A'} >>> d_proxy = MappingProxyType(d) >>> d_proxy mappingproxy({1: 'A'}) >>> d_proxy[1] ➊ 'A' >>> d_proxy[2] = 'x' ➋ Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'mappingproxy' object does not support item assignment >>> d[2] = 'B' >>> d_proxy ➌ mappingproxy({1: 'A', 2: 'B'}) >>> d_proxy[2] 'B' >>> >>>

➊ d에 들어 있는 항목은 d_proxy를 통해서 볼 수 있다. ➋ d_proxy를 변경할 수는 없다. ➌ 동적인 d_proxy는 d에 대한 변경을 바로 반영한다.

Pingo.io에서 Board 서브클래스의 생성자가 pin 객체로 내부 매핑을 채우고, mappingproxy 로 구현한 pins 속성을 공개해서 API 사용자에게 제공함으로써 불변형 매핑을 사용할 수도 있 다. 이렇게 하면 클라이언트가 실수로 핀을 추가, 삭제, 변경할 수 없게 된다.6 지금까지 표준 라이브러리에서 제공하는 대부분의 매핑형 및 각 매핑형을 사용하는 경우에 대 해 살펴보았으므로, 이제 집합형에 대해 알아보자.

3.8 집합 이론 파이썬에서 집합은 비교적 최근에 추가되었으며 그리 많이 사용되지는 않는다. set 형과 set의

6 사실 Pingo.io에서는 MappingProxyType을 사용하고 있지 않다. MappingProxyType은 파이썬 3.3에 추가되었지만, 당시 Pingo. io는 파이썬 2.7을 지원해야 했기 때문이다.

3장 딕셔너리와 집합 127


불변형 버전인 frozenset은 파이썬 2.3에 모듈로 처음 등장했으며, 파이썬 2.6에서 내장형으 로 승격되었다. NOTE_ 이 책에서 ‘집합’이라는 용어는 set과 frozenset을 모두 가리킨다. set 클래스를 구체적으로 명

시할 때는 코드에 사용된 폰트를 사용해서 set이라고 표기한다.

집합은 고유한 객체의 모음으로서, 기본적으로 중복 항목을 제거한다. l = ['spam', 'spam', 'eggs', 'spam'] set(l) {'eggs', 'spam'} >>> list(set(l)) ['eggs', 'spam'] >>> >>>

집합 요소는 반드시 해시할 수 있어야 한다. set은 해시 가능하지 않지만 frozenset은 해시 가 능하므로, frozenset이 set에 들어갈 수 있다. 고유함을 보장하는 것 외에 집합형은 중위 연산자를 이용해서 기본적인 집합 연산을 구현한다. 따라서 두 개의 집합 a와 b가 있을 때, a | b는 합집합, a & b는 교집합, a - b는 차집합을 계 산한다. 집합 연산을 현명하게 이용하면 파이썬 프로그램의 소스 코드 크기와 실행 시간을 줄 일 수 있을 뿐 아니라, 루프나 조건절이 없어지므로 코드의 가독성이 높아진다. 예를 들어 이메일 주소가 들어 있는 큰 집합(haystack )과 몇 가지 이메일 주소가 들어 있는 작은 집합(needles )이 있고 needles에 들어 있는 이메일 중 몇 개가 haystack 안에도 들어 있는지 알고 싶다고 가정하자. 교집합(& ) 연산자를 이용하면 [예제 3-10]과 같이 간단하게 코 딩할 수 있다. 예제 3-10 둘 다 집합형인 haystack 안에 들어 있는 needles 항목 수 구하기

found = len(needles & haystack)

교집합 연산자를 사용하지 않으면 [예제 3-10]과 동일한 작업을 수행하기 위해 [예제 3-11]과 같이 구현해야 한다.

128 2부 데이터 구조체


예제 3-11 haystack 안에서 needles의 발생 횟수 구하기

found = 0 for n in needles: if n in haystack: found += 1

[예제 3-10]은 [예제 3-11]보다 실행 속도가 약간 더 빠르다. [예제 3-10]의 코드는 두 객체가 모두 집합이어야 하는 반면 [예제 3-11]은 needles와 haystack이 반복 가능형이면 어느 객체 든 사용할 수 있다. 그러나 객체가 집합형이 아니더라도 [예제 3-12]와 같이 즉석에서 집합으로 만들 수 있다. 예제 3-12 haystack 안에서 needles의 발생 횟수 구하기

found = len(set(needles) & set(haystack)) # 또 다른 방법: found = len(set(needles).intersection(haystack))

물론 [예제 3-12]에서는 집합을 만드는 추가 비용이 있지만, needles나 haystack 중 하나라 도 이미 집합형이라면 [예제 3-12]의 방법이 [예제 3-11]보다 빨리 실행될 것이다. 앞에서 설명한 예제 모두 10,000,000개의 항목을 가진 haystack 안에서 1,000개의 항목을 3 밀리초 안에 검색할 수 있다. 즉, 항목 하나를 검색하는 데 3마이크로초 정도 걸린다. 내부의 해시 테이블 덕분에 집합 안에 속해 있는지 여부를 아주 빨리 검색할 수 있는 것 외에도 set과 frozenset 내장 자료형은 새로운 집합을 생성하거나, set의 경우 기존 항목을 변경하는

다양한 연산을 제공한다. 잠시 후에 집합 연산에 대해 설명하겠지만, 먼저 구문을 간단히 살펴 보자.

3.8.1 집합 리터럴 {1}, {1, 2} 등 집합 리터럴에 대한 구문은 수학적 표기법과 동일하지만, 공집합은 리터럴로 표

기할 수 없으므로, 반드시 set ( )으로 표기해야 한다.

3장 딕셔너리와 집합 129


CAUTION_ 변덕스러운 구문법

공집합을 생성할 때는 인수 없이 생성자를 호출하는 set() 구문을 사용해야 한다. {} 구문을 사용하면 빈 딕 셔너리가 생성되므로 주의해야 한다.

파이썬 3에서는 공집합 이외의 집합을 표준 문자열로 표현하기 위해 언제나 {} 구문을 사용한다. s = {1} type(s) <class 'set'> >>> s {1} >>> s.pop() 1 >>> s set() >>> >>>

{1, 2, 3}과 같은 리터럴 집합 구문은 set ([1, 2, 3])처럼 생성자를 호출하는 것보다 더 빠르

고 가독성이 좋다. 생성자를 명시적으로 호출하는 경우에는 파이썬이 생성자를 가져오기 위해 집합명을 검색하고, 리스트를 생성하고, 이 리스트를 생성자에 전달해야 하므로 더 느리다. 반 면 {1, 2, 3}과 같은 리터럴 집합 구문을 처리하는 경우, 파이썬은 BUILD_SET이라는 특수 바 이트코드를 실행한다.

디스어셈블러 함수인 dis.dis()를 이용해서 두 개의 연산에 대한 바이트코드를 살펴보자.

from dis import dis dis('{1}') 1 0 LOAD_CONST 3 BUILD_SET 6 RETURN_VALUE >>> dis('set([1])') 1 0 LOAD_NAME 3 LOAD_CONST 6 BUILD_LIST 9 CALL_FUNCTION 12 RETURN_VALUE >>>

>>>

130 2부 데이터 구조체

0 (1) 1

➋ ➌

0 (set) ➍ 0 (1) 1 1 (1 positional, 0 keyword pair)


CHAPTER

8

객체 참조, 가변성, 재활용

“슬픔에 잠겨 있구나.” 기사는 걱정된다는 듯이 말했다. “너를 달래줄 노래를 하나 해줄게. [중략] 노래 제목은 ‘대구의 눈Haddocks’ Eyes ’이라고 불린단다.” “오, 노래 제목이 그거예요, 네?” 흥미를 느끼려고 하면서 앨리스가 말했다. “아니, 이해를 못하는구나.” 다소 귀찮은 듯이 기사가 말했다. “사람들이 제목을 그렇게 부른다는 거야. 실제 제목은 ‘나이든 나이든 사람’이야.” (8장 ‘그건 내가 발명한 거야’에서 발췌) _ 루이스 캐럴Lewis Carroll 『거울 나라의 앨리스』

이 장에서 설명할 내용의 어조는 앨리스와 기사 간의 대화와 비슷하다. 주제는 객체와 그들의 이름 사이의 구분이다. 이름은 객체가 아니다. 이름은 별도의 것이다. 이 장에서는 먼저 파이썬 변수를 은유적으로 표현한다. 변수는 이름표지, 상자 자체가 아니다. 참조 변수를 이미 알고 있다면, 다른 사람에게 별명alias 문제를 설명할 때 이 비유가 도움이 될 것이다. 그러고 나서 객체의 정체성, 동질성, 별명의 개념을 이야기할 것이다. 튜플의 놀라운 성질도 드 러난다. 튜플은 불변형이지만, 그 안에 들어 있는 값은 바뀔 수 있다. 그리고 얕은 복사와 깊은 복사에 대해 설명한다. 그 다음 주제는 참조 및 함수 매개변수다. 가변 매개변수가 기본이 될 때의 문제 및 함수 호출자가 전달한 가변 인수의 안전한 처리에 대해 이야기한다. 이 장 마지막에서는 가비지 컬렉션과 del 명령 및 객체를 보존하지 않으면서 객체를 ‘기억’하기 위해 약한 참조를 사용하는 방법에 대해 설명한다.

291


이 장에서 설명하는 내용은 다소 무미건조하지만, 이 주제는 실제 파이썬 프로그램에서 발생하 는 여러 미묘한 버그의 핵심 원인이기도 하다. 먼저 변수가 데이터를 저장하는 일종의 상자와 같다고 배웠던 것은 잊어버리자.

8.1 변수는 상자가 아니다 1997년 MIT에서 자바 여름 강좌를 수강했다. 수상 경력이 빛나는 컴퓨터 과학 교육자이자 현 재 프랭클린 W. 올린 공과대학교Franklin W. Olin College of Engineering 에서 강의하고 있는 린 안드레 아 스타인Lynn Andrea Stein 교수는 흔히 비유하는 ‘상자로서의 변수’ 개념이 실제로는 객체지향 언 어에서 참조 변수를 이해하는 데 방해가 된다고 강조했다. 파이썬 변수는 자바에서의 참조 변 수와 같으므로 변수는 객체에 붙은 레이블이라고 생각하는 것이 좋다. [예제 8-1]은 ‘상자로서의 변수’ 개념이 설명할 수 없는 간단한 코드다. [그림 8-1]을 보면 파이 썬에서 상자 비유는 잘못된 반면, 포스트잇은 변수가 실제 작동하는 방식을 잘 보여주고 있다. 예제 8-1 사본이 아니라 동일한 리스트를 참조하는 변수 a와 b

a = [1, 2, 3] b = a >>> a.append(4) >>> b [1, 2, 3, 4] >>> >>>

그림 8-1 변수를 상자로 생각하면 파이썬에서 인수 할당을 이해할 수 없다. 그러나 변수를 포스트잇으로 생각하면 [예제

8-1]을 설명하기 쉽다.

292 4부 객체지향 상용구


스타인 교수는 할당에 대해서도 매우 신중하게 설명했다. 예를 들어 시뮬레이션에서 시소 객체 에 대해 얘기할 때 ‘변수 s가 시소에 할당되었다’고 했지 ‘시소가 변수 s에 할당되었다’고 하지 않았다. 참조 변수의 경우 변수가 객체에 할당되었다는 표현이 객체를 변수에 할당했다는 표현 보다 훨씬 타당하다. 결국 객체는 변수가 할당되기 전에 생성된다. [예제 8-2]는 할당문의 오른 쪽이 먼저 실행된다는 것을 입증한다. 예제 8-2 객체가 생성된 후에야 변수가 객체에 할당된다. >>>

... ... ...

class Gizmo: def __init__(self): print('Gizmo id: %d' % id(self))

x = Gizmo() Gizmo id: 4301489152 ➊ >>> y = Gizmo() * 10 ➋ Gizmo id: 4301489432 ➌ Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int' >>>

>>>

dir() ➍ ['Gizmo', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'x'] >>>

➊ Gizmo id : ... 출력은 Gizmo 객체를 생성할 때 부수적으로 생성된다. ➋ Gizmo 객체에 숫자를 곱하면 예외가 발생한다. ➌ 곱셈을 시도하기 전에 두 번째 Gizmo 객체가 실제로 생성되었음을 입증한다. ➍ 그러나 할당문의 오른쪽이 실행되는 동안 예외가 발생했기 때문에 변수 y는 결코 생성되지 않는다.

TIP

파이썬에서의 할당문을 이해하려면 언제나 오른쪽을 먼저 읽어야 한다. 할당문의 오른쪽에서 객체를 생성하거 나 가져온다. 그 후 레이블을 붙이듯이 할당문 왼쪽에 있는 변수가 객체에 바인딩된다. 상자는 잊어버려라.

변수는 단지 레이블일 뿐이므로 객체에 여러 레이블을 붙이지 못할 이유가 없다. 여러 레이블을 붙이는 것을 별명이라고 하며, 다음 절의 주제다.

8장 객체 참조, 가변성, 재활용 293


8.2 정체성, 동질성, 별명 루이스 캐럴은 찰스 럿위지 도지슨Charles Lutwidge Dodgson 교수의 필명이다. 캐럴이 도지슨 교수와 같을 뿐만 아니라, 단 하나의 동일인이다. [예제 8-3]은 이 개념을 파이썬으로 표현한다. 예제 8-3 동일한 객체를 참조하는 charles와 lewis

charles = {'name': 'Charles L. Dodgson', 'born': 1832} lewis = charles ➊ lewis is Charles ➋

>>> >>> >>>

True id(charles), id(lewis) (4300473992, 4300473992) >>> lewis['balance'] = 950 ➌ >>> charles {'name': 'Charles L. Dodgson', 'balance': 950, 'born': 1832} >>>

➊ lewis는 charles의 별명이다. ➋ is 연산자와 id ( ) 함수로 이 사실을 확인한다. ➌ lewis에 항목을 추가하는 것은 charles에 항목을 추가하는 것과 동일하다.

그런데 예를 들어 알렉산더 페다첸코 박사가 자신이 1832년에 태어난 찰스 L. 도지슨이라고 사칭하고 있다고 가정하자. 그의 자격 증명이 동일할 수는 있어도 페다첸코 박사가 도지슨 교 수일 수는 없다. [그림 8-2]는 이 시나리오를 보여준다. 그림 8-2 동일한 객체에 바인딩된 charles와 lewis. alex는 동일 내용을 가진 별도의 객체에 바인딩되어 있다.

294 4부 객체지향 상용구


[예제 8-4]는 [그림 8-2]에 나온 alex 객체를 구현하고 테스트한다. 예제 8-4 alex와 charles를 비교하면 같지만, alex가 charles는 아니다. >>> >>>

alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} ➊ alex == charles ➋

True >>>

alex is not charles ➌

True

➊ alex는 charles에 할당된 객체의 복제본을 가리킨다. ➋ dict 클래스에서 __eq__( )를 구현하는 방식 때문에 두 객체를 비교해서 같다고 판단한다. ➌ 그러나 이 두 객체는 서로 별개의 객체다. a is not b로 기술하는 것은 두 객체의 정체성이 다르다고 표

현하는 파이썬 방식이다.

[예제 8-3]은 별명aliasing 의 예다. 코드 안에서 lewis와 charles는 별명이다. 두 변수가 동일 객체에 바인딩되어 있다. 한편 alex는 charles에 대한 별명이 아니다. 이 두 변수는 서로 다른 객체에 바인딩되어 있다. alex에 바인딩된 객체와 charles에 바인딩된 객체가 동일한 값을 갖 고 있으므로 == 연산자(동치 연산자)에 의해 동일하다고 판단되지만, 정체성은 다르다. 파이썬 언어 참조 문서의 3.1절 ‘객체, 값, 자료형Objects, values and types ’(http://bit.ly/ 1Vm9gv4 ) 에서는 다음과 같이 설명하고 있다. 모든 객체는 정체성, 자료형, 값을 가지고 있다. 객체의 정체성은 일단 생성한 후에는 결코 변경되지 않는다. 정체성은 메모리 내의 객체 주소라고 생각할 수 있다. is 연산자는 두 객체의 정체성을 비교한다. id ( ) 함수 는 정체성을 나타내는 정수를 반환한다.

객체 ID의 실제 의미는 구현에 따라 다르다. CPython의 경우 id ( )는 객체의 메모리 주소를 반환하지만, 다른 파이썬 인터프리터는 메모리 주소 이외의 다른 값을 반환할 수도 있다. 다만

ID는 객체마다 고유한 레이블이라는 것을 보장하며 객체가 소멸될 때까지 결코 변하지 않는다 는 점이 핵심이다. 실제로 프로그래밍할 때는 id ( ) 함수를 거의 사용하지 않는다. 정체성 검사는 주로 is 연산자 를 이용해서 수행하며 ID를 직접 비교하지는 않는다. 다음 절에서는 == 연산자와 is 연산자에 대해 설명한다.

8장 객체 참조, 가변성, 재활용 295


8.2.1 == 연산자와 is 연산자 간의 선택 == 연산자(동치 연산자)가 객체의 값을 비교하는 반면, is 연산자는 객체의 정체성을 비교한다.

정체성보다 값을 비교하는 경우가 많으므로, 파이썬 코드에서는 == 연산자를 is 연산자보다 자 주 볼 수 있다. 그렇지만 변수를 싱글턴singleton 과 비교할 때는 is 연산자를 사용해야 한다. is 연산자를 사용할 때는 변수를 None과 비교하는 경우가 일반적이다. 이 경우 다음과 같이 사용한다. x is None

그리고 이 표현식의 반대는 다음과 같이 작성한다. x is not None

is 연산자는 오버로딩할 수 없으므로 파이썬이 이 값을 평가하기 위해 특별 메서드를 호출할

필요가 없고, 두 정수를 비교하는 정도로 연산이 간단하므로 is 연산자가 == 연산자보다 빠르 다. 반면 a == b는 a.__eq__(b )의 편리 구문이다. object 객체에서 상속받은 __eq__( ) 메서 드는 객체의 ID를 비교하므로 is 연산자와 동일한 결과를 산출한다. 그러나 대부분의 내장 자 료형은 __eq__( ) 메서드를 오버라이드해서 객체의 값을 비교한다. 대형 컬렉션이나 깊이 내포 된 구조체를 비교하는 경우 동치 비교는 상당한 처리를 요구한다. 정체성과 동치성에 대한 설명을 마치기 전에, 불변성으로 유명한 튜플이 생각만큼 딱딱하지 않 다는 것을 확인해보자.

8.2.2 튜플의 상대적 불변성 리스트, 딕셔너리, 집합 등 대부분의 파이썬 컬렉션과 마찬가지로 튜플도 객체에 대한 참조를 담는다.1 참조된 항목이 가변형이면 튜플 자체는 불변형이지만 참조된 항목은 변할 수 있다. 즉, 튜플의 불변성은 tuple 데이터 구조체의 물리적인 내용(즉, 참조 자체)만을 말하는 것이 1 반면 str, bytes, array.array 같은 단일형 시퀀스는 참조 대신 문자, 바이트, 숫자 등의 데이터를 물리적으로 연속된 메모리에 저장 한다.

296 4부 객체지향 상용구


며, 참조된 객체까지 불변성을 가지는 것은 아니다. [예제 8-5]는 튜플이 참조한 가변 객체의 변경에 의해 튜플의 값이 변경되는 상황을 설명한다. 튜플 안에서 결코 변경되지 않는 것은 튜플이 담고 있는 항목들의 정체성뿐이다. 예제 8-5 초기 t1과 t2는 동일하지만, t1 튜플 안에 있는 가변 항목을 변경하면 달라진다. >>> >>> >>>

t1 = (1, 2, [30, 40]) ➊ t2 = (1, 2, [30, 40]) ➋ t1 == t2 ➌

True id(t1[-1]) ➍ 4302515784 >>> t1[-1].append(99) >>> t1 (1, 2, [30, 40, 99]) >>> id(t1[-1]) ➏ 4302515784 >>> t1 == t2 ➐ False >>>

➊ t1은 불변형이지만, t1[-1]은 가변형이다. ➋ t1과 항목이 같은 t2 튜플을 생성한다. ➌ 서로 다른 객체지만, 예상한 대로 t1과 t2는 같다고 판단된다. ➍ t1[-1]에 있는 리스트의 정체성을 확인한다. ➎ t1[-1] 리스트를 그 자리에서 변경한다. ➏ t1[-1]의 정체성은 그대로며 값만 변경되었다. ➐ 이제 t1과 t2는 다르다고 판단된다.

이러한 튜플의 상대적 불변성 때문에 2.6.1절 ‘+= 복합 할당 퀴즈’ 같은 상황이 발생한다. 그리 고 이는 3.1절의 ‘해시 가능하다는 말의 의미는?’ 글상자에서 본 것처럼 일부 튜플이 해시 불가 능한 이유이기도 하다. 동질성과 정체성 간의 차이는 객체를 복사할 때 더 큰 영향을 미친다. 사본은 ID가 다른 동일한 객체다. 그러나 객체가 다른 객체를 담고 있을 때 복사하면 내부 객체도 복사해야 할까? 아니면 내부 객체는 공유해도 될까? 정답은 없다. 다음 절의 설명을 보자.

8장 객체 참조, 가변성, 재활용 297


8.3 기본 복사는 얕은 복사 리스트나 대부분의 내장 가변 컬렉션을 복사하는 가장 손쉬운 방법은 그 자료형 자체의 내장 생 성자를 사용하는 것이다. 다음 예를 보자. l1 = [3, [55, 44], (7, 8, 9)] l2 = list(l1) ➊ >>> l2 [3, [55, 44], (7, 8, 9)] >>> l2 == l1 ➋ True >>> l2 is l1 ➌ False >>> >>>

➊ list (l1 )은 l1의 사본을 생성한다. ➋ 두 사본이 동일하다. ➌ 그러나 서로 다른 두 객체를 참조한다.

리스트 및 가변형 시퀀스의 경우 l2 = l1[:] 코드는 사본을 생성한다. 그러나 생성자나 [:]을 사용하면 얕은 사본shallow copy 을 생성한다. 즉, 최상위 컨테이너는 복제하 지만 사본은 원래 컨테이너에 들어 있던 동일 객체에 대한 참조로 채워진다. 모든 항복이 불변 형이면 이 방식은 메모리를 절약하며 아무런 문제를 일으키지 않는다. 그러나 가변 항목이 들어 있을 때는 불쾌한 문제를 야기할 수도 있다. [예제 8-6]에서는 다른 리스트와 튜플을 담고 있는 리스트의 얕은 사본을 생성한 후 변경해서 참조된 객체에 어떻게 영향을 미치는지 보여준다. TIP

인터넷에 연결된 컴퓨터가 옆에 있다면 온라인 파이썬 튜터(http://www.pythontutor.com/)에서 [예제

8-6]의 대화형 애니메이션을 확인해보기 바란다. 이 책을 쓰고 있는 현재 pythontutor.com에서 준비한 예 제를 직접 바인딩하는 방법은 제대로 작동하지 않지만, 도구는 대단히 훌륭하다. 코드를 복사해서 붙여 넣고 애니메이션으로 확인할 가치가 충분히 있다.

298 4부 객체지향 상용구


예제 8-6 다른 리스트를 담고 있는 리스트의 얕은 복사. 이 코드를 온라인 파이썬 튜터에 복사해서 애니메이션을 확인해 보라.

l1 = [3, [66, 55, 44], (7, 8, 9)] l2 = list(l1) ➊ l1.append(100) ➋ l1[1].remove(55) ➌ print('l1:', l1) print('l2:', l2) l2[1] += [33, 22] ➍ l2[2] += (10, 11) ➎ print('l1:', l1) print('l2:', l2)

➊ l2는 l1의 얕은 사본이다. 이 상태는 [그림

8 -3]과 같다.

➋ l1에 100을 추가해도 l2에는 영향을 미치지 않는다. ➌ 여기서는 내부 리스트 l1[1]에서 55를 제거한다. l2[1]이 l1[1]과 동일한 리스트에 바인딩되어 있으므로

이 코드는 l2에 영향을 미친다. ➍ l2[1]이 참조하는 리스트처럼 가변 객체의 경우 += 연산자가 리스트를 그 자리에서 변경한다. 이 변경은

l2[1]의 별명인 l1[1]에도 반영된다. ➎ 여기서 += 연산자는 새로운 튜플을 만들어서 l2[2]에 다시 바인딩한다. 이 코드는 l2[2] = l2[2] +

(10, 11 )과 동일하다. 이제 l1과 l2의 마지막에 있는 튜플은 더 이상 동일 객체가 아니다. [그림 8 -4]를 참조하라.

[예제 8-7]은 [예제 8-6]을 실행한 결과다. 객체들의 최종 상태는 [그림 8-4]와 같다. 예제 8-7 [예제 8-6]의 실행 결과

l1: l2: l1: l2:

[3, [3, [3, [3,

[66, [66, [66, [66,

44], (7, 8, 9), 100] 44], (7, 8, 9)] 44, 33, 22], (7, 8, 9), 100] 44, 33, 22], (7, 8, 9, 10, 11)]

8장 객체 참조, 가변성, 재활용 299


그림 8-3 [예제 8-6]에서 l2 = list (l1) 할당문을 실행한 직후의 상태. l1과 l2는 서로 다른 리스트를 참조하지만, 각 리스 트는 내부의 리스트 객체 [66, 55, 44]와 튜플 (7, 8, 9)를 공유한다(다이어그램은 온라인 파이썬 튜터로 생성했다).

그림 8-4 l1과 l2의 최종 상태. l1과 l2 모두 [66, 44, 33, 22]를 담고 있는 동일 리스트 객체에 대한 참조를 공유하고 있지만, l2 [2] += (10, 11) 연산은 (7, 8, 9, 10, 11)을 담은 튜플을 새로 생성해서 l1[2]가 참조하는 (7, 8, 9) 튜플과 분리시킨다(다이어그램은 온라인 파이썬 튜터로 생성했다).

이제 얕은 복사를 만들기 쉽다는 것은 잘 알겠지만, 얕은 복사를 원하지 않을 수도 있다. 다음 절 에서는 깊게 복사하는 방법을 알아보자.

300 4부 객체지향 상용구


8.3.1 객체의 깊은 복사와 얕은 복사 얕게 복사한다고 해서 늘 문제가 생기는 것은 아니지만, 내포된 객체의 참조를 공유하지 않도록 깊게 복사할 필요가 있는 경우가 종종 있다. copy 모듈이 제공하는 deepcopy ( ) 함수는 깊은 복 사를, copy ( ) 함수는 얕은 복사를 지원한다. copy ( )와 deepcopy ( ) 사용법을 예제를 통해 설명하기 위해 [예제 8-8]은 노선을 따라가면서

승객을 태우거나 내리는 학교 버스를 나타내는 Bus 클래스를 간단히 정의한다. 예제 8-8 승객을 승차 및 하차하는 버스

class Bus: def __init__(self, passengers=None): if passengers is None: self.passengers = [] else: self.passengers = list(passengers) def pick(self, name): self.passengers.append(name) def drop(self, name): self.passengers.remove(name)

이제 [예제 8-9]와 같이 대화형 콘솔을 이용해서 bus1 객체와 두 개의 사본(bus2는 얕은 사본, bus3은 깊은 사본)을 만들어 bus1이 학생을 내릴 때 어떤 일이 생기는지 보자. 예제 8-9 copy( )와 deepcopy( )의 비교

import copy bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) >>> bus2 = copy.copy(bus1) >>> bus3 = copy.deepcopy(bus1) >>> id(bus1), id(bus2), id(bus3) (4301498296, 4301499416, 4301499752) ➊ >>> bus1.drop('Bill') >>> bus2.passengers ['Alice', 'Claire', 'David'] ➋ >>> >>>

8장 객체 참조, 가변성, 재활용 301


id(bus1.passengers), id(bus2.passengers), id(bus3.passengers) (4302658568, 4302658568, 4302657800) ➌ >>> bus3.passengers ['Alice', 'Bill', 'Claire', 'David'] ➍ >>>

➊ copy ( )와 deepcopy ( )를 이용해서 세 개의 Bus 객체를 생성한다. ➋ bus1이 'Bill'을 내리면 bus2에서도 'Bill'이 사라진다. ➌ passengers 속성을 조사해보면 bus1과 bus2가 동일 리스트를 공유하는 것을 알 수 있다. bus2가 bus1

의 얕은 사본이기 때문이다. ➍ bus3는 bus1의 깊은 사본이므로, passengers 속성이 다른 리스트를 가리킨다.

일반적으로 깊은 사본을 만드는 일은 간단하지 않다는 점에 주의하라. 객체 안에 순환 참조가 있으면 단순한 알고리즘은 무한 루프에 빠질 수 있다. deepcopy ( ) 함수는 순환 참조를 제대로 처리하기 위해 이미 복사한 객체에 대한 참조를 기억하고 있다. 순환 참조를 깊게 복사하는 예 는 [예제 8-10]과 같다. 예제 8-10 순환 참조. b가 a를 참조한 후 a의 뒤에 바인딩되는데, deepcopy( )는 a를 제대로 복사한다.

a = [10, 20] b = [a, 30] >>> a.append(b) >>> a [10, 20, [[...], 30]] >>> from copy import deepcopy >>> c = deepcopy(a) >>> c [10, 20, [[...], 30]] >>> >>>

게다가 깊은 복사가 너무 깊이 복사하는 경우도 있다. 예를 들어 복사하면 안 되는 외부 리소스나 싱글턴을 객체가 참조하는 경우가 있다. copy 모듈 문서(http://docs.python.org/3/library/

copy.html )에 설명된 대로 __copy__( )와 __deepcopy__( ) 특별 메서드를 구현해서 copy ( ) 와 deepcopy ( )의 동작을 제어할 수 있다. 별명을 통한 객체 공유 방식은 파이썬에서 매개변수 전달이 작동하는 방식과 가변 자료형을 매 개변수 기본형으로 사용하는 문제도 설명할 수 있다. 이에 대해서는 다음 절에서 설명한다.

302 4부 객체지향 상용구


8.4 참조로서의 함수 매개변수 파이썬은 공유로 호출call by sharing 하는 매개변수 전달 방식만 지원한다. 이 방식은 루비, 스몰토 크, 자바(자바 참조 자료형일 때만 동일하다. 기본 자료형은 값으로 호출call by value 하는 방식을 사용한다) 등 대부분의 객체지향 언어에서 사용하는 방식과 동일하다. 공유로 호출한다는 말 은 함수의 각 매개변수가 인수로 전달받은 각 참조의 사본을 받는다는 의미다. 달리 말하면, 함 수 안의 매개변수는 실제 인수의 별명이 된다. 이런 체계의 결과로서, 함수는 인수로 전달받은 모든 가변 객체를 변경할 수 있지만, 객체의 정 체성 자체는 변경할 수 없다. 즉, 어떤 객체를 다른 객체로 바꿀 수는 없다. [예제 8-11]은 매개 변수 중 하나에 += 연산자를 사용하는 간단한 함수를 보여준다. 함수에 숫자, 리스트, 튜플을 전달하면, 전달받은 인수는 서로 다른 영향을 받는다. 예제 8-11 함수는 전달받은 가변 객체를 수정할 수 있다. >>>

... ... ... >>> >>> >>>

def f(a, b): a += b return a x = 1 y = 2 f(x, y)

3 x, y ➊ (1, 2) >>> a = [1, 2] >>> b = [3, 4] >>> f(a, b) [1, 2, 3, 4] >>> a, b ➋ ([1, 2, 3, 4], [3, 4]) >>> t = (10, 20) >>> u = (30, 40) >>> f(t, u) ➌ (10, 20, 30, 40) >>> t, u ((10, 20), (30, 40)) >>>

➊ 숫자 x는 변경되지 않는다.

8장 객체 참조, 가변성, 재활용 303


➋ 리스트 a는 변경된다. ➌ 튜플 t는 변경되지 않는다.

함수 매개변수와 관련된 또 다른 문제는 가변형 기본값을 사용하는 것과 관련 있다. 이에 대해서 는 다음 절에서 설명한다.

8.4.1 가변형을 매개변수 기본값으로 사용하기: 좋지 않은 생각 기본값을 가진 선택적 인수는 파이썬 함수 정의에서 아주 좋은 기능으로, 하위 호환성을 유지하 며 API를 개선할 수 있게 해준다. 그러나 매개변수 기본값으로 가변 객체를 사용하는 것은 피해 야 한다. 예를 들어 설명하기 위해 [예제 8-12]에서는 [예제 8-8]의 Bus 클래스를 가져와서 __init__( ) 메서드를 변경하고 HauntedBus 클래스를 정의한다. 여기서는 약간의 꾀를 부려 passengers 의 기본값을 None 대신 []를 사용해서 이전 __init__( ) 메서드에서 if 절로 검사하던 부분을 생략할 수 있게 했다. 여기서는 제 꾀에 제가 넘어가는 경우가 발생한다. 예제 8-12 가변형이 기본값이 될 때의 위험성을 보여주는 간단한 클래스

class HauntedBus: """유령 승객이 출몰하는 버스 모델""" def __init__(self, passengers=[]): ➊ self.passengers = passengers ➋ def pick(self, name): self.passengers.append(name)

def drop(self, name): self.passengers.remove(name)

➊ passengers 인수를 전달하지 않는 경우 이 매개변수는 기본값인 빈 리스트에 바인딩된다. ➋ 이 할당문은 self.passengers를 passengers에 대한 별명으로 만드므로, passengers 인수를 전달하

지 않는 경우 self.passengers를 기본값인 빈 리스트에 대한 별명으로 설정한다. ➌ self.passengers에 remove ( )와 append ( ) 메서드를 사용할 때, 실제로는 함수 객체의 속성인 가변형

기본 리스트를 변경하는 것이다.

304 4부 객체지향 상용구


[예제 8-13]은 HauntedBus의 기괴한 작동을 보여준다. 예제 8-13 유령 승객이 출몰하는 버스

bus1 = HauntedBus(['Alice', 'Bill']) bus1.passengers ['Alice', 'Bill'] >>> bus1.pick('Charlie') >>> bus1.drop('Alice') >>> bus1.passengers ➊ ['Bill', 'Charlie'] >>> bus2 = HauntedBus() ➋ >>> bus2.pick('Carrie') >>> bus2.passengers ['Carrie'] >>> bus3 = HauntedBus() ➌ >>> bus3.passengers ➍ ['Carrie'] >>> bus3.pick('Dave') >>> bus2.passengers ➎ ['Carrie', 'Dave'] >>> bus2.passengers is bus3.passengers ➏ True >>> bus1.passengers ➐ ['Bill', 'Charlie'] >>> >>>

➊ 지금까지는 문제가 없다. bus1이 예상한 대로 작동한다. ➋ bus2가 빈 리스트로 시작하므로, 기본값인 빈 리스트가 self.passengers에 할당된다. ➌ bus3도 빈 리스트로 시작하므로, 여기서도 기본값인 빈 리스트가 self.passengers에 할당된다. ➍ 기본값이 더 이상 비어 있지 않다! ➎ 이제 bus3에 승차한 Dave가 bus2에 나타난다. ➏ 문제는 bus2.passengers와 bus3.passengers가 동일한 리스트를 참조한다는 것이다. ➐ 그러나 bus1.passengers는 별개의 리스트다.

결국 명시적인 승객 리스트로 초기화되지 않은 Bus 객체들이 승객 리스트를 공유하게 되는 문제 가 발생한다. 이런 버그는 찾아내기 쉽지 않다. [예제 8-13]에서 본 것처럼 HauntedBus 객체를 passengers 로 초기화하면 원하는 대로 작동한다. HauntedBus 객체가 빈 리스트로 시작할 때만 이상한 일

8장 객체 참조, 가변성, 재활용 305


이 발생한다. self.passengers가 passengers 매개변수 기본값의 별명이 되기 때문이다. 문 제는 각 기본값이 함수가 정의될 때(즉, 일반적으로 모듈이 로딩될 때) 평가되고 기본값은 함 수 객체의 속성이 된다는 것이다. 따라서 기본값이 가변 객체고, 이 객체를 변경하면 변경 내용 이 향후에 이 함수의 호출에 영향을 미친다. [예제 8-13]의 문장들을 실행한 후 HauntedBus.__init__ 객체를 조사하면 다음과 같이 __defaults__ 속성 안에 유령 학생이 들어 있는 것을 볼 수 있다.

dir(HauntedBus.__init__) # doctest: +ELLIPSIS ['__annotations__', '__call__', ..., '__defaults__', ...] >>> HauntedBus.__init__.__defaults__ (['Carrie', 'Dave'],) >>>

끝으로, 다음 문장을 실행하면 bus2.passengers가 HauntedBus.__init__.__defaults__ 속성 의 첫 번째 항목에 바인딩된 별명임을 확인할 수 있다. HauntedBus.__init__.__defaults__[0] is bus2.passengers

>>>

True

가변 기본값에 대한 이러한 문제 때문에, 가변 값을 받는 매개변수의 기본값으로 None을 주로 사용한다. [예제 8-8]에서 __init__( ) 메서드는 passengers 인수가 None인지 확인하고 새로 만든 빈 리스트를 self.passengers에 할당한다. 다음 절에서 설명하는 것처럼, passengers 인수가 None이 아니면 인수의 사본을 self.passengers에 할당하는 것이 올바른 방법이다. 좀 더 자세히 살펴보자.

8.4.2 가변 매개변수에 대한 방어적 프로그래밍 가변 매개변수를 받는 함수를 구현할 때는, 전달된 인수가 변경될 것이라는 것을 호출자가 예 상할 수 있는지 없는지 신중하게 고려해야 한다. 예를 들어 여러분이 구현하는 함수를 dict 객체를 받아서 처리하는 동안 그 dict 객체를 변경한 다면, 함수가 반환된 후에도 변경 내용이 남아 있어야 할까 아닐까? 판단은 상황에 따라 다르다. 정말 중요한 것은 함수 구현자와 함수 호출자가 예상하는 것을 일치시키는 것이다.

306 4부 객체지향 상용구


이 장에서 마지막으로 구현할 버스 예제인 TwilightBus 클래스는 승객 리스트를 코드 호출자 와 공유함으로써 어떻게 호출자가 예상치 못한 일이 발생하는지 보여준다. 클래스 구현에 앞 서, 클래스 사용자의 입장에서 TwilightBus 클래스가 어떻게 작동해야 하는지 살펴보자(예제

8-14 ). 예제 8-14 TwilightBus가 하차시킬 때 사라지는 승객들

basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat'] ➊ >>> bus = TwilightBus(basketball_team) ➋ >>> bus.drop('Tina') ➌ >>> bus.drop('Pat') >>> basketball_team ➍ ['Sue', 'Maya', 'Diana'] >>>

➊ basketball_team에 학생 다섯 명이 있다. ➋ TwilightBus가 팀을 태운다. ➌ bus가 학생을 한 명을 내린 후 한 명 더 내린다. ➍ 내린 학생들이 농구팀에서 사라졌다!

TwilightBus 클래스는 인터페이스 디자인에서 가장 중요한 ‘최소 놀람의 법칙 Principle of least astonishment

’을 어긴다. 학생이 버스에서 내린다고 해서 그 학생이 농구팀 출전 명단에서 빠진다는

것은 분명 놀라운 일이다. [예제 8-15]는 TwilightBus 클래스 구현 코드며 문제의 원인을 알려준다. 예제 8-15 받은 인수를 변경하는 위험성을 보여주는 간단한 클래스

class TwilightBus: """승객이 사라지게 만드는 버스 모델""" def __init__(self, passengers=None): if passengers is None: self.passengers = [] ➊ else: self.passengers = passengers ➋ def pick(self, name): self.passengers.append(name)

8장 객체 참조, 가변성, 재활용 307


def drop(self, name): self.passengers.remove(name)

➊ passengers가 None일 때 빈 리스트를 새로 생성하는 신중함을 보여준다. ➋ 그러나 이 할당문에 의해 self.passengers는 passengers에 대한 별명이 된다. 이때 passengers는

[예제 8 -14]에서의 basketball_team처럼 __init__( )에 전달된 인수의 별명이다. ➌ self.passengers의 remove ( )나 append ( ) 메서드를 사용하면, 생성자에 인수로 전달된 원래 리스트

를 변경하게 된다.

여기서 문제는 bus가 생성자에 전달된 리스트의 별명이라는 점이다. 여기서는 TwilightBus 객체 고유의 리스트를 유지해야 한다. 해결하는 방법은 __init__( ) 메서드가 passengers 인 수를 받을 때 인수의 사본으로 self.passengers를 초기화하면 된다. 8.3.1절 ‘객체의 깊은 복 사와 얕은 복사’의 [예제 8-8]에서는 다음과 같이 제대로 구현했다. def __init__(self, passengers=None): if passengers is None: self.passengers = [] else: self.passengers = list(passengers)

➊ passengers 리스트의 사본을 만들거나, passengers가 리스트가 아닐 때는 리스트로 변환한다.

이제 TwilightBus 객체 안에서 passenger 리스트를 변경해도 TwilightBus 객체를 초기화하 기 위해 전달한 인수에는 아무런 영향을 미치지 않는다. 게다가 융통성도 향상된다. list ( ) 생 성자가 모든 반복 가능한 객체를 받으므로, 튜플은 물론 집합이나 데이터베이스 결과 등의 반 복 가능한 객체는 모두 passengers 매개변수에 사용할 수 있다. 관리할 리스트를 자체적으로 생성하므로 pick ( )과 drop ( ) 메서드 안에서 사용하는 remove ( )와 append ( ) 메서드 지원 을 보장할 수 있다. TIP

인수로 받은 객체를 메서드가 변경할 것이라는 의도가 명백하지 않은 한 클래스 안에서 인수를 변수에 할당함 으로써 인수 객체에 별명을 붙이는 것에 대해 주의할 필요가 있다. 불명확한 경우에는 사본을 만들어라. 여러 분이 만든 클래스를 사용하는 프로그래머들의 행복도가 향상될 것이다.

308 4부 객체지향 상용구


8.5 del과 가비지 컬렉션 객체는 결코 명시적으로 제거되지 않는다. 그러나 도달할 수 없을 때 객체는 가비지 컬렉트될 수 있다. _ 파이썬 언어 참조 문서의 ‘데이터 모델’ 장

del 명령은 이름을 제거하는 것이지, 객체를 제거하는 것이 아니다. del 명령의 결과로 객체가

가비지 컬렉트될 수 있지만, 제거된 변수가 객체를 참조하는 최후의 변수거나 객체에 도달할 수 없을 때만 가비지 컬렉트된다.2 변수를 다시 바인딩해도 객체에 대한 참조 카운트를 0으로 만 들어 객체가 제거될 수 있다. CAUTION_ __del__()이라는 특별 메서드가 있기는 하지만, 객체가 제거되도록 만들지 않으며, 사용자

코드에서 직접 호출하면 안 된다. __del__()은 객체가 제거되기 직전에 외부 리소스를 해제할 기회를 주기 위해 파이썬 인터프리터가 호출한다. 사용자 코드에서 __del__()을 구현해야 하는 경우는 거의 없지만, 종 종 파이썬 초보자들이 타당한 이유 없이 이 메서드에 시간을 쏟는 경우가 있다. __del__()은 제대로 사용 하기 다소 어렵다. 파이썬 언어 참조 문서의 ‘데이터 모델’ 장에서 __del__() 특별 메서드 문서(http://bit.

ly/1GsWPac)를 참조하라.

CPython의 경우 가비지 컬렉션은 주로 참조 카운트reference count 에 기반한다. 본질적으로 각 객 체는 얼마나 많은 참조가 자신을 가리키는지 개수refcount 를 세고 있다. refcount가 0이 되자마 자 CPython이 객체의 __del__( ) 메서드를 호출하고(정의되어 있는 경우) 객체에 할당되어 있는 메모리를 해제함으로써 객체가 제거된다. CPython 2.0에는 순환 참조(그룹 안에서 서로 참조하고 있어서 참조 카운트는 0이 아니지만 도달할 수 없는 상태)에 관련된 객체 그룹을 탐 지하기 위해 세대별 가비지 컬렉션 알고리즘generational garbage collection algorithm 을 추가했다. 다른 파이썬 구현에서는 참조 카운트에 기반하지 않는 더 정교한 가비지 컬렉터를 사용하므로, 객 체에 대한 참조가 모두 사라진 경우에도 __del__( ) 메서드가 바로 호출되지 않을 수도 있다. __del__( ) 메서드의 적절한 사용과 부적절한 사용에 대해서는 제시 지류 데이비스Jesse Jiryu Davis

의 ‘PyPy, 가비지 컬렉션, 데드락PyPy, Garbage Collection, and a Deadlock ’(http://bit.ly/1GsWTa7 )을 참조하라. 2 [예제 8-10]처럼 두 객체가 서로를 참조하더라도, 가비지 컬렉터가 이들이 서로를 가리키는 참조만 있을 뿐 코드의 나머지 부분에서 이 객체에 도달할 수 없다고 판단하면, 이 객체들도 제거될 수 있다.

8장 객체 참조, 가변성, 재활용 309


객체가 소멸될 때를 보여주기 위해 [예제 8-16]에서는 weakref.finalize ( )를 사용해서 객체 가 소멸될 때 호출되는 콜백 함수를 등록한다. 예제 8-16 가리키는 참조가 없을 때 객체가 소멸되는 것을 지켜보기

import weakref s1 = {1, 2, 3} >>> s2 = s1 ➊ >>> def bye(): ➋ print('Gone with the wind...') ... ... >>> ender = weakref.finalize(s1, bye) ➌ >>> ender.alive ➍ True >>> del s1 >>> ender.alive ➎ True >>> s2 = 'spam' ➏ Gone with the wind... >>> ender.alive False >>> >>>

➊ s1과 s2는 동일한 집합 {1, 2, 3}을 가리키는 별명이다. ➋ 이 함수는 제거될 객체의 메서드에 바인딩되거나 제거될 객체를 참조하면 안 된다. ➌ s1이 가리키는 객체에 대해 bye ( ) 콜백을 등록한다. ➍ finalize 객체가 호출되기 전의 alive 속성은 참이다. ➎ 앞에서 설명한 대로 del은 객체가 아니라 객체에 대한 참조를 제거한다. ➏ 마지막 참조인 s2를 다른 객체에 바인딩하면 {1, 2, 3} 튜플에 도달할 수 없게 된다. 튜플이 제거되고,

bye ( ) 콜백이 호출되고, ender.alive는 거짓이 된다.

[예제 8-16]은 del이 객체를 제거하는 것이 아니고, del을 실행한 후 객체가 도달할 수 없게 된 결과로 객체가 제거됨을 명확히 보여준다. [예제 8-16]에서 {1, 2, 3} 튜플 객체가 제거된 이유가 궁금할 것이다. 어쨌든 s1 참조가 finalize ( ) 함수에 전달되었고, finalize ( ) 함수는 객체를 감시하고 콜백을 호출하기 위해

객체에 대한 참조를 갖고 있는 것이 아닐까? 이 코드가 작동하는 것은 finalize ( )가 {1, 2, 3} 튜플 객체에 대한 약한 참조weak reference 를 가지고 있기 때문이다. 약한 참조에 대해서는 다음 절 에서 설명한다.

310 4부 객체지향 상용구


8.6 약한 참조 객체가 메모리에 유지되거나 유지되지 않도록 만드는 것은 참조의 존재 여부다. 객체 참조 카운 트가 0이 되면 가비지 컬렉터는 해당 객체를 제거한다. 그러나 불필요하게 객체를 유지시키지 않으면서 객체를 참조할 수 있으면 도움이 되는 경우가 종종 있다. 캐시가 대표적인 경우다. 약한 참조는 참조 카운트를 증가시키지 않고 객체를 참조한다. 참조의 대상인 객체를 참조 대 상referent 이라고 한다. 따라서 약한 참조는 참조 대상이 가비지 컬렉트되는 것을 방지하지 않는 다고 말할 수 있다. 약한 참조는 캐시 애플리케이션에서 유용하게 사용된다. 캐시가 참조하고 있다고 해서 캐시된 객체가 계속 남아 있기 원치 않기 때문이다. [예제 8-17]은 weakref.ref 객체를 호출해서 참조 대상에 접근하는 방법을 보여준다. 객체가 살아 있으면 약한 참조 호출은 참조된 객체를 반환하고, 그렇지 않으면 None을 반환한다. TIP

[예제 8-17]은 콘솔 세션이며, 파이썬 콘솔은 None이 아닌 표현식의 결과에 _ 변수를 자동으로 할당한다. 이 런 성질 때문에 의도한 대로 예제를 보여줄 수는 없었지만, 한편으로는 실제 상황을 잘 보여준다. 메모리를 섬 세하게 제어하고자 할 때, 객체에 새로운 참조를 생성하는 암묵적인 할당 때문에 당황하는 경우가 종종 있다. _ 변수가 그런 사례다. Traceback 객체도 예기치 않은 참조를 만들어내는 원인이다.

예제 8-17 약한 참조는 콜러블이다. 객체가 살아 있으면 참조된 객체를 반환하고, 그렇지 않으면 None을 반환한다.

import weakref a_set = {0, 1} >>> wref = weakref.ref(a_set) ➊ >>> wref <weakref at 0x100637598; to 'set' at 0x100636748> >>> wref() ➋ {0, 1} >>> a_set = {2, 3, 4} ➌ >>> wref() ➍ {0, 1} >>> wref() is None ➎ False >>> wref() is None ➏ True >>> >>>

8장 객체 참조, 가변성, 재활용 311


➊ 약한 참조 객체

wref를 생성하고 다음 행에서 조사한다.

➋ wref ( )를 호출하면 참조된 객체 {0, 1}을 반환한다. 콘솔 세션에서 실행하고 있으므로 결과로 나온 {0,

1}이 _ 변수에 바인딩된다. ➌ a_set이 더 이상 {0, 1} 집합을 참조하지 않으므로 참조 카운트가 줄어든다. 그렇지만 _ 변수가 여전히

{0, 1}을 참조한다. ➍ wref ( )를 호출하면 여전히 {0, 1}이 반환된다. ➎ 표현식을 평가할 때 {0, 1}이 살아 있으므로 wref ( )는 None이 아니다. 그렇지만 _ 변수는 결과값인

False에 바인딩된다. 이제 _ 변수는 더 이상 {0, 1}을 참조하지 않는다. ➏ 이제 {0, 1} 객체가 제거되었으므로 wref ( )를 호출하면 None이 반환된다.

weakref 모듈 문서( http://docs.python.org/3/library/weakref.html )에서는 weakref. ref 클래스는 고급 사용자를 위한 저수준 인터페이스며, 일반 프로그래머는 weakref 컬렉션과 finalize ( )를 사용하는 것이 좋다고 설명한다. 즉, weakref.ref 객체를 직접 만들기보다는 WeakKeyDictionary, WeakValueDictionary, WeakSet, 그리고 내부적으로 약한 참조를 이용

하는 finalize ( )를 사용하는 것이 좋다. [예제 8-17]에서는 weakref.ref 객체 하나가 작동 하는 방식을 보면서 약한 참조 개념을 익히기 위해 weakref.ref 객체를 직접 만들었지만, 실 제로 대부분의 파이썬 프로그램은 weakref 컬렉션을 사용한다. 다음 절에서는 weakref 컬렉션에 대해 간단히 살펴본다.

8.6.1 WeakValueDictionary 촌극 WeakValueDictionary 클래스는 객체에 대한 약한 참조를 값으로 가지는 가변 매핑을 구현한

다. 참조된 객체가 프로그램 다른 곳에서 가비지 컬렉트되면 해당 키도 WeakValueDictionary 에서 자동으로 제거된다. 이 클래스는 일반적으로 캐시를 구현하기 위해 사용된다. 여기에서 설명하는 WeakValueDictionary 사용 예는 몬티 파이튼의 고전적인 ‘치즈 가게Cheese Shop

’ 촌극에서 영감을 받았다. ‘치즈 가게’ 촌극에서는 체다, 모차렐라 등 40여 종의 치즈를 고객

이 주문하지만, 그중 어느 것도 재고가 없다.3 3 cheeseshop.python.org는 파이썬 패키지 인덱스( Python Package Index , PyPI ) 소프트웨어 저장소의 또 다른 도메인명이기 도 하다. 초기 PyPI는 거의 비어 있었지만, 이 책을 쓰고 있는 현재 파이썬 치즈 가게에는 41,426개의 패키지가 등록되어 있다. 나쁘지 는 않지만 131,000여 개의 모듈이 등록되어 있어 모든 동적 언어 커뮤니티에서 부러워하는 종합 펄 저장소 네트워크( Comprehensive Perl Archive Network , CPAN )까지는 아직 멀었다.

312 4부 객체지향 상용구


CHAPTER

18

asyncio를 이용한 동시성

동시성은 한 번에 많은 것을 다룬다. 병렬성은 한 번에 많은 것을 한다. 똑같지는 않지만, 연관성은 있다. 동시성은 구조, 병렬성은 실행에 관한 것이다. 동시성은 (꼭 해야 하는 것은 아니지만) 병렬화할 수 있는 문제를 해결하기 위해 해결책을 구조화하는 방법을 제공한다.1 _ 롭 파이크Rob Pike

Go 언어의 공동 창시자

이므리 사이먼Imre Simon 교수2는 동일한 것을 의미하기 위해 다른 단어를 사용하는 것과 동일한 단어를 이용해서 여러 가지를 의미하는 것은 과학에서의 2대 죄악이라고 이야기하곤 했다. 동 시성이나 병렬 프로그래밍에 대해 연구하다보면 ‘동시성’과 ‘병렬성’에 대한 상이한 정의를 발 견할 수 있다. 필자는 앞에서 인용한 롭 파이크의 비공식 정의를 따른다. 진짜 병렬로 처리하려면 CPU 코어가 여러 개 있어야 한다. 최신 랩톱 컴퓨터는 4개의 CPU 코 어를 가지고 있지만, 일반적인 운용 환경에서도 100개 이상의 프로세스를 일상적으로 실행한다. 따라서 실제로는 대부분의 처리가 동시에 수행되지만 병렬로 수행되지는 않는다. 컴퓨터는 한 번 에 고작해야 4개의 작업을 병렬로 처리할 수 있지만, 100개 이상의 프로세스를 계속 처리하면 서 각 프로세스가 진행할 수 있게 보장한다. 10년 전에 필자는 코어가 하나밖에 없지만 100개의 1 ‘동시성은 병렬처리가 아니다(더 낫다) (Concurrency Is Not Parallelism (It’s Better ) )’ 발표 자료(http://bit.ly/1OwVTUf )의 5번 째 슬라이드 2 이므리 사이먼( 1943-2009 ) 교수는 브라질 컴퓨터 과학의 개척자로서, 자동화 이론에 커다란 기여를 했으며, 열대 수학( Tropical Mathematics ) 분야를 창시했다. 자유 소프트웨어와 자유 문화의 지지자였다. 그로부터 배우고, 함께 연구하고, 시간을 보낼 수 있어서 필자는 운이 좋았다.

651


프로세스를 처리할 수 있는 컴퓨터는 사용했다. 그렇기 때문에 롭 파이크가 자신의 발표 자료 제목을 ‘동시성은 병렬처리가 아니다(더 낫다)’라고 한 것이다. 이 장에서는 이벤트 루프에 의해 운용되는 코루틴을 이용해서 동시성을 구현하는 asyncio 패키 지에 대해 설명한다. 이 패키지는 파이썬에 추가된 가장 거대하고 야심찬 라이브러리 중 하나다. 귀도 반 로섬은 asyncio를 파이썬 리포지토리repository (저장소) 외부에서 개발하고, 프로젝트 코 드명을 ‘튤립’이라고 했다. 그래서 인터넷에서 이 주제에 대해 검색할 때 tulip이라는 이름을 많 이 볼 수 있다. 예를 들어 주요 토론 그룹명은 아직도 python-tulip (http://bit.ly/1HGtMiO ) 이다. 파이썬 3 .4 의 표준 라이브러리에 추가되면서 튤립이라는 이름이 asyncio 로 변경되었다. asyncio는 파이썬 3.3과도 호환되며, PyPI에서 새로운 공식명( https://pypi.python.org/

pypi/asyncio )으로도 볼 수 있다. yield from 표현식을 아주 많이 사용하므로, asyncio는 이전 버전의 파이썬과는 호화되지 않는다. 튤립처럼 꽃 이름을 딴 Trollius 프로젝트(http://trollius.readthedocs.org )는 asyncio를 파이썬 2.6 이

TIP

후 버전으로 하위 포팅한 것으로서, yield from을 ‘yield’와 ‘From과 Return이라는 이름을 가진 콜러 블’로 대체한다. yield from...에서 yield From (...) 형태로 바뀌고, 결과를 반환해야 하는 코루틴은 return result에서 raise Return (result )로 바뀌었다. Trollius 프로젝트는 asyncio의 핵심 개발자 인 빅터 스티너 Victor Stinner 에 의해 주도되었으며, 친절하게도 빅터는 이 책을 제작하고 있는 동안 이 장 내용을 검토해주었다.

이 장에서는 다음과 같은 내용을 설명한다. 간단한 스레드 프로그램과 그에 준하는 asyncio 버전을 비교하면서, 스레드와 비동기 작업의 관계를 보여

준다. asyncio.Future 클래스와 concurrent.futures.Future 클래스의 차이점을 설명한다.

17장에서 구현한 국기 내려받기 예제의 비동기 버전을 구현한다.

스레드나 프로세스를 사용하지 않고 비동기 프로그래밍이 네트워크 프로그램에서 높은 동시성을 관리하는

방법을 설명한다. 코루틴으로 비동기 프로그래밍을 하기 위한 콜백을 개선시키는 방법을 설명한다.

블로킹 연산을 스레드 풀에 덜어줌으로써 이벤트 루프를 블로킹하지 않는 방법을 알아본다.

asyncio 서버를 작성하고, 웹 애플리케이션의 높은 동시성을 다시 생각해본다.

asyncio가 파이썬 생태계에서 커다란 영향을 줄 수밖에 없는 이유를 설명한다.

652 5부 제어 흐름


먼저 threading과 asyncio를 비교해볼 수 있는 간단한 예제를 만들어보자.3

18.1 스레드와 코루틴 비교 스레드 및 GIL에 대해 설명하면서 미셸 시미오나토Michele Simionato는 장시간 연산이 실행되는 동 안 multiprocessing 패키지를 이용해서 콘솔에 ‘|/-\’ 아스키 문자로 스피너spinner 애니메이션 을 보여주는 간단하고 재미있는 예제(http://bit.ly/1Ox3vWA )를 올렸다. 필자는 시미오나토의 예제를 약간 수정했다. 먼저 threading 모듈의 스레드를 이용해서 구현한 후 다시 asyncio 모듈의 코루틴을 이용해서 구현함으로써 스레드 없이 코루틴이 동시 동작을 어 떻게 하는지 코드를 비교해서 볼 수 있게 했다. [예제 18-1]과 [예제 18-2]를 실행하면 애니메이션을 볼 수 있으므로, 이 코드가 어떻게 작동 하는지 알아보려면 실제로 코드를 실행해보는 것이 좋다. 만약 지하철에서 이 책을 보고 있다 면(혹은 와이파이로 연결되어 있지 않다면), [그림 18-1]을 보고 ‘thinking!’ 단어 앞의 \ 막대 가 돌아가는 것을 상상해보라. 그림 18-1 비슷한 출력을 생성하는 spinner_thread.py와 spinner_asyncio.py 스크립트. 스피너 객체spinner object 와 ‘Answer : 42’라는 텍스트가 출력된다. 현재 spinner_asyncio.py가 실행 중인데, ‘ \ thinking !’이라는 스피너 메시지 가 나타난 상태다. 이 스크립트도 실행을 마치면 ‘Answer: 42’라는 메시지를 출력한다.4

3 옮긴이_ 파이썬 3.5에 async와 await 키워드가 추가되었다. 18장에서 설명하는 예제를 async와 await 키워드를 사용해서 구현한 버전은 내려받은 파일의 18b-async-await/ 폴더에서 볼 수 있다. 4 옮긴이_ 이 코드를 파이썬 IDLE에서 실행하면 백스페이스 문자가 작동하지 않는다. 터미널이나 명령행 프롬프트에서 python 명령으로 실행해야 한다.

18장 asyncio를 이용한 동시성 653


먼저 spinner_thread.py 스크립트를 살펴보자(예제 18-1 ). 예제 18-1 spinner_thread.py : 스레드로 텍스트 스피너 애니메이트하기

import import import import

threading itertools time sys

class Signal: go = True

def spin(msg, signal): ➋ write, flush = sys.stdout.write, sys.stdout.flush for char in itertools.cycle('|/-\\'): ➌ status = char + ' ' + msg write(status) flush() write('\x08' * len(status)) ➍ time.sleep(.1) if not signal.go: ➎ break write(' ' * len(status) + '\x08' * len(status)) ➏ def slow_function(): ➐ # 입출력을 위해 장시간 기다리는 것처럼 보이게 만든다. time.sleep(3) ➑ return 42 def supervisor(): ➒ signal = Signal() spinner = threading.Thread(target=spin, args=('thinking!', signal)) print('spinner object:', spinner) ➓ spinner.start() result = slow_function()  signal.go = False  spinner.join()  return result def main(): result = supervisor()  print('Answer:', result)

654 5부 제어 흐름


if __name__ == '__main__': main()

➊ 이 클래스는 외부에서 스레드를 제어하기 위해 사용할 go 속성 하나만 있는 간단한 가변 객체를 정의한다. ➋ 이 함수는 별도의 스레드에서 실행된다. signal 인수는 바로 앞에서 정의한 Signal 클래스의 객체를 받

는다. ➌ itertools.cycle ( )은 주어진 시퀀스를 순환하면서 끝없이 항목을 생성하므로, 이 for 루프는 사실상

무한 루프다. ➍ 텍스트 모드 애니메이션 기법으로서, 문자열의 길이만큼 백스페이스 문자(\08 )를 반복해서 커서를 앞으로

이동시킨다. ➎ go 속성이 True가 아니면 루프를 빠져나온다. ➏ 공백 문자로 덮어쓰고 다시 커서를 처음으로 이동해서 메시지 출력 행을 청소한다. ➐ 실행에 시간이 오래 걸리는 함수라고 생각하자. ➑ 주 스레드에서 sleep ( ) 함수를 호출할 때

GIL이 해제되므로 두 번째 스레드가 진행된다.

➒ 이 함수는 두 번째 스레드를 만들고, 스레드 객체를 출력하고, 시간이 오래 걸리는 연산을 수행하고 나서 스

레드를 제거한다. ➓ 두 번째 스레드 객체를 출력한다. <Thread (Thread -1, initial ) >과 같은 형태로 출력된다. 두 번째 스레드를 실행한다.  slow_function ( ) 함수를 실행한다. 그러면 주 스레드가 블로킹되고, 그 동안 두 번째 스레드가 텍스트

스피너 애니메이션을 보여준다.  signal의 상태를 변경한다. 그러면 spin ( ) 함수 안의 for 루프가 중단된다.  spinner 스레드가 끝날 때까지 기다린다.  supervisor ( ) 함수를 실행한다.

파이썬에는 스레드를 종료시키는 API가 정의되어 있지 않음에 주의하라. 스레드에 메시지를 보 내 종료시켜야 한다. 여기서는 signal.go 속성을 사용했다. 주 스레드가 이 속성을 False로 설 정하면, spinner 스레드가 이 값을 확인하고 깔끔하게 종료한다. 이제 스레드 대신 @asyncio.coroutine을 이용해서 동일한 동작을 어떻게 구현할 수 있는지 알 아보자.

18장 asyncio를 이용한 동시성 655


NOTE_ 16.10절 ‘요약’에서 설명한 것처럼 asyncio는 ‘코루틴’을 더욱 엄격히 정의한다. asyncio API에

사용할 코루틴은 본체 안에서 yield가 아니라 yield from을 사용해야 한다. 그리고 asyncio 코루틴은 yield from으로 호출하는 호출자에 의해 구동되거나, 이 장에서 설명할 asyncio.async ( ) 등의 함수에 전달해서 구동해야 한다. 마지막으로, 예제에서 보여주는 것처럼 @asyncio.coroutine 데커레이터를 코루 틴에 적용해야 한다.

[예제 18-2]를 보자. 예제 18-2 spinner_asyncio.py : 코루틴으로 텍스트 스피너 애니메이트하기

import asyncio import itertools import sys @asyncio.coroutine ➊ def spin(msg): ➋ write, flush = sys.stdout.write, sys.stdout.flush for char in itertools.cycle('|/-\\'): status = char + ' ' + msg write(status) flush() write('\x08' * len(status)) try: yield from asyncio.sleep(.1) ➌ except asyncio.CancelledError: ➍ break write(' ' * len(status) + '\x08' * len(status)) @asyncio.coroutine def slow_function(): ➎ # 입출력을 위해 장시간 기다리는 것처럼 보이게 만든다. yield from asyncio.sleep(3) ➏ return 42 @asyncio.coroutine def supervisor(): ➐ spinner = asyncio.async(spin('thinking!')) print('spinner object:', spinner) ➒ result = yield from slow_function() ➓ spinner.cancel() return result

656 5부 제어 흐름


def main(): loop = asyncio.get_event_loop()  result = loop.run_until_complete(supervisor()) loop.close() print('Answer:', result)

if __name__ == '__main__': main()

➊ asyncio에 사용할 코루틴은 @asyncio.coroutine으로 데커레이트해야 한다. 반드시 해야 하는 것은 아

니지만, 되도록 사용하라고 권장한다. 코드 설명을 마친 뒤 이 데커레이터에 대해 알아본다. ➋ 여기에서는 [예제 18 -1]의 spin ( ) 함수에서 스레드를 종료하기 위해 사용했던 signal 인수가 필요 없다. ➌ 이벤트 루프를 블로킹하지 않고 잠자기 위해 time.sleep (.1 ) 대신 yield from asyncio.sleep (.1 )

을 사용한다. ➍ spin ( )이 깨어난 후 asyncio.CancelledError 예외가 발생하면, 취소가 요청된 것이므로 루프를 종료

한다. ➎ slow_function ( )은 이제 코루틴으로서, 코루틴이 잠자면서 입출력을 수행하는 체 하는 동안 이벤트 루

프가 진행될 수 있게 하기 위해 yield from을 사용한다. ➏ yield from asyncio.sleep (3 ) 표현식은 메인 루프의 제어 흐름을 처리하는데, 메인 루프는 잠자고 난

후에 코루틴을 계속 실행한다. ➐ 이제는 supervisor ( )도 코루틴이므로, yield from을 이용해서 slow_function ( )을 구동할 수 있다. ➑ asyncio.async ( )는 spin ( ) 코루틴의 실행을 스케줄링하고 Task 객체 안에 넣어, Task 객체를 즉시

반환한다. ➒ Task 객체를 출력한다. <Task pending coro=<spin ( ) running at spinner_asyncio.py :12>>

와 같은 메시지가 출력된다. ➓ slow_function ( ) 함수를 구동해서 완료되면 반환된 값을 가져온다. 그러는 동안 이벤트 루프는 계속 실

행된다. slow_function ( )이 궁극적으로 yield from asyncio.sleep (3 )을 실행해서 메인 루프로 제어권을 넘기기 때문이다. Task 객체는 cancel ( ) 메서드를 호출해서 취소할 수 있다. 그러면 코루틴이 중단된 곳의 yield from에

서 asyncio.CancelledError 예외가 발생한다. 코루틴은 예외를 잡아서 지연시키거나 취소 요청을 거부 할 수 있다.  이벤트 루프에 대한 참조를 가져온다.  supervisor ( ) 코루틴을 구동해서 완료한다. 코루틴의 반환값은 run_until_complete ( ) 메서드의 반

환값이 된다.

18장 asyncio를 이용한 동시성 657


CAUTION_ 주 스레드를 블로킹해서 이벤트 루프를 중단시키고 그래서 애플리케이션 전체를 멈추고 싶은

경우가 아니라면, 결코 asyncio 코루틴 안에서 time.sleep ( )을 호출하지 말라. 코루틴 안에서 아무 것도 안 하고 잠시 시간을 보내고 싶으면 yield from asyncio.sleep (<초> )를 사용해야 한다.

반드시 @asyncio.coroutine 데커레이터를 사용해야 하는 것은 아니지만, 되도록이면 사용 하라고 강력히 권고한다. @asyncio.coroutine은 코루틴을 일반 함수와 다르게 보이도록 만 들며, 코루틴이 yield from되지 않고(즉, 일부 작업이 완료되지 않았으므로, 버그가 발생할 가능성이 높다) 가비지 컬렉트되는 경우 경고 메시지를 출력하므로 디버깅에 도움이 된다. @asyncio.coroutine은 데커레이트된 제너레이터를 자동으로 기동하지 않는다.

spinner_thread.py와 spinner_asyncio.py의 소스 코드 길이가 거의 비슷하다는 점에 주목 하라. supervisor ( ) 함수는 이 예제의 핵심이다. 이제부터 이 함수를 자세히 비교해보자. [예제

18-3]은 스레드 예제에서 supervisor ( ) 함수만 가져온 것이다. 예제 18-3 spinner_thread.py : 스레드화된 supervisor ( ) 함수

def supervisor(): signal = Signal() spinner = threading.Thread(target=spin, args=('thinking!', signal)) print('spinner object:', spinner) spinner.start() result = slow_function() signal.go = False spinner.join() return result

이와 비교하기 위해 [예제 18-4]에는 supervisor ( ) 코루틴만 가져왔다. 예제 18-4 spinner_asyncio.py : 비동기 supervisor ( ) 코루틴

@asyncio.coroutine def supervisor(): spinner = asyncio.async(spin('thinking!')) print('spinner object:', spinner) result = yield from slow_function() spinner.cancel() return result

658 5부 제어 흐름


이 두 supervisor ( ) 함수의 주요 차이점을 정리하면 다음과 같다. asyncio.Task는 threading.Thread와 거의 대등하다. 이 장의 특별 테크니컬 리뷰어인 빅터 스티너에

의하면 Task는 gevent 같은 협업적 멀티태스킹을 구현하는 라이브러리에서의 그린 스레드green thread 5 와 같다. Task는 코루틴을 구동하고, Thread는 콜러블을 호출한다.

Task 객체는 직접 생성하지 않고, 코루틴을 asyncio.async ( )나 loop.create_task ( )에 전달해서

가져온다. Task 객체를 가져오면, 이 객체는 이미 asyncio.async ( ) 등에 의해 실행이 스케줄링되어 있다. Thread

객체는 start ( ) 메서드를 호출해서 실행하라고 명령해야 한다. 스레드화된 supervisor ( )에서 slow_function ( )은 평범한 함수로서, 스레드에 의해 직접 호출된다.

비동기 supervisor ( )에서 slow_function ( )은 yield from으로 구동하는 코루틴이다. 스레드는 외부에서 API를 이용해서 중단시킬 수 없다. 스레드를 아무 때나 중단시키면 시스템 상태의 무결

성이 훼손되기 때문이다. Task에는 코루틴 안에서 CancelledError를 발생시키는 Task.cancel ( ) 객 체 메서드가 있다. 코루틴은 중단되었던 yield 문에서 예외를 잡아서 처리할 수 있다. supervisor ( ) 코루틴은 main ( ) 함수 안에서 loop.run_until_complete ( )로 실행해야 한다.

이렇게 익숙한 threading 모듈과 하나하나 비교하면, asyncio가 동시성 작업을 어떻게 지휘하 는지 이해하는 데 도움이 된다. 스레드와 코루틴의 비교와 관련해서 마지막으로 한 가지만 더 이야기하자. 스레드로 복잡한 프 로그램을 구현해봤다면 스케줄러가 언제든 스레드를 중단시킬 수 있으므로 프로그램을 분석하 는 작업이 얼마나 힘든지 잘 알 것이다. 프로그램의 크리티컬 섹션을 보호하기 위해 락을 잠그 고, 여러 단계의 작업을 수행하는 도중에 인터럽트되지 않게 해야 한다. 크리티컬 섹션 도중에 인터럽트되면 데이터가 잘못된 상태에 놓일 수 있기 때문이다. 코루틴의 경우, 모든 것이 기본적으로 인터럽트로부터 보호된다. 명시적으로 yield를 실행해 야 프로그램의 다른 부분이 실행된다. 여러 스레드의 연산을 동기화하기 위해 락을 잠그는 대 신, 언제든 실행되고 있는 코루틴 중 하나만 사용하면 된다. 그리고 제어권을 넘겨주고 싶을 때 는 yield나 yield from을 이용해서 스케줄러에 넘겨줄 수 있다. 그렇기 때문에 코루틴은 안전 하게 취소할 수 있다. 코루틴은 yield 지점에서 중단되었을 때만 취소할 수 있다고 정의되어 있 으므로, 단지 CancelledError 예외를 처리해서 마무리하면 된다.

5 옮긴이_ OS가 아니라 런타임 라이브러리나 가상 머신에서 스케줄링되는 스레드

18장 asyncio를 이용한 동시성 659


이제 asyncio.Future 클래스가 17장에서 본 concurrent.futures.Future 클래스와 어떻게 다른지 알아보자.

18.1.1 asyncio.Future: 논블로킹 설계 asyncio.Future와 concurrent.futures.Future 클래스는 인터페이스가 거의 같지만, 다

르게 구현되어 있으므로 서로 바꿔 쓸 수 없다. ‘PEP-3156 - 비동기 입출력 지원 재시동:

asyncio 모듈Asynchronous IO Support Rebooted: the “asyncio” Module ’(https://www.python.org/dev/ peps/pep-3156/)은 이런 불행한 상황에 대해 다음과 같이 이야기한다. 언젠가 concurrent.futures.Future에 __iter__( ) 메서드를 추가해서 yield from에 사용할 수 있 게 하는 등 asyncio.Future와 concurrent.futures.Future를 통합할 수도 있다.

17.1.3절 ‘Future는 어디에 있나?’에서 이야기한 것처럼 Future는 실행할 코드를 스케줄링 해야 생성된다. asyncio에서 BaseEventLoop.create_task ( ) 메서드는 코루틴을 받아서 실 행하기 위해 스케줄링하고, asyncio.Task 객체를 반환한다. Task는 코루틴을 래핑하기 위 해 설계된 Future의 서브클래스이므로, Task 객체는 Future 객체이기도 하다. 이 과정은 Executor.submit ( )을 호출해서 concurrent.futures.Future 객체를 생성하는 방법과 비슷

하다. 단짝인 concurrent.futures.Future 클래스와 마찬가지로 asyncio.Future 클래스도 done ( ) , add_done_callback ( ) , result ( ) 등의 메서드를 제공한다. 앞의 두 메서드는

17.1.3절 ‘Future는 어디에 있나?’에서 설명한 대로 작동하지만, result ( )는 아주 다르다. asyncio.Future에서 result ( ) 메서드는 인수를 받지 않으므로 시간초과를 지정할 수 없다.

그리고 아직 실행이 완료되지 않은 Future 객체의 result ( ) 메서드를 호출하면, 결과를 기다 리느라 블로킹되는 대신 asyncio.InvalidStateError 예외가 발생한다. 그러나 asyncio.Future에서 결과를 가져오기 위해서는 [예제 18-8]처럼 일반적으로 yield from을 이용한다. Future 객체에 yield from을 호출하면 이벤트 루프를 블로킹하지 않고 작업 완료를 기다리는

과정을 자동으로 처리해준다. asyncio에서 yield from은 이벤트 루프에 제어권을 넘겨주기 위 해 사용하기 때문이다.

660 5부 제어 흐름


Future 객체에 yield from을 사용하는 것은 코루틴에 add_done_callback ( ) 함수를 호출하

는 것과 비슷하다. 콜백을 호출하지 않고 지연된 작업이 완료되면, 이벤트 루프는 Future 객체 의 결과를 설정하고 yield from 표현식은 지연된 코루틴 내부에서 반환된 값을 생성하고 실행 을 계속 진행한다. 정리하면, asyncio.Future는 yield from과 함께 사용하도록 설계되었으므로 다음과 같은 메 서드가 필요 없다. 코루틴 안에서 my_future가 실행을 완료한 다음에 수행할 작업은 단순히 yield from my_future 뒤에

넣으면 되므로 my_future.add_done_callback ( )을 호출할 필요가 없다. 이것은 중단했다가 재개할 수 있는 함수라는 코루틴의 커다란 장점이다. my_future에 대한 yield from 표현식의 값이 result가 되므로(예를 들면 result = yield from

my_future ) my_future.result ( )를 호출할 필요 없다.

물론 done ( ), add_done_callback ( ), result ( ) 메서드가 필요한 경우도 있지만, 일반적으 로 asyncio의 Future 객체는 이런 메서드를 호출하지 않고, yield from으로 구동된다. 이제 yield from과 asyncio API가 Future, Task, 코루틴을 어떻게 통합하는지 알아보자.

18.1.2 Future, Task, 코루틴에서 생성하기 asyncio에서는 yield from을 이용해서 asyncio.Future 객체의 결과를 가져올 수 있으므로 Future와 코루틴의 관계는 밀접하다. 이는 foo ( )가 코루틴 함수거나(즉, 호출되면 코루틴 객

체를 반환한다), Future나 Task 객체를 반환하는 일반 함수면, res = yield from foo ( ) 코 드가 작동한다는 것을 의미한다. 그렇기 때문에 asyncio API에서 코루틴과 Future 객체를 바꿔가면서 쓸 수 있는 경우가 많다. 실행하려면 반드시 코루틴을 스케줄링해야 하며, 그러고 나서 코루틴이 asyncio.Task 객체 안 에 래핑된다. 코루틴을 받아서 Task 객체를 가져오기 위해서는 주로 다음과 같은 두 가지 방법을 사용한다.

asyncio.async(coro_or_future, *, loop= None) 이 함수는 코루틴과 Future 객체를 통합한다. 첫 번째 인수로는 둘 중 아무거나 올 수 있다. Future 나 Task 객체면 그대로 반환된다. 코루틴이면 async ( )가 loop.create_task ( )를 호출해서 Task를

18장 asyncio를 이용한 동시성 661


생성한다. loop 키워드 인수에 이벤트 루프를 전달할 수도 있다. 생략하면 async ( )가 asyncio. get_event_loop ( )를 호출해서 루프 객체를 가져온다.

BaseEventLoop.create_task(coro) 이 메서드는 코루틴을 실행하기 위해 스케줄링하고 asyncio.Task 객체를 반환한다. Tornado 등 의 외부 라이브러리에서 제공하는 BaseEventLoop의 서브클래스에 호출하면, 외부 라이브러리에 서 제공하는 Task와 호환되는 클래스의 객체가 반환될 수도 있다.

CAUTION_ BaseEventLoop.create_task ( )는 파이썬 3.4.2부터 사용할 수 있다. 파이썬 3.3이나 3.4

등 이전 버전의 파이썬을 사용하고 있다면 asyncio.async ( )를 사용하거나, PyPI (https://pypi.python.

org/pypi/asyncio )에서 asyncio 최신 버전을 설치해야 한다.

내부적으로 asyncio.async ( ) 를 이용해서 받은 코루틴을 자동으로 asyncio.Task 객체 안 에 래핑하는 asyncio 함수들이 많이 있다. 그중 대표적인 것이 BaseEventLoop.run_until_ complete ( )다.

파이썬 콘솔이나 간단한 테스트에서 Future 객체나 코루틴을 실험하고 싶다면 다음 코드를 사용 한다.6 import asyncio def run_sync(coro_or_future): loop = asyncio.get_event_loop() ... return loop.run_until_complete(coro_or_future) ... ... >>> a = run_sync(some_coroutine()) >>> >>>

코루틴, Future, Task의 관계는 asyncio 문서의 18.5.3절 ‘태스크와 코루틴Tasks and coroutines ’ (https://docs.python.org/3/library/asyncio-task.html )에서 다음과 같이 설명하고 있다. 이 문서에는 Future 객체를 반환하는 평범한 파이썬 함수임에도 불구하고 코루틴이라고 부르는 메서드들 이 종종 있다. 이것은 의도적인 것으로서, 향후에 이 함수들의 구현을 변경할 여지를 남겨두고자 한 것이다.

기본적인 사항을 어느 정도 설명했으니, 이제 [예제 17-1]에서 순차 및 스레드 풀 스크립트와 함

6 2014년 9월 11일 Python-ideas 메일링 리스트에 페트르 빅토린(Petr Viktorin )이 제안한 방법(http://bit.ly/1JIwJmc )

662 5부 제어 흐름


께 테스트했던, 비동기식으로 국기 이미지를 내려받는 flags_asyncio.py 스크립트의 소스 코 드를 살펴보자.

18.2 asyncio와 aiohttp로 내려받기 파이썬 3.4에서 asyncio는 TCP와 UDP만 직접 지원한다. HTTP 등의 프로토콜을 지원하려 면 서드파티 패키지가 필요하다. 현재 비동기 HTTP 클라이언트/서버를 구현하는 사람들은 모 두 aiohttp를 사용하는 것 같다. [예제 18-5]는 국기 이미지를 내려받는 flags_asyncio.py 스크립트의 전체 소스 코드다. 코드 의 전반적인 흐름은 다음과 같다. 1 download_one ( )을 호출해서 생성된 여러 코루틴 객체를 이벤트 루프에 넣어 download_many ( ) 안에

서 프로세스를 시작한다. asyncio 이벤트 루프는 각각의 코루틴을 차례대로 활성화한다. 2 3 get_flag ( ) 등의 클라이언트 코루틴이 aiohttp.request ( ) 등의 라이브러리 코루틴에 위임하기 위해

yield from을 사용하면, 제어권이 이벤트 루프로 넘어가서, 이벤트 루프가 이전에 스케줄링된 다른 코루 틴을 실행할 수 있게 된다. 4 블로킹된 연산이 완료되었을 때 통지받기 위해, 이벤트 루프는 콜백에 기반한 저수준 API를 사용한다. 5 연산이 완료되면 메인 루프는 결과를 중단된 코루틴에 보낸다. 6 그러고 나면 코루틴이 예를 들면 get_flag ( )의 yield from resp.read ( )와 같은 다음 yield from

문으로 넘어간다. 이제 이벤트 루프가 다시 제어권을 가져오고, 종료될 때까지 4, 5, 6단계를 반복한다.

이 과정은 16.9.2절 ‘택시 집단 시뮬레이션’에서 메인 루프가 여러 택시 프로세스를 차례대로 실행했던 예제와 비슷하다. 각 택시 프로세스가 yield를 실행하면, 메인 루프는 그 택시에 대한 다음 이벤트를 스케줄링하고, 계속해서 큐에 들어 있는 다음 택시를 활성화한다. 택시 시뮬레이 션은 훨씬 더 단순하므로 메인 루프를 쉽게 이해할 수 있다. 그러나 전체 흐름은 asyncio에서 도 동일하다. 단지 메인 루프가 큐에 들어 있는 코루틴을 하나씩 활성화하는 단일 스레드 프로그 램일 뿐이다. 각 코루틴이 몇 단계 진행하고 나서, 메인 루프에 제어권을 넘기고, 그러고 나면 메 인 루프가 큐에 들어 있는 다음 코루틴을 활성화한다. 이제 [예제 18-5]의 코드를 하나하나 살펴보자.

18장 asyncio를 이용한 동시성 663


예제 18-5 flags_asyncio.py : asyncio와 aiohttp를 사용한 비동기 내려받기 스크립트

import asyncio import aiohttp

from flags import BASE_URL, save_flag, show, main

@asyncio.coroutine ➌ def get_flag(cc): url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower()) resp = yield from aiohttp.request('GET', url) ➍ image = yield from resp.read() ➎ return image @asyncio.coroutine def download_one(cc): ➏ image = yield from get_flag(cc) ➐ show(cc) save_flag(image, cc.lower() + '.gif') return cc def download_many(cc_list): loop = asyncio.get_event_loop() ➑ to_do = [download_one(cc) for cc in sorted(cc_list)] wait_coro = asyncio.wait(to_do) ➓ res, _ = loop.run_until_complete(wait_coro) loop.close() 

return len(res) if __name__ == '__main__': main(download_many)

➊ aiohttp는 기본 라이브러리에 들어 있지 않으므로 별도로 설치해야 한다. ➋ flags 모듈(예제

17 -2 )에서 구현한 일부 함수를 재사용한다.

➌ 코루틴은 @asyncio.coroutine으로 데커레이트해야 한다. ➍ 블로킹 연산은 코루틴으로 구현되었고, yield from을 이용해서 이 코루틴에 위임하면, 코루틴이 비동기식

으로 실행된다. ➎ 응답 내용을 읽는 것은 별도의 비동기 연산에서 구현한다. ➏ yield from을 사용하는 download_one ( ) 함수도 코루틴이어야 한다.

664 5부 제어 흐름


➐ 단어들을 yield from으로 가져온다는 것이 순차 버전의 download_one ( )과의 유일한 차이점이다. 이

함수의 나머지 부분은 기존과 완전히 동일하다. ➑ 하위 이벤트 루프 구현에 대한 참조를 가져온다. ➒ 국기 이미지를 가져올 때마다 download_one ( ) 함수를 호출해서 제너레이터 객체 리스트를 생성한다. ➓ 이름에도 불구하고 wait ( )는 블로킹 함수가 아니다. 일종의 코루틴으로서 자신에게 전달된 코루틴들이

모두 완료되면 완료된다(이것이 바로 wait ( )의 기본 작동 방식이다. 소스 코드 설명을 마치고 나서 자세 히 설명한다). wait_coro ( )가 완료될 때까지 이벤트 루프를 실행한다. 이 부분은 이벤트 루프가 실행하는 동안 블로킹된다.  이벤트 루프를 종료한다.

NOTE_ 이벤트 루프 객체가 콘텍스트 관리자면 with 블록을 이용해서 이벤트 루프 종료를 보장할 수 있으

므로 좋을 것이다. 그러나 호출자 코드가 이벤트 루프를 직접 생성하지 않고 asyncio.get_event_loop ( ) 를 호출해서 이벤트 루프에 대한 참조를 가져오므로 상황이 복잡하다. 때로는 호출 코드가 이벤트 루프를 ‘소 유’하지 않을 수도 있으므로 종료하면 안 된다. 예를 들어 Quamash 패키지(https://pypi.python.org/pypi/

Quamash/)를 이용해서 외부 GUI 이벤트 루프를 사용하는 경우에는 애플리케이션을 종료할 때 Qt 라이브 러리가 이벤트 루프를 닫아야 한다.

asyncio.wait ( ) 코루틴은 Future 객체나 코루틴의 반복형을 받고, wait ( )는 각 코루틴을 Task 안에 래핑한다. 결국 wait ( )가 관리하는 모든 객체는 Future 객체가 된다. wait ( )는 코

루틴 함수이기 때문에 이를 호출하면 코루틴/제너레이터 객체가 반환된다. wait_coro 변수에 들어 있는 게 바로 wait ( )가 반환한 코루틴 제너레이터 객체다. 이 코루틴을 구동하기 위해 그 것을 loop.run_until_complete ( )에 전달한다. loop.run_until_complete ( ) 함수는 Future 객체나 코루틴을 받는다. 코루틴을 받으면 wait ( )가 하는 것과 비슷하게 run_until_complete ( )도 코루틴을 Task 안에 래핑한다. 코

루틴, Future, Task는 모두 yield from으로 구동할 수 있으며, 이것이 바로 wait ( )가 반환 한 wait_coro 객체에 run_until_complete ( )가 하는 일이다. wait_coro는 실행이 완료되면 (<실행 완료된 Future들의 집합>,<실행이 완료되지 않은 Future들의 집합> ) 튜플을 반환한다.

[예제 18-5]에서 두 번째 집합은 언제나 공집합이므로 언더바(_ )에 할당해서 명백히 무시한 다. 그러나 wait ( )는 일부 Future 객체가 완료되지 않았더라도 반환하게 만드는 timeout과 return_when 키워드 전용 인수를 받는다. 자세한 사항은 asyncio.wait ( ) 문서( http://bit.

ly/1JIwZS2 )를 참조하라.

18장 asyncio를 이용한 동시성 665


[예제 18-5]에서는 flags.py (예제 17-2 )의 get_flag ( ) 함수를 재사용할 수 없었다는 점에 주의하라. get_flag ( ) 함수는 블로킹 입출력을 수행하는 requests 라이브러리를 사용하기 때문이다. asyncio를 활용하기 위해 네트워크에 접속하는 모든 함수를 yield from으로 호출하 는 비동기 버전으로 바꿔서 제어권을 다시 이벤트 루프로 넘겨야 한다. get_flag ( )에서 yield from을 사용한다는 것은 get_flag ( )가 코루틴으로 구동되어야 한다는 것을 의미한다.

그렇기 때문에 flags_threadpool.py (예제 17-3 )의 download_one ( ) 함수도 재사용할 수 없 었다. [예제 18-5]가 yield from으로 get_flag ( )를 구동하므로 download_one ( ) 자체도 코 루틴이다. 매번 요청할 때마다 download_one ( ) 코루틴 객체가 download_many ( ) 안에서 생 성되고, 이 코루틴 객체는 asyncio.wait ( ) 코루틴에 의해 래핑된 후, 모두 loop.run_until_ complete ( ) 함수에 의해 구동된다. asyncio에 대해 알아야 할 새로운 개념이 많지만, 귀도 반 로섬이 제안한 비결을 사용하면 [예

제 18-5]의 전반적인 논리를 쉽게 이해할 수 있다. 바로 ‘실눈을 뜨고 보면서 yield from 키워 드가 없는 것처럼 생각’하면 된다. 이렇게 하면 처음 구현한 평범한 순차 버전의 코드처럼 읽기 쉬워진다. 예를 들어 다음과 같은 코루틴이 있다고 가정하자. @asyncio.coroutine def get_flag(cc): url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower()) resp = yield from aiohttp.request('GET', url) image = yield from resp.read() return image

위 코드는 블로킹되지 않는다는 점만 제외하면 다음 코드와 똑같이 작동한다. def get_flag(cc): url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower()) resp = aiohttp.request('GET', url) image = resp.read() return image

666 5부 제어 흐름


yield from foo 구문을 사용하면 현재의 코루틴(즉, yield from 코드가 있는 대표 제너레이

터)이 중단되지만, 제어권이 이벤트 루프로 넘어가고 이벤트 루프가 다른 코루틴을 구동할 수 있게 되므로, 블로킹되지 않는다. foo가 Future 객체이든 코루틴이든 이 객체가 완료되면, 결 과를 중단된 코루틴으로 반환해서 실행을 계속하게 만든다.

16.7절 ‘yield from 사용하기’의 끝부분에서 yield from의 사용법에 대한 두 가지 사실을 이 야기했다. 여기서 다시 요약하면 다음과 같다. yield from으로 연결된 전체 코루틴 체인은 궁극적으로 가장 바깥쪽에 있는 대표 제너레이터의 next ( )

나 send ( )를 명시적 혹은 암묵적(for 루프에 사용하는 경우)으로 호출하는 비코루틴 호출자에 의해 구 동된다. 이 체인 가장 안쪽에 있는 하위 제너레이터는 단지 yield를 사용하는 단순 제너레이터이거나 반복형 객체

여야 한다.

asyncio API와 함께 yield from을 사용할 때도 이 사실은 유효하며, 다음과 같은 특징이 있다. 우리가 만든 코루틴 체인은 언제나 가장 바깥쪽 대표 제너레이터를 loop.run_until_complete ( ) 등의

asyncio API에 전달함으로써 구동된다. 즉, asyncio를 사용할 때는 next ( )나 send ( ) 메서드를 직접 호출해서 코루틴 체인을 구동하는 것이 아니라, asyncio 이벤트 루프가 처리하도록 해야 한다. 우리가 만든 코루틴 체인은 언제나 어떤 asyncio 코루틴 함수나 코루틴 메서드([예제 18 -2]의 yield

from asyncio.sleep ( ) ), 혹은 상위 수준 프로토콜을 구현하는 라이브러리의 코루틴([예제 18 - 5] get_flag ( ) 코루틴 안의 resp = yield from aiohttp.request ('GET', url ) )에 yield from을 호출하면서 끝나야 한다. 즉, 가장 안쪽의 하위제너레이터는 우리가 만든 코드가 아니라, 실제로 입출력을 수행하는 라이브러리 함수여야 한다.

요약하자면, asyncio를 사용할 때 우리는 코루틴 체인을 만들며, 가장 바깥쪽 대표 제너레이터 는 asyncio 자체에 의해 구동되며, 체인을 통해 궁극적으로 가장 안쪽에 있는 하위 제너레이터 는 (aiohttp 등의 서드파티 라이브러리를 경유해서) asyncio 라이브러리가 제공하는 코루틴 에 위임한다. 즉, asyncio 이벤트 루프가 코루틴 체인을 구동하고, 코루틴 체인은 결국 저수준 비동기 입출력을 수행하는 라이브러리 함수에서 끝난다. 이제 17장에서 제기한 다음 문제에 답할 준비가 된 것 같다. 둘 다 단일 스레드로 실행되는데, 어떻게 flags_asyncio.py가 flags.py보다 5배나 빨리 실행될까?

18장 asyncio를 이용한 동시성 667


18.3 블로킹 호출을 에둘러 실행하기 Node.js의 창시자인 라이언 달Ryan Dahl 은 ‘우리는 입출력을 완전히 잘못하고 있다’고 말하면서 그의 프로젝트 철학을 소개한다.7 그는 블로킹 함수를 디스크나 네트워크 입출력의 수행으로 정의하면서, 이 함수들을 논블로킹 함수처럼 다루면 안 된다고 말한다. 이유를 설명하기 위해 그는 [표 18-1]의 왼쪽 두 개의 열을 보여준다. 표 18-1 최신 컴퓨터의 여러 장치에서 데이터를 읽는 데 걸리는 지연 시간. 세 번째 열은 우리가 느끼는 체감 규모로 바 꾸기 위해 필자가 추가한 것이다. 장치

CPU 사이클 수

비례 ‘체감’ 규모

L1 캐시

3

3초

L2 캐시

14

14초

250

250초

디스크

41,000,000

1.3년

네트워크

240,000,000

7.6년

[표 18-1]을 볼 때 최신 CPU는 GHz 대역의 클록으로 작동하므로 초당 수십억 개의 사이클 을 실행한다는 점에 주의하라. 가령 CPU가 초당 10억 개의 사이클을 실행한다고 가정하자. 그 CPU는 1초에 L1 캐시는 333,333,333번 읽을 수 있고, 네트워크는 4번 읽을 수 있다. [표

18-1]의 세 번째 열은 두 번째 열을 초 단위로 변환한 것이다. 따라서 사람이 느끼기에 L1 캐 시를 읽는 데 3초가 걸린다면, 네트워크에서 읽는 데는 7.6년이 걸린다! 블로킹 함수가 전체 애플리케이션의 실행을 멈추지 않게 하는 두 가지 방법이 있다. 블로킹 연산을 각기 별도의 스레드에서 실행한다.

모든 블로킹 연산을 논블로킹 비동기 연산으로 바꾼다.

스레드는 제대로 작동하지만, 파이썬이 사용하는 OS 스레드는 (OS에 따라 다르지만) 각기 수 메가바이트의 메모리를 사용한다. 수천 개의 연결을 처리해야 한다면 연결마다 하나의 스레드를 사용할 수 없다. 전통적으로 메모리 부담을 줄이기 위해 콜백으로 비동기 호출을 구현했다. 개념적으로 보면 하 드웨어 인터럽트와 비슷한 저수준 개념이다. 응답을 기다리는 대신, 어떤 일이 발생할 때 호출될

7 ‘Node.js 소개(Introduction to Node.js )’ 동영상(https://www.youtube.com/watch?v=M-sc73Y-zQA )에서 4:55분 위치

668 5부 제어 흐름


CHAPTER

21

클래스 메타프로그래밍

메타클래스는 99%의 사용자가 신경 써야 하는 것보다 훨씬 더 깊이 있는 마술이다. 필요한 것일까 하는 의문이 든다면, 여러분에게 메타클래스는 필요 없다 (실제로 메타클래스가 필요한 사람은 자신에게 메타클래스가 필요하다는 것을 명확히 알고 있으며, 이유를 설명할 필요가 없다).1 _ 팀 피터스 팀정렬 알고리즘의 고안자, 다수의 파이썬 프로젝트 참여자

클래스 메타프로그래밍은 실행 도중에 클래스를 생성하거나 커스터마이즈하는 기술을 말한다. 클래스는 파이썬의 일급 객체이므로, class라는 키워드를 사용하지 않고도 언제든 함수를 사용 해서 생성할 수 있다. 클래스 데커레이터도 함수지만, 장식된 클래스를 조사하고, 변경하고, 심 지어 다른 클래스로 대체할 수 있다. 끝으로, 메타클래스는 클래스 메타프로그래밍을 하기 위 한 최첨단 도구로서, 우리가 이미 살펴본 추상 베이스 클래스처럼 특별한 기질이 있는 완전히 새 로운 부류의 클래스를 만들 수 있게 해준다. 메타클래스는 강력하지만, 제대로 사용하기는 어렵다. 클래스 데커레이터는 이와 같은 문제의 상당 부분을 간단히 해결한다. 사실 메타클래스는 실제 코딩에서는 정당화하기 아주 어려우며, 파이썬 2.6에 클래스 데커레이터가 등장함으로써 필자가 즐겨 사용하던 흥미로운 예제들은 매 력을 잃어버렸다. 그리고 여기서는 임포트 타임과 런타임을 구분한다. 파이썬에서 효율적으로 메타프로그래밍을 하기 위해서는 반드시 이 차이를 구분할 수 있어야 한다.

1 comp.lang.python에 올라온 ‘C 언어 프로그래밍에 대한 통렬한 비판(Acrimony in c.l.p.)’ 글(http://bit.ly/1e8iABS )에서 발췌. 서 문에서 인용한 동일한 메시지의 다른 부분이다. 그날 팀봇(TimBot )이라는 말이 만들어졌다.

783


CAUTION_ 메타프로그래밍은 흥미로운 주제며, 휩쓸리기도 쉽다. 그러므로 이 장은 다음과 같은 충고로 시

작해야 할 것 같다. 프레임워크를 만들고 있지 않다면, 메타클래스를 작성해서는 안 된다. 그냥 재미로 하거나, 배운 개념을 적용 해보기 위한 것이 아니라면.

먼저 런타임에 클래스를 어떻게 생성하는지 알아보자.

21.1 클래스 팩토리 이 책에서 이미 여러 번 봤지만, 표준 라이브러리에는 collections.namedtuple ( )이라는 클 래스 팩토리가 있다. 이것은 일종의 함수로서, 클래스명과 속성명을 전달하면 이름으로 항목을 가져올 수 있게 해주고 디버깅하기 좋은 __repr__( ) 메서드를 제공하는 tuple의 서브클래스를 생성한다. 이따금 가변 객체에 이와 비슷한 팩토리가 있었으면 하는 때가 있었다. 예를 들어 지금 애완동 물 가게용 애플리케이션을 만들고 있는데, 개에 대한 데이터를 간단한 레코드로 처리하고 싶다 고 하자. 다음과 같은 식상한 코드는 좋지 않다. class Dog: def __init__(self, name, weight, owner): self.name = name self.weight = weight self.owner = owner

따분하다. 똑같은 필드명이 세 번씩 나온다. 이렇게 따분한 코드는 repr ( )로 출력한 내용도 마 음에 들지 않는다. rex = Dog('Rex', 30, 'Bob') rex <__main__.Dog object at 0x2865bac> >>> >>>

784 6부 메타프로그래밍


collections.namedtuple ( )에서 힌트를 얻어 Dog 같은 간단한 클래스를 즉석으로 생성하는 record_factory ( )를 만들어보자. [예제 21-1]은 이 팩토리를 사용하는 예를 보여준다. 예제 21-1 간단한 클래스 팩토리인 record_factory ( )의 테스트

Dog = record_factory('Dog', 'name weight owner') ➊ >>> rex = Dog('Rex', 30, 'Bob') >>> rex ➋ Dog(name='Rex', weight=30, owner='Bob') >>> name, weight, _ = rex ➌ >>> name, weight ('Rex', 30) >>> "{2}'s dog weighs {1}kg".format(*rex) ➍ "Bob's dog weighs 30kg" >>> rex.weight = 32 ➎ >>> rex Dog(name='Rex', weight=32, owner='Bob') >>> Dog.__mro__ ➏ (<class 'factories.Dog'>, <class 'object'>) >>>

➊ 이 팩토리 함수의 시그너처는 namedtuple ( )의 시그너처와 비슷하게, 클래스명과 속성명을 공백이나 콤

마로 구분해서 만든 문자열 하나를 받는다. ➋ repr ( )도 멋지게 출력한다. ➌ 객체를 반복할 수 있으므로 할당문에서 간편하게 언패킹할 수 있다. ➍ 그리고 format ( )과 같은 함수에도 쉽게 사용할 수 있다. ➎ record 객체는 가변형이다. ➏ 새로 생성된 클래스는 object를 상속하며, 팩토리와는 아무 관련이 없다.

[예제 21-2]는 record_factory ( ) 함수의 코드다.2 예제 21-2 factories.py : 간단한 클래스 팩토리

def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() ➊ except AttributeError: # replace()나 split()을 사용할 수 없다.

2 코드를 제공한 친구 J.S. 두에노에게 감사한다.

21장 클래스 메타프로그래밍 785


pass # 이미 식별자의 시퀀스로 되어 있다고 가정한다. field_names = tuple(field_names) ➋ def __init__(self, *args, **kwargs): ➌ attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): ➍ for name in self.__slots__: yield getattr(self, name) def __repr__(self): ➎ values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ __init__ __iter__ __repr__

= = = =

field_names, __init__, __iter__, __repr__)

return type(cls_name, (object,), cls_attrs)

➊ 실전 덕 타이핑. field_names를 콤마나 공백으로 분할하려고 시도한다. 실패하면, 이미 한 항목에 이름 하

나씩 들어 있는 반복형이다. ➋ 속성명들의 튜플을 생성한다. 이 튜플은 새로운 클래스의 __slots__ 속성으로 사용하며, 언패킹과

__repr__( )에 사용할 필드 순서도 설정한다. ➌ 이 함수는 새로운 클래스의 __init__( ) 메서드가 된다. 위치 인수나 키워드 인수를 받는다. ➍ __iter__( ) 메서드를 구현해서 클래스 객체를 반복형으로 만든다. __slots__에 들어 있는 순서대로 필

드값을 생성한다. ➎ __slots__와 self를 반복해서 멋지게 출력하는 __repr__( ) 메서드를 정의한다. ➏ 클래스 속성의 딕셔너리를 조합한다. ➐ type ( ) 생성자를 호출해서 새로운 클래스를 생성하고 반환한다.

type (my_object )를 이용해서 객체의 클래스와 동일한 my_object.__class__를 가져오므로, type ( )을 일종의 함수로 생각하기 쉽다. 그러나 type은 클래스다. 다음과 같이 인수 세 개를

받아서 호출하면 새로운 클래스를 생성하는 일종의 클래스처럼 작동한다.

786 6부 메타프로그래밍


MyClass = type('MyClass', (MySuperClass, MyMixin), {'x': 42, 'x2': lambda self: self.x * 2})

type ( )에 전달하는 세 인수의 이름은 name, bases, dict며, 이때 dict는 새로운 클래스의 속

성명과 속성의 매핑이다. 위 코드는 기능상으로 다음 코드와 동일하다. class MyClass(MySuperClass, MyMixin): x = 42 def x2(self): return self.x * 2

특이한 점은 여기에 나온 MyClass나 [예제 21-1]의 Dog처럼 type의 객체가 클래스라는 것이다. 정리하면, [예제 21-2] record_factory의 마지막 행은 cls_name 값을 이름으로 사용하고, object를 단 하나의 직속 슈퍼클래스로 사용하며, __slots__, __init__( ) , __iter__( ) , __repr__( ) 클래스 속성을 가진 하나의 클래스를 생성한다. 이때 __init__( ), __iter__( ), __repr__( )은 객체 메서드다. __slots__ 클래스 속성의 이름을 다른 이름으로 할 수도 있었지만, 그러면 할당할 속성명을 검

증하기 위해 __setattr__( ) 메서드를 구현해야 한다. record 같은 클래스의 경우 속성들의 이름이 언제나 동일하고 같은 순서로 되어 있어야 하기 때문이다. __slots__를 사용하면 수백 만 객체를 사용할 때 메모리를 절약하는 특징이 있지만, 9.8절 ‘__slots__ 클래스 속성으로 공 간 절약하기’에서 설명한 단점도 있다. 클래스를 동적으로 생성하기 위해 일반적으로 인수 세 개를 전달해서 type ( ) 을 호출한다. collections.namedtuple ( ) 소스 코드( http://bit.ly/1HGwxRl )에서는 다른 방법을 사용

한다. 하나의 문자열로 되어 있는 소스 코드 템플릿 _class_template과 namedtuple ( ) 함수 가 _class_template.format ( )을 호출해서 빈 칸을 채운다. 그러고 나서 exec ( ) 내장 함수를 이용해서 생성된 소스 코드 문자열을 평가한다. record_factory ( )로 생성한 클래스의 객체들은 직렬화할 수 없다는 제한이 있다. 즉, pickle

모듈의 dump ( ), load ( ) 함수와 함께 사용할 수 없다. 이 절의 목표는 간단한 사례에서 type 클 래스의 사용법을 보여주는 것이므로, 이 문제를 해결하는 것은 이 예제의 범위를 벗어난다. 직

21장 클래스 메타프로그래밍 787


렬화 문제를 해결하려면 collections.nameduple 소스 코드(http://bit.ly/ 1HGwxRl )에서 ‘pickle’을 검색해서 연구하기 바란다. CAUTION_ 파이썬에서 메타프로그래밍을 할 때는 exec ( )나 eval ( )을 피하는 것이 좋다. 이 함수들이

신뢰할 수 없는 곳에서 가져온 문자열을 전달하면 심각한 보안 위험이 발생할 수 있다. 파이썬은 내부 조사를 할 수 있는 도구를 충분히 제공하므로, 대부분의 경우 exec ( )와 eval ( )을 사용할 필요가 없다. 그러나 파 이썬 핵심 개발자는 namedtuple ( )을 구현할 때 exec ( )를 사용하기로 결정했다. 이 방법은 클래스를 생 성하기 위해 만들어진 코드를 _source 속성(http://bit.ly/1HGwAfW )을 통해 확인할 수 있게 해준다.

21.2 디스크립터를 커스터마이즈하기 위한 클래스 데커레이터 20.1.3절 ‘LineItem 버전 #5: 새로운 디스크립터형’에서는 알아보기 쉬운 저장소명을 사용하 는 문제를 남겨둔 채 LineItem 예제를 마쳤다. weight 값이 _Quantity#0이라는 이름의 객체 속성에 저장되어 있어서 디버깅하기 힘든 상태다. 다음 명령을 이용하면 [예제 20-7]의 디스크립 터에서 저장소명을 가져올 수 있다. LineItem.weight.storage_name '_Quantity#0' >>>

그러나 저장소명이 다음과 같이 실제 관리 대상 속성의 이름을 포함하고 있으면 더 좋을 것이다. LineItem.weight.storage_name '_Quantity#weight' >>>

20.1.2절 ‘LineItem 버전 #4: 자동 저장소 속성명’에서 설명한 것처럼 디스크립터 객체가 생 성될 때는 관리 대상 속성(디스크립터 객체가 바인딩될 클래스 속성으로서, LineItem 예제 에서 weight 등의 속성)의 이름을 알 수 없기 때문에 알아보기 쉬운 저장소명을 사용할 수 없 었다. 그러나 일단 전체 클래스가 만들어지고 디스크립터 객체가 클래스 속성에 바인딩된 후에 는, 클래스를 조사하고 디스크립터에 적절한 저장소명을 설정할 수 있다. LineItem 클래스의 __new__( ) 메서드에서 이 작업을 수행할 수 있으므로, __init__( ) 메서드 안에서 디스크립터

788 6부 메타프로그래밍


를 사용할 때가 되면, 이미 올바른 저장소명이 설정되어 있다. 그러나 이런 용도로 __new__ ( )를 사용하면 컴퓨터 자원을 낭비하는 문제가 있다. LineItem 클래스 자체가 만들어진 후에는 디스 크립터 객체와 관리 대상 속성 간의 바인딩은 변하지 않지만, __new__( )의 논리는 LineItem 객체가 생성될 때마다 실행되기 때문이다. 그러므로 클래스를 생성할 때 저장소명을 설정해야 한다. 바로 이 작업을 클래스 데커레이터나 메타클래스를 이용해서 처리할 수 있다. 먼저 간단한 것부터 만들어보자. 클래스 데커레이터는 함수 데커레이터와 아주 비슷하다. 클래스를 받아서 동일하거나 수정된 클래스를 반환한다. [예제 21-3]에서 LineItem 클래스는 인터프리터에 의해 평가되어 생성된 클래스 객체가 model. entity ( ) 함수에 전달된다. 파이썬은 model.entity ( )가 반환하는 것을 전역 명칭인 LineItem

에 할당한다. 이 예제에서 model.entity ( )는 LineItem 클래스 안에 있는 각 디스크립터 객체 의 storage_name 속성을 변경해서 반환한다. 예제 21-3 bulkfood_v6.py : Quantity와 NonBlank 디스크립터를 사용하는 LineItem 클래스

import model_v6 as model @model.entity ➊ class LineItem: description = model.NonBlank() weight = model.Quantity() price = model.Quantity() def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price def subtotal(self): return self.weight * self.price

➊ 이 클래스는 데커레이터가 추가되었다는 점 외에는 그대로다.

[예제 21-4]는 데커레이터의 소스 코드다. model_v6.py의 끝부분에 추가된 코드만 여기에 나 열한다. 나머지 코드는 model_v5.py (예제 20-6 )와 동일하다.

21장 클래스 메타프로그래밍 789


예제 21-4 model_v6.py : 클래스 데커레이터

def entity(cls): ➊ for key, attr in cls.__dict__.items(): ➋ if isinstance(attr, Validated): ➌ type_name = type(attr).__name__ attr.storage_name = '_{}#{}'.format(type_name, key) return cls ➎

➊ 데커레이터는 클래스를 인수로 받는다. ➋ 클래스의 속성을 담고 있는 딕셔너리를 반복한다. ➌ 속성이 Validated 디스크립터 클래스인지 확인한다. 만일 그렇다면 ➍ 디스크립터 클래스명과 관리 대상 속성명을 사용하기 위해 storage_name을 설정한다 (예를 들면

_NonBlank#description ). ➎ 변경된 클래스를 반환한다.

bulkfood_v6.py에 들어 있는 doctest는 수정한 코드가 제대로 작동함을 입증한다. [예제 21-5]는 LineItem 객체의 저장소 속성명을 보여준다. 예제 21-5 bulkfood_v6.py : 새로운 storage_name 디스크립터 속성에 대한 doctest

raisins = LineItem('Golden raisins', 10, 6.95) dir(raisins)[:3] ['_NonBlank#description', '_Quantity#price', '_Quantity#weight'] >>> LineItem.description.storage_name '_NonBlank#description' >>> raisins.description 'Golden raisins' >>> getattr(raisins, '_NonBlank#description') 'Golden raisins' >>> >>>

그리 복잡하지 않다. 클래스 데커레이터를 사용하면 이전에는 메타클래스를 사용해야 했던 작업 (클래스가 생성되는 순간 클래스의 커스터마이즈)을 더 간단히 수행할 수 있다. 그러나 클래스 데커레이터는 자신에게 직접 적용된 클래스에서만 작동할 수 있다는 커다란 단점 이 있다. 즉, 장식된 클래스의 변경된 내용에 따라 서브클래스가 변경된 내용을 상속할 수도 아 닐 수도 있다. 다음 절에서는 이 문제를 알아보고, 이 문제의 해결책도 살펴본다.

790 6부 메타프로그래밍


21.3 임포트 타임과 런타임 성공적으로 메타프로그래밍을 하려면, 파이썬 인터프리터가 언제 각 코드 블록을 평가하는지 알고 있어야 한다. 파이썬 프로그래머들은 ‘임포트 타임’과 ‘런타임’을 구분하지만, 이 용어들은 엄격히 정의되어 있지 않으며 구분이 모호한 경우도 있다. 임포트 타임에 인터프리터는 .py 모 듈에 들어 있는 소스 코드를 위에서부터 한 번 파싱하고, 실행할 바이트코드를 생성한다. 구문 에러가 있으면 이때 발생한다. 만일 지역 __pycache__ 디렉터리에 최신 .pyc 파일이 있으면 바이트코드를 실행할 준비가 된 것이므로 이 과정을 생략한다. 컴파일 작업은 확실히 임포트 타임의 활동이긴 하지만, 이때 다른 일도 일어난다. 파이썬 대부분 의 문장이 사용자 코드를 실행하고 사용자 프로그램의 상태를 변경한다는 의미에서 실행문이 기 때문이다. import 문은 단지 단순한 선언이 아니며,3 처음 임포트되는 모듈의 모든 최상위 수준 코드를 실제로 실행한다. 이후에 다시 임포트되는 경우에는 동일 모듈의 캐시를 사용하고 이름들만 바인딩한다. 최상위 수준 코드에는 데이터베이스에 연결하는 등 일반적으로 ‘런타임’ 에 수행하는 작업들도 포함될 수 있다.4 import 문이 각종 ‘런타임’의 동작을 유발하기 때문에 ‘임포트 타임’과 ‘런타임’의 구분이 모호해진다. 위 단락에서 임포트 타임에 ‘모든 최상위 수준 코드를 실행한다’고 말했지만, ‘최상위 수준 코 드’에 대해 명확히 정의할 필요가 있다. 인터프리터는 모듈이 임포트될 때 모듈의 최상위 수준에 서 def 문을 실행하지만, 그러면 어떤 일이 발생할까? 모듈이 처음 임포트될 때 인터프리터가 함수 본체를 컴파일하고 함수 객체를 전역 이름에 바인딩하지만, 함수 본체를 실행하는 것은 아 니다. 일반적인 경우, 인터프리터는 최상위 수준 함수를 임포트 타임에 정의하지만, 런타임에 호출될 때만 실제로 함수를 실행한다. 클래스의 경우 이야기가 다르다. 인터프리터는 임포트 타임에 클래스 안에 들어 있는 클래스의 본체까지 모든 클래스 본체를 실행한다. 클래스 본체를 실행한다는 것은 클래스의 속성과 메서 드가 정의되고, 클래스 객체가 만들어짐을 의미한다. 이런 관점에서 보면 클래스 본체는 ‘최상 위 수준 코드’다. 임포트 타임에 실행되기 때문이다. 지금까지 다소 추상적으로 설명했으므로, 이제 예제 코드를 이용해서 언제 어떤 일이 발생하는 지 구체적으로 알아보자. 3 이와 반대로 자바의 import 문은 단순한 선언으로서, 어떠한 패키지가 필요함을 컴파일러에 알려준다. 4 모듈이 임포트될 때 데이터베이스를 연결하는 것이 좋다는 의미로 이야기하는 것이 아니라, 이렇게 할 수도 있다는 것을 이야기하기 위한 것이다.

21장 클래스 메타프로그래밍 791


21.3.1 코드 평가 시점 연습문제 evalsupport.py를 임포트하는 evaltime.py 스크립트가 있다고 가정하자. 이 모듈 둘 다 <[N]>

포맷으로 표시를 출력하는 print ( ) 문을 여러 개 가지고 있으며, 이때 N은 숫자다. 이

두 연습문제는 이 print ( ) 문이 언제 호출되는지 알아보기 위한 것이다. NOTE_ 이 연습문제가 파이썬이 소스 코드를 어떻게 평가하는지 이해하는 데 많은 도움이 되었다고 학생들

이 의견을 주었다. 794쪽 ‘시나리오 #1에 대한 해결책’을 보기 전에 직접 종이와 연필을 들고 문제를 풀어보 기 바란다.

[예제 21-6]과 [예제 21-7]은 각 모듈의 소스 코드다. 코드를 실행하지 말고, 종이와 연필을 들 고 다음과 같은 두 가지 시나리오에서 숫자 표시가 나타날 순서를 적어보라.

시나리오 #1 파이썬 콘솔에서 evaltime.py 모듈을 임포트한다. >>>

import evaltime

시나리오 #2 명령행에서 evaltime.py 모듈을 실행한다. $ python3 evaltime.py

예제 21-6 evaltime.py : 출력에 나타날 순서대로 번호가 붙은 <[N]> 표시 써내려가기

from evalsupport import deco_alpha print('<[1]> evaltime module start') class ClassOne(): print('<[2]> ClassOne body') def __init__(self): print('<[3]> ClassOne.__init__') def __del__(self): print('<[4]> ClassOne.__del__') def method_x(self):

792 6부 메타프로그래밍


print('<[5]> ClassOne.method_x') class ClassTwo(object): print('<[6]> ClassTwo body') @deco_alpha class ClassThree(): print('<[7]> ClassThree body') def method_y(self): print('<[8]> ClassThree.method_y') class ClassFour(ClassThree): print('<[9]> ClassFour body') def method_y(self): print('<[10]> ClassFour.method_y') if __name__ == '__main__': print('<[11]> ClassOne tests', 30 * '.') one = ClassOne() one.method_x() print('<[12]> ClassThree tests', 30 * '.') three = ClassThree() three.method_y() print('<[13]> ClassFour tests', 30 * '.') four = ClassFour() four.method_y() print('<[14]> evaltime module end')

예제 21-7 evalsupport.py : evaltime.py에 의해 임포트될 모듈

print('<[100]> evalsupport module start') def deco_alpha(cls): print('<[200]> deco_alpha') def inner_1(self): print('<[300]> deco_alpha:inner_1') cls.method_y = inner_1 return cls

21장 클래스 메타프로그래밍 793


class MetaAleph(type): print('<[400]> MetaAleph body') def __init__(cls, name, bases, dic): print('<[500]> MetaAleph.__init__') def inner_2(self): print('<[600]> MetaAleph.__init__:inner_2') cls.method_z = inner_2 print('<[700]> evalsupport module end')

시나리오 #1에 대한 해결책 [예제 21-8]은 파이썬 콘솔에서 evaltime.py 모듈을 임포트한 결과다. 예제 21-8 시나리오 #1: 파이썬 콘솔에서 evaltime 임포트하기

import evaltime evalsupport module start <[400]> MetaAleph body ➋ <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body ➌ <[6]> ClassTwo body ➍ <[7]> ClassThree body <[200]> deco_alpha ➎ <[9]> ClassFour body <[14]> evaltime module end ➏ >>>

<[100]>

➊ 모듈을 임포트할 때 evalsupport 모듈의 최상위 수준 코드가 실행된다. deco_alpha ( ) 함수를 컴파일

하지만, 본체는 실행하지 않는다. ➋ MetaAleph 클래스의 본체가 실행된다. ➌ 모든 클래스의 본체가 실행된다. ➍ 내포된 클래스도 실행된다. ➎ 장식된 ClassThree의 본체가 평가된 후 데커레이터 함수가 실행된다. ➏ 이 시나리오에서는 evaltime을 임포트했으므로 if __name__ == '__main__': 블록은 실행되지 않는다.

794 6부 메타프로그래밍


시나리오 #1에서 주의할 점은 다음과 같다. 1 이 시나리오는 import evaltime 문으로 실행한다. 2 인터프리터는 임포트된 모듈과 이 모듈이 임포트하는 모듈(evalsupport )에 들어 있는 모든 클래스를 실

행한다. 3 장식된 클래스에 붙어 있는 데커레이터 함수를 실행하기 전에 장식된 클래스의 본체를 먼저 평가한다. 데커

레이터가 처리할 클래스 객체를 받아야 하므로, 클래스 객체를 먼저 생성하는 것이 당연하다. 4 이 시나리오에서는 사용자 정의 함수나 메서드 중 deco_alpha ( ) 데커레이터만 유일하게 실행된다.

이제 시나리오 #2에서는 어떻게 되는지 확인해보자.

시나리오 #2에 대한 해결책 [예제 21-9]는 python evaltime.py 명령으로 스크립트를 실행한 결과다. 예제 21-9 시나리오 #2: 셸에서 evaltime.py 실행하기

$ python3 evaltime.py <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body <[7]> ClassThree body <[200]> deco_alpha <[9]> ClassFour body ➊ <[11]> ClassOne tests .............................. <[3]> ClassOne.__init__ ➋ <[5]> ClassOne.method_x <[12]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 ➌ <[13]> ClassFour tests .............................. <[10]> ClassFour.method_y <[14]> evaltime module end <[4]> ClassOne.__del__ ➍

➊ 아직까지는 [예제

21 -8]과 동일하다.

➋ 클래스의 표준 동작이다.

21장 클래스 메타프로그래밍 795


➌ ClassThree.method_y가 deco_alpha ( ) 데커레이터에 의해 변경되었으므로, three.method_y ( )를

호출하면 inner_1 ( ) 함수가 실행된다. ➍ 전역 변수 one에 바인딩된 ClassOne 객체는 프로그램이 종료될 때가 되어서야 가비지 컬렉트된다.

시나리오 #2에서 핵심은 클래스 데커레이터가 변경한 내용이 서브클래스에 영향을 주지 않을 수 있다는 것이다. [예제 21-6]에서 ClassFour는 ClassThree의 서브클래스로 정의되어 있다. @deco_alpha 데커레이터는 ClassThree에 적용되어, 이 클래스의 method_y ( )를 대체했지만, ClassFour에는 전형 영향을 주지 않았다. 물론 ClassFour.method_y ( )에서 super ( )를 이용

해서 ClassThree.method_y ( ) 메서드를 실행했다면 데커레이터의 영향으로 inner_1 ( ) 함수 를 실행했을 것이다. 다음 절에서는 클래스 데커레이터로 한 번에 한 클래스씩 변경하는 대신, 클래스 계층구조 전 체를 커스터마이즈하고자 할 때는 메타클래스를 이용해서 더욱 효율적으로 처리할 수 있음을 보여준다.

21.4 메타클래스 기본 지식 메타클래스는 일종의 클래스 팩토리다. 다만 [예제 21-2]의 record_factory ( )와 같은 함수 대신 클래스로 만들어진다는 점이 다르다. [그림 21-1]은 공장과 장치 표기법을 이용해서 메타 클래스를 설명한다. 공장이 또 다른 공장을 만든다. 그림 21-1 메타클래스는 클래스를 만드는 클래스다.

Ŗᰆŝ ᰆ⊹ ⢽ʑჶ

796 6부 메타프로그래밍


파이썬 객체 모델을 생각해보자. 클래스도 객체이므로, 각 클래스는 다른 어떤 클래스의 객체 여야 한다. 기본적으로 파이썬 클래스는 type의 객체다. 즉, type은 대부분의 내장된 클래스와 사용자 정의 클래스에 대한 메타클래스다. 'spam'.__class__ 'str'> >>> str.__class__ <class 'type'> >>> from bulkfood_v6 import LineItem >>> LineItem.__class__ <class 'type'> >>> type.__class__ <class 'type'> >>>

<class

무한 회귀를 방지하기 위해, 마지막 행에서 보는 것처럼 type은 자기 자신의 객체로 정의되어 있다. 여기서는 str이나 LineItem이 type을 상속한다는 것이 아니라, str과 LineItem 클래스가 모 두 type의 객체라는 점을 강조하려는 것이다. 이들 클래스는 object의 서브클래스다. [그림

21-2]를 보면 이런 이상한 현실을 접하는 데 도움이 될 것이다. 그림 21-2 둘 다 맞는 다이어그램. 왼쪽 그림은 str, type, LineItem이 object의 서브클래스임을 강조한다. 오른쪽 그 림은 str, object, LineItem이 클래스이기 때문에 type의 객체임을 강조한다. 메타클래스

객체

객체

메타클래스

객체

21장 클래스 메타프로그래밍 797


NOTE_ object와 type 클래스는 독특한 관계를 맺고 있다. object는 type의 객체며, type은 object

의 서브클래스다. 이 관계는 ‘마술’과도 같아서 파이썬으로는 표현할 수 없다. 두 클래스 모두 다른 클래스를 정의하기 전에 존재해야 하기 때문이다. 사실 type 클래스가 type 자신의 객체라는 사실도 신비롭다.

표준 라이브러리에는 type 외에도 ABCMeta, Enum 등의 메타클래스도 있다. 다음 예제는 collections.Iterable의 클래스가 abc.ABCMeta임을 보여준다. Iterable은 추상 클래스지

만, ABCMeta는 아니다. 즉, Iterable은 ABCMeta 클래스의 객체다. import collections collections.Iterable.__class__ <class 'abc.ABCMeta'> >>> import abc >>> abc.ABCMeta.__class__ <class 'type'> >>> abc.ABCMeta.__mro__ (<class 'abc.ABCMeta'>, <class 'type'>, >>> >>>

<class

'object'>)

궁극적으로 ABCMeta의 클래스도 type이다. 모든 클래스는 직간접적으로 type의 객체지만, 메 타클래스만 type의 서브클래스다. 메타클래스를 이해하려면 이점에 주의해야 한다. ABCMeta 등 의 메타클래스는 type으로부터 클래스 생성 능력을 상속한다. [그림 21-3]은 이러한 중요한 관 계를 잘 보여준다. 그림 21-3 object의 서브클래스이자 ABCMeta의 객체인 Iterable. object와 ABCMeta는 type의 객체지만, 여기 서 핵심 관계는 ABCMeta가 메타클래스이므로 type의 서브클래스이기도 하다는 점이다. 이 다이어그램에서 Iterable 은 단지 추상클래스일 뿐이다.

메타클래스

객체

서브클래스

798 6부 메타프로그래밍

서브클래스

객체


정리하자면, 모든 클래스는 type의 객체지만, 메타클래스는 type의 서브클래스이기도 하므로, 클래스 팩토리로서 행동한다. 특히 메타클래스는 __init__( ) 메서드를 구현함으로써 자신의 객 체를 커스터마이즈할 수 있다. 메타클래스의 __init__( ) 메서드는 클래스 데커레이터가 하는 모든 일을 할 수 있지만, 다음 연습문제에서 알 수 있는 것처럼 더욱 강력한 영향력을 발휘한다.

21.4.1 메타클래스 평가 시점 연습문제 다음 예제는 21.3.1절 ‘코드 평가 시점 연습문제’를 변형한 것으로서, evalsupport.py는 [예 제 21-7]과 동일하지만, 핵심 스크립트인 evaltime_meta.py는 [예제 21-10]과 같다. 예제 21-10 evaltime_meta.py : MetaAleph 메타클래스의 객체인 ClassFive

from evalsupport import deco_alpha from evalsupport import MetaAleph print('<[1]> evaltime_meta module start') @deco_alpha class ClassThree(): print('<[2]> ClassThree body') def method_y(self): print('<[3]> ClassThree.method_y') class ClassFour(ClassThree): print('<[4]> ClassFour body') def method_y(self): print('<[5]> ClassFour.method_y') class ClassFive(metaclass=MetaAleph): print('<[6]> ClassFive body') def __init__(self): print('<[7]> ClassFive.__init__') def method_z(self): print('<[8]> ClassFive.method_z')

21장 클래스 메타프로그래밍 799


class ClassSix(ClassFive): print('<[9]> ClassSix body') def method_z(self): print('<[10]> ClassSix.method_z') if __name__ == '__main__': print('<[11]> ClassThree tests', 30 * '.') three = ClassThree() three.method_y() print('<[12]> ClassFour tests', 30 * '.') four = ClassFour() four.method_y() print('<[13]> ClassFive tests', 30 * '.') five = ClassFive() five.method_z() print('<[14]> ClassSix tests', 30 * '.') six = ClassSix() six.method_z() print('<[15]> evaltime_meta module end')

여기서도 마찬가지로 종이와 연필을 들고 다음 두 시나리오에서 <[N]> 표시의 숫자를 평가 순 서대로 적어보라.

시나리오 #3 파이썬 콘솔에서 evaltime_meta.py 모듈을 임포트한다. 시나리오 #4 명령행에서 evaltime_meta.py 모듈을 실행한다.

시나리오 #3에 대한 해결책 [예제 21-11]은 파이썬 콘솔에서 evaltime_meta.py를 임포트한 결과다. 예제 21-11 시나리오 #3 : 파이썬 콘솔에서 evaltime_meta 임포트 >>>

import evaltime_meta evalsupport module start

<[100]>

800 6부 메타프로그래밍


<[400]>

MetaAleph body evalsupport module end <[1]> evaltime_meta module start <[2]> ClassThree body <[200]> deco_alpha <[4]> ClassFour body <[6]> ClassFive body <[500]> MetaAleph.__init__ ➊ <[9]> ClassSix body <[500]> MetaAleph.__init__ ➋ <[15]> evaltime_meta module end <[700]>

➊ 시나리오 #1과 두드러진 차이점은 방금 생성된 ClassFive를 초기화하기 위해 MetaAleph.__init__( )

메서드가 호출된다는 점이다. ➋ MetaAleph.__init__( )은 ClassFive의 서브클래스인 ClassSix도 초기화한다.

파이썬 인터프리터는 ClassFive의 본체를 평가한다. 그러고 나서 실제 클래스 본체를 만들기 위 해 type ( )을 호출하는 대신 MetaAleph ( )를 호출한다. [예제 21-12]에서 MetaAleph의 정의 를 보면 __init__( ) 메서드가 다음과 같이 인수 네 개를 받는 것을 알 수 있다.

self 초기화하고 있는 클래스 객체다(예들 들면 ClassFive ).

name, bases, dic 클래스를 생성하기 위해 type에 전달되는 인수와 동일한 인수다.

예제 21-12 evalsupport.py : [예제 21-7]의 MetaAleph 메타클래스의 정의

class MetaAleph(type): print('<[400]> MetaAleph body') def __init__(cls, name, bases, dic): print('<[500]> MetaAleph.__init__') def inner_2(self): print('<[600]> MetaAleph.__init__:inner_2') cls.method_z = inner_2

21장 클래스 메타프로그래밍 801


NOTE_ 메타클래스를 구현할 때는 관례적으로 self대신 cls를 사용한다. 예를 들어 메타클래스의

__init__( ) 메서드에서 첫 번째 인수에 cls라는 이름을 사용해서 현재 생성하고 있는 것이 클래스임을 명 시한다.

__init__( ) 본체에서는 inner_2 ( ) 함수를 정의하고, 이 함수를 cls.method_z에 바인딩한다. MetaAleph.__init__( ) 시그너처에서 cls 인수는 현재 생성하고 있는 것( ClassFive )이 클래

스임을 알려준다. 한편 inner_2 ( )의 시그너처에 있는 self라는 이름은 우리가 궁극적으로 생 성할 객체(예를 들면 ClassFive의 객체)를 가리킨다.

시나리오 #4에 대한 해결책 [예제 21-13]은 명령행에서 evaltime_meta.py를 실행한 결과를 보여준다. 예제 21-13 시나리오 #4 : 셸에서 evaltime_meta.py 실행하기

$ python3 evaltime.py <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime_meta module start <[2]> ClassThree body <[200]> deco_alpha <[4]> ClassFour body <[6]> ClassFive body <[500]> MetaAleph.__init__ <[9]> ClassSix body <[500]> MetaAleph.__init__ <[11]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 ➊ <[12]> ClassFour tests .............................. <[5]> ClassFour.method_y ➋ <[13]> ClassFive tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 ➌ <[14]> ClassSix tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 ➍ <[15]> evaltime_meta module end

802 6부 메타프로그래밍


w w w. h a n b i t . c o . k r

이것이 프로그래밍이다! 저자 직강 동영상 제공!

이것이 안드로이드다

이것이 C언어다

이것이 자바다

진정한 안드로이드 개발자로 이끌어줍니다.

세상에 없던 새로운 C언어 입문서 탄생!

가장 중요한 프로그래밍 언어를 하나 배워야 한다면, 결론은 자바다!

SDK 5.0 롤리팝 호환!

삼성, LG에서 펼쳐졌던 전설의 명강의를 풀타임 동영상 강좌로!

중급 개발자로 나아가기 위한 람다식, JavaFX, NIO 수록

이보다 더 확실한 방법은 없다, 칠판강의

책만 보고, 동영상 강좌로도 만족하지 못했다면 Daum 카페 '슈퍼드로이드'에서 만나요

전체 동영상 강좌 유투브 전격 공개!

자바의 모든 것을 알려주는 인터넷 강의 궁금한 것은 카페에서!

cafe.daum.net/superdroid

http://goo.gl/tJK3Tu

cafe.naver.com/thisisjava

박성근 저 | 1,164쪽 | 45,000원

서현우 저 | 708쪽 | 25,000원

신용권 저 | 1,224쪽 | 30,000원


w w w. h a n b i t . c o . k r

지금은 모던 웹 시대! 모던 웹 디자인을 위한

모던 웹을 위한

HTML5 + CSS3 입문 HTML5 분야 부동의 1위 도서

JavaScript + jQuery 입문

HTML5 표준안 확정에 맞춘 완전 개정판의 귀환!

자바스크립트에서 제이쿼리, 제이쿼리 모바일까지 한 권으로 끝낸다!

HTML5 권고안과 최신 웹 브라우저 환경 대응

시대의 흐름에 맞춰 다시 쓴 자바스크립트 교과서

윤인성 저 | 624쪽 | 30,000원

윤인성 저 | 980쪽 | 32,000원

모던 웹을 위한

HTML5 + CSS3 정복

Node.js

프로그래밍 페이스북, 월마트, 링크드인은 왜 Node.js를 선택했는가?

필요한 것만 배워 바로 현장에서 쓰는 HTML5

이 물음에 대한 답은 Node.js가 보여주는 빠른 처리 능력 때문이다.

순서대로 읽으며 실습할 수 있는 HTML5 자습서

윤인성 저 | 484쪽 | 25,000원

김상형 저 | 700쪽 | 32,000원


w w w. h a n b i t . c o . k r

Hanbit eBook

Realtime w w w. h a n b i t . c o . k r / e b o o k

DRM free! 어떤 디바이스에서도 자유롭게

eBook Oriented! 전자책에 꼭 맞는 최적의 내용과 디자인

Hanbit eBook

Hanbit eBook

Realtime 70

Realtime 89 49

MFC 프로그래밍 주식분석 프로그램 만들기 김세훈 지음

Hanbit eBook

Hanbit eBook

Realtime 90

Realtime 92 49

자바 개발자를 위한

Vert.x JavaScript Promise​ azu​지음 /​주우영​옮김

애플리케이션 개발 모바일/웹 메시징 STOMP와 MQTT로 개발하는 IoT 모바일/웹 애플리케이션 Mobile and Web Messaging 제프 메스닐 지음 / 조건희 옮김

이연복 지음


w w w. h a n b i t . c o . k r

즐거운 상상이 가득! 2015년 화제의 신간

즐거운 상상이 가득! 2015년 화제의 신간

전자부품 백과사전 vol.1 찰스 플랫 지음 / 배지은 옮김 / 30,000원

취미공학에 필요한 핵심 전자부품을 사전식으로 정리한 안내서.

전자부품 백과사전 vol.1 찰스 플랫 지음 / 배지은 옮김 / 30,000원

처음 시작하는 센서

취미공학에 필요한 핵심 전자부품을 사전식으로 정리한 안내서.

전자부품 백과사전 vol.2

찰스 플랫 지음 / 가격미정

키모 카르비넨, 테로 카르비넨 지음 임지순 옮김 / 13,000원

세상을 수치로 읽어내는

<전자부품 백과사전> 시리즈의 두 번째 도서다.

부품인 센서를 알려주 는 책. 이 책을 통해 자신 만의 프로젝트에 다양한 처음 센서를 사용해보자.

Zero to Maker

: 누구나 메이커가 될 수 있다 데이비드 랭 지음 / 장재웅 옮김 / 14,000원

전자부품 백과사전 vol.2

찰스 플랫 지음 / 가격미정

일반인에서 메이커로. 날백수에서 무인 잠

<전자부품 백과사전> 시리즈의 수정 회사 CEO 가 된 사나이, 데이비드두 번째 도서다. 랭의 메이커 도전기.

Make: 센서

시작하는 센서

키모 카르비넨, 테로 카르비넨 지음 임지순 옮김 / 13,000원

세상을 수치로 읽어내는 부품인 센서를 알려주

키모 카르비넨, 테로 카르비 넨, 빌 발토카리 지음 는 책. 이 책을 통해 자신 / 가격미정

만의 프로젝트에 다양한

필수 전자부품인 센서를

센서를 사용해보자. 마이크로 컨트롤러 보드

Zero to Maker

: 누구나 메이커가 될 수 있다

에 응용하는 방법을 담

데이비드 랭 지음 / 장재웅 옮김 / 14 ,000원 Maker Pro

았다.

베이첼 지음 / 가격미정 일반인에서 메이커로.존날백수에서 무인 잠

메이커라면 반드시 읽어야 할 필수 계발

수정 회사 CEO가 된 사나이, 데이비드 서. 프로 메이커들과의 인터뷰 및 에세이 랭의 메이커 도전기. 수록.

프로젝트로 배우는 라즈베리 파이

도날드 노리스 지음 / 임지순 옮김

다양한 실전 프로젝트를 통해 라즈베리 파이를 쉽고 재미있게 배워본다.

Maker Pro 존 베이첼 지음 / 가격미정

메이커라면 반드시 읽어야 할 필수 계발

Make: 센서 키모 카르비넨, 테로 카르비 넨, 빌 발토카리 지음 / 가격미정

필수 전자부품인 센서를 마이크로 컨트롤러 보드 에 응용하는 방법을 담 았다.


파이썬의 잠재력을 끌어올리는 프로그래밍 기법 F

l

u

e

n

t

P

y

t

h

o

간단하고, 명료하고, 효율적인 파이썬 프로그래밍

n

이썬의 핵심 요소 및 주요 기본 라이브러리도 기본에 충실하게 소개한다. 이 책에서 더욱 간결하고, 읽기 좋 고, 빠른 코드를 작성하는 방법을 터득할 수 있을 것이다.

1. 파이썬 데이터 모델 : 일관성 있는 객체 행위의 핵심인 특별 메서드 이해하기 2. 자료구조 : 시퀀스·매핑·집합 등 내장형 자료구조 사용법과, 텍스트와 바이트의 차이점 이해하기 3. 객체로서의 함수 : 함수를 일급 객체로 다루는 파이썬의 철학이 주요 디자인 패턴에 미치는 영향 이해하기 4. 객체지향 관용 코드 : 참조, 가변성, 인터페이스, 연산자 오버로딩, 다중 상속을 익히고 클래스 구현하기 5. 제어 흐름 : 분기, 루프, 서브루틴 등의 순차적 제어 흐름을 뛰어넘는 파이썬의 기능과 라이브러리 이해하기 6. 메타프로그래밍 : 동적 속성을 가지는 클래스 생성 기법과 프로퍼티 메커니즘 이해하기

이 책은 자신의 지식 한계를 넘어서려는 파이썬 중급 및 고급 개발자에게 유용한 프로그래밍 기법이

Fluent Python

실용 안내서다. 특히 다른 언어에서는 찾아볼 수 없는 파이썬 고유의 기능을 중점적으로 살펴본다. 또한 파

전문가를 위한 파이썬

이 책은 초보자들이 놓치기 쉬운 파이썬 기능들을 활용하여, 효율적인 파이썬 코드 작성 방법을 제시하는

가득한 보물 창고다. 대니얼 로이 그린펠드, 오드리 로이 그린펠드, 『Two Scoops of Django』 저자

강권학 옮김

뇌를 자극하는 파이썬 3

처음 시작하는 파이썬

고성능 파이썬

루시아누 하말류 지음

관련 도서

Fluent

파이썬 3 버전 기준

Python 전문가를 위한 파이썬

프로그래밍 언어 / 파이썬

예제 소스 http://www.hanbit.co.kr/exam/2498

정가 55,000원

루시아누 하말류 지음 강권학 옮김


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