넥슨코리아 사내 발표자료로 왓 스튜디오에서 파이썬으로 《야생의 땅: 듀랑고》 서버를 비롯한 여러가지 도구를 만든 경험을 공유합니다.
- 게임서버와 각종 툴, 테스트/빌드/배포 시스템을 만들 때 사용한 재료
- 파이썬 코드 품질 개선, 디버깅, 프로파일링, 최적화
- 파이썬 오픈소스 생태계와 왓 스튜디오가 하는 오픈소스 활동
[야생의 땅: 듀랑고] 서버 아키텍처 - SPOF 없는 분산 MMORPG 서버Heungsub Lee
NDC14에서 발표한 "[야생의 땅: 듀랑고] 서버 아키텍처" 세션의 슬라이드입니다.
슬라이드에 설명이 많지 않은데, 디스이즈게임에서 발표 내용을 잘 정리해주었습니다. 기사도 함께 보시면 좋을 것 같습니다.
http://www.thisisgame.com/webzine/news/nboard/4/?n=54955
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유Hyojun Jeon
NDC18에서 발표하였습니다. 현재 보고 계신 슬라이드는 1부 입니다.(총 2부)
- 1부 링크: https://goo.gl/3v4DAa
- 2부 링크: https://goo.gl/wpoZpY
(SlideShare에 슬라이드 300장 제한으로 2부로 나누어 올렸습니다. 불편하시더라도 양해 부탁드립니다.)
[야생의 땅: 듀랑고] 서버 아키텍처 - SPOF 없는 분산 MMORPG 서버Heungsub Lee
NDC14에서 발표한 "[야생의 땅: 듀랑고] 서버 아키텍처" 세션의 슬라이드입니다.
슬라이드에 설명이 많지 않은데, 디스이즈게임에서 발표 내용을 잘 정리해주었습니다. 기사도 함께 보시면 좋을 것 같습니다.
http://www.thisisgame.com/webzine/news/nboard/4/?n=54955
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유Hyojun Jeon
NDC18에서 발표하였습니다. 현재 보고 계신 슬라이드는 1부 입니다.(총 2부)
- 1부 링크: https://goo.gl/3v4DAa
- 2부 링크: https://goo.gl/wpoZpY
(SlideShare에 슬라이드 300장 제한으로 2부로 나누어 올렸습니다. 불편하시더라도 양해 부탁드립니다.)
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...Amazon Web Services Korea
서비스 런칭을 위해 라이온하트와 카카오게임즈가 어떻게 최적 성능의 인스턴스를 선택하고, Windows 운영 체제를 최적화하며, 왜 Amazon Aurora를 기본 데이터베이스로 채택하였는지를 설명합니다. 또한, 출시부터 운영까지의 과정에서 MMORPG가 어떻게 AWS 상에서 설계되고, 게임 서버 성능을 극대할 수 있었는지에 대해 전달해드립니다.
[우리가 데이터를 쓰는 법] 모바일 게임 로그 데이터 분석 이야기 - 엔터메이트 공신배 팀장Dylan Ko
Gonnector(고넥터) 고영혁 대표가 주최한 스타트업 데이터 활용 세미나 '우리가 데이터를 쓰는 법' 의 세 번째 발표 자료
세미나 : 우리가 데이터를 쓰는 법 (How We Use Data)
일시 : 2016년 4월 12일 화요일 10:00 ~ 18:00
장소 : 마루180 (Maru180) B1 Think 홀
제목 : 모바일 게임 로그 데이터 분석 이야기
연사 : 엔터메이트 공신배 팀장
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...Amazon Web Services Korea
서비스 런칭을 위해 라이온하트와 카카오게임즈가 어떻게 최적 성능의 인스턴스를 선택하고, Windows 운영 체제를 최적화하며, 왜 Amazon Aurora를 기본 데이터베이스로 채택하였는지를 설명합니다. 또한, 출시부터 운영까지의 과정에서 MMORPG가 어떻게 AWS 상에서 설계되고, 게임 서버 성능을 극대할 수 있었는지에 대해 전달해드립니다.
[우리가 데이터를 쓰는 법] 모바일 게임 로그 데이터 분석 이야기 - 엔터메이트 공신배 팀장Dylan Ko
Gonnector(고넥터) 고영혁 대표가 주최한 스타트업 데이터 활용 세미나 '우리가 데이터를 쓰는 법' 의 세 번째 발표 자료
세미나 : 우리가 데이터를 쓰는 법 (How We Use Data)
일시 : 2016년 4월 12일 화요일 10:00 ~ 18:00
장소 : 마루180 (Maru180) B1 Think 홀
제목 : 모바일 게임 로그 데이터 분석 이야기
연사 : 엔터메이트 공신배 팀장
TensorFlow에 대한 분석 내용
- TensorFlow?
- 배경
- DistBelief
- Tutorial - Logistic regression
- TensorFlow - 내부적으로는
- Tutorial - CNN, RNN
- Benchmarks
- 다른 오픈 소스들
- TensorFlow를 고려한다면
- 설치
- 참고 자료
NDC 16에서 발표한 '스매싱더배틀 1년간의 개발일지'라는
제목의 포스트 모템입니다.
PT의 내용은 실제 발표 자료에 조금 더 설명을 붙였으며
PT의 내용에 대한 질문은 아래의 주소를 통해서
문의 부탁드립니다.
Twitter
https://twitter.com/Studio_HG
Facebook
https://www.facebook.com/GameStudioHG
3월 중순부터 한달이라는 기간동안 선거운동을 경험하면서 느낀점과 의견들을 정리해 봤습니다. 비전문가의 의견이라 부족한 점이 많고, 선거캠프의 공식적인 입장이나 견해와도 다른 개인의 생각일 뿐입니다.
IT업계가 단기간에 성장을 할 수 있었던 이유는 지식의 공유 문화가 활발했었기 때문이라고 생각합니다. 성공하든 실패하든 자신의 경험을 다른 사람들과 공유하고, 그것을 통해 배움으로써 업계 전체가 빠르게 발전할 수 있었습니다.
정치계에서는 이런 식으로 무언가를 공유하는 문화가 낯설고 걱정도 되시겠지만, 개인적으로는 우리나라 정치권에서도 사소한 지식이라도 문서로 정리되고 공유하는 문화가 만들어 지면 좋겠다고 생각합니다. 그런 문화 속에서 자연스럽게 정치권에 종사하시는 분들이 서로 배우고 성장해, 우리나라의 정치를 더 발전시켜 주실테니까요. 고 노무현 대통령이 대통령 기록실을 만드신 취지도 그런 게 아니었을까 감히 추측해봅니다.
벌써 선거가 끝난 지 한달이 지났고, 저는 다시 일상으로 돌아왔습니다. 그 전과 달라진 게 있다면 정치뉴스란을 좀 더 흥미롭게 읽을 수가 있게 되었다는 정도? 많이 부족한 글이지만 그냥 이런 의견도 있구나 정도로 가볍게 읽어봐 주세요. 감사합니다.
Approximate nearest neighbor methods and vector models – NYC ML meetupErik Bernhardsson
Nearest neighbors refers to something that is conceptually very simple. For a set of points in some space (possibly many dimensions), we want to find the closest k neighbors quickly.
This presentation covers a library called Annoy built my me that that helps you do (approximate) nearest neighbor queries in high dimensional spaces. We're going through vector models, how to measure similarity, and why nearest neighbor queries are useful.
발표 당일에 발표를 결심하는 바람에 아침부터 코엑스로 가는 버스 안에서, 점심 시간과 쉬는 쉬간에 틈틈이 작업하느라 리허설을 한 번밖에 해보지 못해서 발표할 때 거의 슬라이드 노트를 읽다시피 했던 점 넓은 마음으로 양해 부탁드립니다. 마지막 한 문장을 남겨두고 징이 울려서 매우 아쉽네요. 좋은 행사를 만드는데 기여하신 모든 스텝, 발표자 그리고 참가자 분들께 진심으로 감사드립니다. 내년에 또 뵐 수 있었으면 좋겠습니다.
오픈소스 개발을 시작하기로 결정했더라도, 처음 개발하는 경우에는 막상 무엇을 개발할지, 그리고 어떻게 개발해야 할 지 막막하기만 합니다. 이 때는 기존에 공개되어 있는 오픈소스 프로젝트를 활용해 개선해나가는 프로젝트부터 시작하면 많은 도움이 됩니다. 이번 강연에서는 기존 오픈소스 프로젝트를 처음부터 새로 만들어가면서 개선해나갔던 경험을 이야기하고 어떻게 하면 오픈소스 개발에 쉽게 접근할 수 있는지를 알려줍니다.
2D 컴퓨터비젼에 대한 설명. 영상으로부터 정보를 추출해내는 공학/과학 분과인 컴퓨터비젼의 기술에 대한 쉬운 설명. 파이썬(Python)의 컴퓨터비젼/영상처리 라이브러리인 scikit-image를 주로 활용하였으며 코드를 함께 담음.
R컨퍼런스 발표본 (2014.5.30) 임.
이 발표는 [야생의 땅: 듀랑고]의 지형 배포 시스템과 생태계 시뮬레이션 자동화 시스템에 대한 이야기를 다룹니다. 듀랑고의 각 섬은 크기와 지형, 기후 조건이 다양하고 섬의 개수가 많아서 수동으로 관리하는 것은 사실상 불가능합니다. 몇번의 사내 테스트와 베타 테스트를 거치면서 이러한 문제를 해결해주는 자동화된 도구의 필요성이 절실해졌고, 작년에 NDC에서 발표했던 생태계 시뮬레이터와 Docker, 그리고 아마존 웹서비스(AWS)를 이용하여 수많은 섬들을 자동으로 생성하고 관리하는 자동화 시스템을 구축하게 되었습니다. 그 과정에서 했던 고민들, 기존의 애플리케이션을 "Dockerizing" 했던 경험, AWS의 각 서비스들을 적절히 활용했던 이야기, AWS의 각 지역별 요금이 상이하다는 점을 이용해서 비용을 절감한 사례, 그리고 자동화 시스템의 문제점과 앞으로의 방향에 대해서 이야기 할 계획입니다.
머신러닝 및 데이터 과학 연구자를 위한 python 기반 컨테이너 분산처리 플랫폼 설계 및 개발Jeongkyu Shin
머신러닝 및 데이터 과학 분야의 컴퓨팅 수요는 해가 갈수록 급증하고 있습니다. 이와 더불어 분산처리 기술, 데이터 파이프라이닝 및 개발 환경 스택 관리 등의 관련된 다양한 이슈들 또한 엄청나게 늘어나고 있습니다. 머신러닝 모델의 기하급수적인 모델 복잡도 증가 추세와 마찬가지로, 모델 학습을 위한 환경 관리 또한 갈수록 복잡도가 높아지는 추세입니다.
이 세션에서는 이러한 문제를 해결하기 위해 python 언어 기반의 분산처리 스케쥴링/오케스트레이션 미들웨어 플랫폼을 개발한 4년간의 과정에서 겪은 다양한 문제들에 대해 다룹니다. 2015년 컨테이너 기반의 고밀도 분산처리 플랫폼 설계 및 프로토타이핑 과정을 PyCon KR에서 발표한 이후, 실제 구현 및 오픈소스화, 안정화를 거치며 겪은 다양한 기술적/비기술적 문제들에 대한 경험을 공유합니다.
기술적으로는 최근 몇 년 간의 클러스터 플랫폼 관련 기술의 진보와 함께 탄생한 다양한 도구들과, 이러한 도구들을 python 기반으로 엮어내기 위해 사용하고 개발한 다양한 오픈소스들을 다룹니다. Python 기반의 컨테이너 스케쥴링 및 오케스트레이션 과정의 구현과, 다양한 프로그래밍 언어로 만든 SDK를 graphQL을 이용하여 연동하는 과정에서의 몇몇 유의점을 설명합니다. 아울러 python 기반의 SDK를 다양한 언어로 포팅했던 경험을 간단하게 안내합니다.
플랫폼을 개발하는 중 등장한 TensorFlow, PyTorch 등의 다양한 머신러닝 프레임워크들을 도입하며 겪은 문제와 해결 과정에 대해서도 나눕니다. 연구 분야에는 Python 2.7 기반의 프레임워크들이 여전히 많습니다. 이러한 프레임워크 및 라이브러리의 지원을 위하여 Python 2 기반의 프레임워크와 Python 3.7로 구현한 컨테이너 인터페이스를 단일 컨테이너 환경에 중복 빌드 및 상호 간섭 없이 공존시키기 위해 개발한 아이디어를 소개합니다.
마지막으로 Python 기반의 프레임워크를 개발, 배포 및 상용화 하는 과정에서 겪은 다양한 어려움을 소개합니다. 솔루션을 배포 및 보급할 때 겪는 다양한 런타임, 하드웨어 환경 및 개인 정보 보호를 위한 폐쇄망 대상의 디플로이 등에 대응하기 위하여 Python 응용프로그램을 단독 실행용으로 패키징하는 과정에서 겪은 팁들을 설명합니다. 또한 GUI 빌드 및 Python, Go 및 C++을 함께 사용한 드라이버 가상화 레이어 개발 등의 내용도 살짝 다룹니다.
이 슬라이드는 PyCon KR 2019의 발표 슬라이드입니다. ( https://www.pycon.kr/program/talk-detail?id=138 )
"파이썬 성능 잠재력을 끌어내는 실용적인 개발 전략서"
미샤 고렐릭,이안 오스발트 지음 / 김영근,오현석 옮김 | 한빛미디어 | 2016년 8월 | 30,000원
★ 파이썬 3 대응
★ 프로토타이핑에서 실무 운영까지 살아가는 파이썬 코드의 비밀
아이디어 무한경쟁이 펼쳐지는 대(大)스타트업 시대! 높은 생산성으로 유명한 파이썬은 최우선 고려사항이다. 한 가지, 컴파일 언어 대비 느린 성능 때문에 성공한 서비스나 성능이 중요한 기능은 컴파일 언어로 다시 작성하곤 한다. 하지만 실무 운영까지 파이썬 코드를 계속 이어가는 방법이 있다면?
이 책은 파이썬을 둘러싼 설계와 작동 원리를 설명하고 이에 기반한 실용적인 구현 전략을 소개한다. 멀티코어 아키텍처와 클러스터의 이점을 활용하려면 어떻게 해야 하는가? 안정성을 잃지 않고 확장하는 방법은 무엇인가? 여러분은 이에 대한 명확한 해법을 찾고 방대한 소셜 데이터까지도 눈부신 속도로 처리해내는 파이썬 코드를 얻게 될 것이다.
★ 주요 내용
● numpy, Cython, 프로파일러 활용하기
● 파이썬의 시선으로 바라보는 컴퓨터 아키텍처 이해하기
● CPU 시간과 메모리 사용량을 프로파일링하여 병목 지점 찾기
● 상황에 맞는 컬렉션으로 효율적인 프로그램 작성하기
● 행렬과 벡터 연산 가속하기
● 네이티브 코드로 컴파일하기
● 대량의 I/O와 연산 동시 수행하기
● 원격 클러스터로 병렬 처리하기
● 적은 메모리로 대규모 데이터 처리하기
82. 1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
14. 흔한 실수
15. 오픈소스
저희가 이런 걸 만들 때
어떤 재료를 써왔고
83. 1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
14. 흔한 실수
15. 오픈소스
또 어떤 재료를 안 썼거나
혹은 못 썼는지
84. 1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
14. 흔한 실수
15. 오픈소스
그리고 코드 품질은
어떻게 관리하고 개선하고 있는지
85. 1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
14. 흔한 실수
15. 오픈소스
주제가 꽤 다양하긴 한데
하나씩 살펴보겠습니다.
136. g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.joinall([g1, g2, g3])
print([g.value for g in [g1, g2, g3]])
코루틴을 직접 다루는 건
굉장히 까다로워요.
137. g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.joinall([g1, g2, g3])
print([g.value for g in [g1, g2, g3]])
gevent는 다루기 까다로운
greenlet 코루틴을
138. g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.joinall([g1, g2, g3])
print([g.value for g in [g1, g2, g3]])
평범한 멀티스레딩 코드처럼
쓸 수 있게 해주죠.
139. g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.joinall([g1, g2, g3])
print([g.value for g in [g1, g2, g3]])
특정한 일을 하는
스레드를 띄우고
140. g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.joinall([g1, g2, g3])
print([g.value for g in [g1, g2, g3]])
다른 스레드가 일을 다 할 때까지 기다려서
동기화 하는 식이에요.
141. g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.joinall([g1, g2, g3])
print([g.value for g in [g1, g2, g3]])
평범하죠?
178. @asyncio.coroutine
def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
yield from fut
print([fut.result() for fut in [f1, f2, f3]])
파이썬의 언어 기능인 제너레이터를 이용해서
명시적인 코루틴을 구현하고 있어요.
179. @asyncio.coroutine
def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
yield from fut
print([fut.result() for fut in [f1, f2, f3]])
gevent와 다르게 코드만 봐도
어디서 봉쇄가 발생할지 명확하게 보이죠.
180. async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
print([fut.result() for fut in [f1, f2, f3]])
파이썬 3.5에서는 아예 C#처럼
async와 await 키워드까지 추가돼서
181. async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
print([fut.result() for fut in [f1, f2, f3]])
더 편하게 쓸 수 있게 됐어요.
182. async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
print([fut.result() for fut in [f1, f2, f3]])
다만 asyncio를 쓰면
183. async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
print([fut.result() for fut in [f1, f2, f3]])
asyncio에 맞춰진 라이브러리만
써야해서 선택의 폭이 좁아지긴 해요.
215. def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
return ['Hello, worldn']
이 인터페이스에만 맞추면
파이썬의 거의 모든 웹 도구를 같이 쓸 수 있어요.
240. ❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT Your name.
--help Show this message and exit.
돌려보면 이렇게 동작합니다.
241. ❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT Your name.
--help Show this message and exit.
보시다시피 리눅스 CLI를
상식적으로 잘 구현하고 있다는 점에서
242. ❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT Your name.
--help Show this message and exit.
애용하는 프레임워크예요.
243. ❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT Your name.
--help Show this message and exit.
상식과 미묘하게 다르게 동작하는
프레임워크도 여럿 봐왔거든요.
275. ❯ pytest test.py
========= FAILURES =========
E assert 24 == 42
E + where 24 = answer()
def test_answer():
assert answer() == 42
테스트가 실패했을 때 왜 실패했는지
단서까지 함께 보고받을 수 있어요.
276. ❯ pytest test.py
========= FAILURES =========
E assert 24 == 42
E + where 24 = answer()
def test_answer():
assert answer() == 42
여기 보시면 answer의 결과값이
42가 아니라 24였다는 걸 알 수 있죠.
279. assertEqual(a, b)
assertNotEqual(a, b)
assertIs(a, b)
assertIn(a, b)
assertGreater(a, b)
unittest
assert a == b
assert a != b
assert a is b
assert a in b
assert a > b
pytest
이런 식으로 비교연산 별로
메소드가 따로 마련돼있죠.
281. assertEqual(a, b)
assertNotEqual(a, b)
assertIs(a, b)
assertIn(a, b)
assertGreater(a, b)
unittest
assert a == b
assert a != b
assert a is b
assert a in b
assert a > b
pytest
사실 처음에 제가 pytest를
쓰기 시작했던 이유는 이 점이 전부였는데
282. assertEqual(a, b)
assertNotEqual(a, b)
assertIs(a, b)
assertIn(a, b)
assertGreater(a, b)
unittest
assert a == b
assert a != b
assert a is b
assert a in b
assert a > b
pytest
쓰다 보니까 테스트 만드는 수고를 덜어주는
편의 기능도 많다는 걸 알게 됐어요.
285. @pytest.fixture
def db(request):
db = Database()
request.addfinalizer(db.close)
return db
def test_db(db):
db.insert('answer', 42)
assert db.get('answer') == 42
“픽스처”란 테스트에서 쓰이는
상태가 일관적인 재료를 말하는데
286. @pytest.fixture
def db(request):
db = Database()
request.addfinalizer(db.close)
return db
def test_db(db):
db.insert('answer', 42)
assert db.get('answer') == 42
이렇게 데코레이터로 픽스처를 정의해두면
매개변수 이름을 이용해서
287. @pytest.fixture
def db(request):
db = Database()
request.addfinalizer(db.close)
return db
def test_db(db):
db.insert('answer', 42)
assert db.get('answer') == 42
어느 테스트 케이스에서든
쉽게 끌어다 쓸 수 있게 해줘요.
343. ❯ flake8 bad.py
bad.py:1:1: F401 'sys' imported but unused
bad.py:2:1: F401 'os' imported but unused
bad.py:7:6: E111 indentation is not a multiple of four
이런 식으로 어떤 코드가 컨벤션에 안 맞는지
아니면 잘못 쓰였는지
344. ❯ flake8 bad.py
bad.py:1:1: F401 'sys' imported but unused
bad.py:2:1: F401 'os' imported but unused
bad.py:7:6: E111 indentation is not a multiple of four
코드를 읽어서 정적으로 분석해주는
단순한 Lint인데
345. ❯ flake8 bad.py
bad.py:1:1: F401 'sys' imported but unused
bad.py:2:1: F401 'os' imported but unused
bad.py:7:6: E111 indentation is not a multiple of four
이걸 작업자 각자가
에디터에 연동해서 쓰기도 하고
346. ❯ flake8 bad.py
bad.py:1:1: F401 'sys' imported but unused
bad.py:2:1: F401 'os' imported but unused
bad.py:7:6: E111 indentation is not a multiple of four
CI에서 빌드 돌릴 때
같이 돌리기도 합니다.
374. ❯ python
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
[GCC 5.3.1 20160413] on linux2
Type "help", "copyright", "credits" or "license"
for more information.
>>> import os
>>> os.getpid()
42
파이썬 기본 REPL은 다들 아실 테고
375. ❯ ipython
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
Type "copyright", "credits" or "license" for
more information.
In [1]: import os
In [2]: os.getpid()
Out[2]: 42
IPython이라는 조금 더 스마트한
REPL도 있는데
376. ❯ ipython
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
Type "copyright", "credits" or "license" for
more information.
In [1]: import os
In [2]: os.getpid()
Out[2]: 42
문법강조도 되고 자동완성도 되고
그 밖에도 다양한 편의기능이 들어있어요.
377. ❯ ipython
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
Type "copyright", "credits" or "license" for
more information.
In [1]: import os
In [2]: os.getpid()
Out[2]: 42
이 IPython을 터미널 대신
웹에서도 쓸 수 있게 나온 게
383. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
저희가 만드는 게 MMORPG라서 그런지
384. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
간단한 게임플레이 테스트 시나리오
하나 짜려고 해도
385. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
그 시나리오에 필요한
게임 세계를 구축하는데
386. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
굉장히 깊은 의존성을
풀어야 하는 경우가 있어요.
387. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
이 코드는 읽으라고 쓴 게 아니에요.
388. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
여기서 테스트하고 싶은 건
389. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
그저 플레이어를 호수 근처로
옮기는 것 뿐인데
390. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
플레이어와 호수를 준비하려면
391. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
앞쪽에 훨씬 복잡하고 많은 코드가
필요할 수 있다는 걸 보여드리고 싶었어요.
392. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
게임서버에서는 게임 세계가
알아서 구축되잖아요?
393. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
만약 서버를 직접 돌리는 방식으로만
테스트하게 되면
394. >>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for island in islands:
... if 'Python' in island.name:
... break
... else:
... assert False, 'island not found'
...
>>> lakes = island.find_lakes()
>>> lake = lakes[42]
>>> p = Player(id=1)
>>> p.level = 0
>>> for item in INITIAL_ITEMS:
... p.inventory.add_item(item)
...
>>> homes = island.find_homes()
>>> p.home = homes[999]
>>> p.move_to(lake)
Player-1 moved to lake-42!
이런 부분이 고달플 수 있다는 걸
잘 모르고 넘어갈 수 있어요.
395. >>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nearby(lake)
True
하지만 REPL을 주로 쓰면
자연스레 귀찮고 반복적인 의존성 풀기를
396. >>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nearby(lake)
True
최대한 쉽게 쓸 수 있게끔
추상화하게 되곤 합니다.
397. >>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nearby(lake)
True
그래야 REPL에서 다시 쓰기 편하니까요.
398. >>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nearby(lake)
True
이런 식으로 REPL에서
한 번 돌려본 명령어들을
399. def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
복붙해서 테스트케이스로 구워 두면
두고두고 도움 될 거예요.
400. def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
이렇듯 우리가 만드는 프로그램을
유저로서 테스트하는 데에 그치지 말고
401. def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
REPL을 주요 개발 도구 중 하나로 삼아서
402. def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
API를 직접,
그리고 또 자주 써보는 게
403. def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
API를 더 낫게 만드는 데
도움된다고 생각합니다.
413. > helloworld.py(7)hello_world()
-> return hello
(Pdb) hello
'world'
(Pdb) l
4 def hello_world():
5 hello = 'world'
6 pdb.set_trace()
7 -> return hello
8 hello_world()
[EOF]
아니면 콜스택 위아래를 오간다거나
한 스텝씩 넘겨본다거나 할 수 있어요.
414. > helloworld.py(7)hello_world()
-> return hello
(Pdb++) hello
'world'
(Pdb++) l
4 def hello_world():
5 hello = 'world'
6 pdb.set_trace()
7 -> return hello
8 hello_world()
[EOF]
pdb++ 확장을 깔면
문법강조도 볼 수 있죠.
415. >>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb.pm()
> <stdin>(1)<module>()
(Pdb) x
0
Pdb는 REPL에서 놀다가
에러 났을 때도 유용하게 쓸 수 있어요.
416. >>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb.pm()
> <stdin>(1)<module>()
(Pdb) x
0
pdb.pm(), “부검”을 뜻하는
“Post-mortem”의 약자죠.
417. >>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb.pm()
> <stdin>(1)<module>()
(Pdb) x
0
이것을 실행하면 마지막 에러가 발생했던 곳을
바로 디버깅할 수 있어요.
418. >>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb.pm()
> <stdin>(1)<module>()
(Pdb) x
0
저희 서버도 디버깅 용으로 돌리면
서버 실행 도중 pdb.pm()으로
419. >>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb.pm()
> <stdin>(1)<module>()
(Pdb) x
0
마지막 에러를 디버깅해볼 수 있게
연동해놨어요.
420. >>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb.pm()
> <stdin>(1)<module>()
(Pdb) x
0
다만 디버깅 콘솔 사용법이
처음엔 조금 생소할 수 있는데
421. (Pdb) ?
Documented commands (type help <topic>):
========================================
EOF bt cont enable jump pp run unt
a c continue exit l q s until
alias cl d h list quit step up
args clear debug help n r tbreak w
b commands disable ignore next restart u whatis
break condition down j p return unalias where
Miscellaneous help topics:
==========================
exec pdb
물음표 치면 설명서가 나오니까
422. (Pdb) ?
Documented commands (type help <topic>):
========================================
EOF bt cont enable jump pp run unt
a c continue exit l q s until
alias cl d h list quit step up
args clear debug help n r tbreak w
b commands disable ignore next restart u whatis
break condition down j p return unalias where
Miscellaneous help topics:
==========================
exec pdb
조금씩 찾아보면서 익히다 보면
아마 금방 익숙해질 수 있을 거예요.
439. stack = []
while f:
stack.append(f)
f = f.f_back
stack.reverse()
stack = deque()
while f:
stack.appendleft(f)
이런 식으로 list에 순차적으로 쌓은 다음
뒤집는 경우가 있는데
440. stack = []
while f:
stack.append(f)
f = f.f_back
stack.reverse()
stack = deque()
while f:
stack.appendleft(f)
deque을 쓰면 처음부터
역순으로 쌓을 수 있어요.
441. >>> sub = OrderedDict([('S', 1), ('U', 2), ('B', 3)])
>>> sub['S']
1
>>> sub.keys()
['S', 'U', 'B']
OrderedDict는 dict의 서브클래스인데
그냥 dict와 달리 순서를 보장해줘서
442. >>> sub = OrderedDict([('S', 1), ('U', 2), ('B', 3)])
>>> sub['S']
1
>>> sub.keys()
['S', 'U', 'B']
순서도 중요하고
키로 접근하는 것도 중요할 때 쓰면 좋아요.
456. foo = [1, 10, 100, 1000]
x = bisect_left(foo, 42)
foo.insert(x, 42)
from sortedcontainer import SortedList
bar = SortedList([1, 10, 100, 1000])
bar.add(42)
sortedcontainers는
항상 정렬돼있는 list와 dict를 제공해서
457. foo = [1, 10, 100, 1000]
x = bisect_left(foo, 42)
foo.insert(x, 42)
from sortedcontainer import SortedList
bar = SortedList([1, 10, 100, 1000])
bar.add(42)
굉장히 빠르게 원소를 정렬된 상태로
넣거나 빼는 데 쓸 수 있어요.
458. foo = [1, 10, 100, 1000]
x = bisect_left(foo, 42)
foo.insert(x, 42)
from sortedcontainer import SortedList
bar = SortedList([1, 10, 100, 1000])
bar.add(42)
표준 list나 set으로는
할 수 없는 일이죠.
460. >>> from bidict import bidict
>>> type_table = bidict()
>>> type_table[1] = Ping
>>> type_table[2] = Pong
>>> type_table.inv[Pong]
2
키로 값을 찾는 것 뿐만 아니라
값으로 키를 찾는 것까지 가능해서
461. >>> from bidict import bidict
>>> type_table = bidict()
>>> type_table[1] = Ping
>>> type_table[2] = Pong
>>> type_table.inv[Pong]
2
타입코드 테이블 같은 거
만들 때 유용해요.
486. ❯ python -m timeit
-s 'from exm import only_odds' '
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq)
list(seq)
'
10 loops, best of 3: 137 msec per loop
간단히 벤치마킹해보니까
제너레이터를 쓸 땐 137ms가 걸렸어요.
487. ❯ python -m timeit
-s 'from exm import only_odds' '
seq = list(xrange(1000000))
seq = list(only_odds(seq))
seq = [x ** 2 for x in seq]
'
10 loops, best of 3: 456 msec per loop
반면 매번 이터레이션하는 방식으로는
456ms나 걸리는 걸 볼 수 있었습니다.
488. >>> list(gen)
[1, 2, 3]
>>> list(gen)
[]
def gen():
yield 1
yield 2
yield 3
제너레이터는 딱 한 바퀴만
돌릴 수 있어서
489. >>> list(gen)
[1, 2, 3]
>>> list(gen)
[]
def gen():
yield 1
yield 2
yield 3
여러 번 돌려야 하는 경우에
당황스러울 수 있어요.
491. gen1, gen2 = itertools.tee(gen)
do_something(gen1)
do_another(gen2)
아니면 표준 라이브러리
itertools에 있는 tee를 이용해서
492. gen1, gen2 = itertools.tee(gen)
do_something(gen1)
do_another(gen2)
제너레이터를 여러 개의 뷰로
나눠서 쓸 수 있어요.
493. gen1, gen2 = itertools.tee(gen)
do_something(gen1)
do_another(gen2)
tee는 리눅스 커맨드에서 따온 이름인데
T자형 파이프를 뜻합니다.
494. enemy = player.nearest_player()
while player.alive() or enemy.alive():
player.move_to(enemy)
player.attack(enemy)
yield
제너레이터는 AI 만들 때
쓰기에도 굉장히 편리한데
495. enemy = player.nearest_player()
while player.alive() or enemy.alive():
player.move_to(enemy)
player.attack(enemy)
yield
지금이 어떤 상태고
각종 정보를 다음 틱에 어떻게 넘길지
496. enemy = player.nearest_player()
while player.alive() or enemy.alive():
player.move_to(enemy)
player.attack(enemy)
yield
고민할 필요 없이
그냥 지역변수를 쓰면 되거든요.
497. if self.state is not Fighting:
self.enemy = player.nearest_player()
self.state = Fighting
elif player.alive() or self.enemy.alive():
player.move_to(self.enemy)
player.attack(self.enemy)
제너레이터가 없다면
어떤 형태로든 스테이트머신이 필요하겠죠?
505. ❯ python -m timeit
-s 한 번 실행할 코드 벤치마킹할 코드
timeit은 표준 라이브러리예요.
506. ❯ python -m timeit
-s 한 번 실행할 코드 벤치마킹할 코드
앞에서 제너레이터 벤치마킹할 때
이것을 썼었죠.
507. ❯ python -m timeit
-s 한 번 실행할 코드 벤치마킹할 코드
그냥 쉘에다가 이렇게 치면
508. ❯ python -m timeit
-s 한 번 실행할 코드 벤치마킹할 코드
적당히 너무 오래 걸리지 않는 선에서
반복실행한 다음
509. ❯ python -m timeit
-s 한 번 실행할 코드 벤치마킹할 코드
한 번 돌릴 때 실행시간이
얼마나 걸리는지 알려줍니다.
510. ❯ python -m timeit
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
이런 식으로요.
511. ❯ python -m timeit
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
이건 math.log10()을
벤치마킹하는 모습이에요.
512. ❯ python -m timeit
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
여기서 math 모듈을 import하는 부분은
벤치마킹 대상이 아니잖아요?
513. ❯ python -m timeit
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
그래서 -s 옵션에 넣어서
한 번만 부르게 했어요.
514. ❯ python -m timeit
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
1,000만 번 씩 3번 돌려봤고
515. ❯ python -m timeit
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
그 중 가장 빨랐을 때 log10 한 번에
0.108μs가 걸렸다는 내용입니다.
516. •호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m profile 파이썬 파일
profile 역시 표준 라이브러리인데
517. •호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m profile 파이썬 파일
함수에 진입하고 나가는 이벤트를
모두 기록해서
518. •호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m profile 파이썬 파일
함수 별로 호출 횟수와
실행시간을 측정해주죠.
519. •호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m cProfile 파이썬 파일
표준 라이브러리에 cProfile이라고
C로 만들어서 오버헤드가 적은 버전도 있으니까
520. •호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m cProfile 파이썬 파일
저희처럼 PyPy가 아닌
CPython을 쓰신다면 이걸 쓰세요.
521. ❯ python -m cProfile log10.py
10000002 function calls in 2.269 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.327 0.327 2.269 2.269 log10.py:1(<module>)
10000000 1.942 0.000 1.942 0.000 {math.log10}
측정 결과는
이런 식으로 출력되는데