Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
파이썬 생존 안내서
이흥섭 @ 왓 스튜디오
2016년 10월 11일 넥슨토크
파이썬 생존 안내서
이흥섭 @ 왓 스튜디오
2016년 10월 11일 넥슨토크
안녕하세요.
이흥섭
게임서버 아키텍트
@ 왓 스튜디오
파이썬을 주제로 발표하게 된
왓 스튜디오의 이흥섭입니다.
이흥섭
게임서버 아키텍트
@ 왓 스튜디오
현재 《야생의 땅: 듀랑고》 프로젝트에서
게임서버 아키텍트로 일하고 있습니다.
• 《야생의 땅: 듀랑고》
• 《카트라이더 대시》
• 《카트라이더 코인러시》
• 한글라이즈
• Profiling
• TrueSkill 파이썬 구현
이전엔 《카트라이더 대시》와
《코인러시》 시리즈의 서버를 만들었고
• 《야생의 땅: 듀랑고》
• 《카트라이더 대시》
• 《카트라이더 코인러시》
• 한글라이즈
• Profiling
• TrueSkill 파이썬 구현
틈틈이 GitHub 같은 곳에서
오픈소스 활동도 하고 있어요.
• NDC 2014 ― 《야생의 땅: 듀랑고》 서버 아키텍처
• 파이콘 KR 2015 ― Profiling
• NDC 2016 ― 《야생의 땅: 듀랑고》 서버 아키텍처 Vol. 2
오늘이 제 4번째 발표인데요
• NDC 2014 ― 《야생의 땅: 듀랑고》 서버 아키텍처
• 파이콘 KR 2015 ― Profiling
• NDC 2016 ― 《야생의 땅: 듀랑고》 서버 아키텍처 Vol. 2
재작년과 올해 NDC에서
《듀랑고》 서...
• NDC 2014 ― 《야생의 땅: 듀랑고》 서버 아키텍처
• 파이콘 KR 2015 ― Profiling
• NDC 2016 ― 《야생의 땅: 듀랑고》 서버 아키텍처 Vol. 2
작년 파이콘 KR에선 저희가 만든
파이...
2006년 파이썬 2.4~
저는 2006년, 최신 파이썬 버전이 2.4이던
시절부터 파이썬을 써왔어요.
코드
의도
특히 프로그래머의 의도에 비해서
코드
의도
써야 되는 코드의 양이 적다는 점이
매력으로 느껴졌었는데
코드
의도
아무래도 문법도 C 스타일 언어에 비해선
짧은 코드에 좀 더 맞춰져 있고
코드
의도
표준 라이브러리나
성숙한 써드파티 라이브러리도 풍부해서
코드
의도
어지간한 기능은 직접 짜는 대신
끌어다 쓸 수 있기 때문인 것 같아요.
재작년 NDC 발표에서 소개했듯이
•
《야생의 땅: 듀랑고》 서버는
파이썬으로 만들고 있습니다.
파이썬
파이썬
그럼 본격적인 이야기에 앞서
간단히 파이썬을 소개해 볼게요.
1989년 12월
Guido van Rossum
제가 1989년 12월 생인데
마침 파이썬도 같은 달에 시작됐더라고요.
1989년 12월
Guido van Rossum
그때부터 Guido van Rossum이
만든 언어입니다.
• https://github.com/python
• PEP https://www.python.org/dev/peps/
• 파이콘
파이썬은 오픈소스고
• https://github.com/python
• PEP https://www.python.org/dev/peps/
• 파이콘
사람들이 PEP이라는
파이썬 개선 제안서를 제출하면
• https://github.com/python
• PEP https://www.python.org/dev/peps/
• 파이콘
토론한 후 채택하거나
반려하는 식으로 발전해왔어요.
• https://github.com/python
• PEP https://www.python.org/dev/peps/
• 파이콘
파이콘은 파이썬 공식 컨퍼런스인데
• https://github.com/python
• PEP https://www.python.org/dev/peps/
• 파이콘
전세계에서 정기적으로 열리고 있고
2014년부터는 한국에서도 진행되고 있습니다.
import this
class Example(object):
pass
def example():
return Example()
print 'Hello, world'
파이썬 코드는 대충 이렇게 생겼어요.
import this
class Example(object):
pass
def example():
return Example()
print 'Hello, world'
모듈 체계가 있고
import this
class Example(object):
pass
def example():
return Example()
print 'Hello, world'
클래스와 함수가 있고
import this
class Example(object):
pass
def example():
return Example()
print 'Hello, world'
Java나 C# 등과 다르게 꼭 메소드 속이
아니더라...
for x in numbers:
if 2 < x < 4:
break
else:
print 'not found'
그리고 은근히 편한 문법이 많은데
for x in numbers:
if 2 < x < 4:
break
else:
print 'not found'
가령 숫자 범위를 검사할 때
for x in numbers:
if 2 < x and x < 4:
break
else:
print 'not found'
부등식 2개를 and로 잇는 대신
for x in numbers:
if 2 < x < 4:
break
else:
print 'not found'
부등호를 숫자 양 옆에
바로 쓸 수도 있고
for x in numbers:
if 2 < x < 4:
break
else:
print 'not found'
if 문 뿐만 아니라
루프에도 else 절을 달 수 있어서
for x in numbers:
if 2 < x < 4:
break
else:
print 'not found'
루프가 중간에 멈췄는지
아니면 끝까지 무사히 돌았는지
for x in numbers:
if 2 < x < 4:
break
else:
print 'not found'
표시해둘 변수를
따로 두지 않아도 되죠.
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
다들 아시다시피
파이썬은 동적 타...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
컴파일되는 언어도 아니고
타입을 ...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
그렇다고 타입개념이
아주 희박하진...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
이 사분면은 〈 〉란
글에서 따왔...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
여기서 “강타입”과 “약타입”은
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
얼마나 코드에
명시적으로 쓰지 않...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
저절로 타입캐스팅 되는 경우가
많...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
이 사분면에서 파이썬은
이쯤 위치...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
아주 강타입인 것도 아니지만
그렇...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
나도 모르게 숫자와 문자열 사이를...
Erlang
Clojure
Python
Groovy
Ruby
Magik
C#
F#
Scala
Haskell
C
C++
Perl
VB
PHP
JavaScript
Java
동적 정적
약
강
대신 런타임 에러가 나죠.
과학
SciPy, NumPy, matplotlib
파이썬은 과학계에서
오랫동안 깊게 쓰여왔는데
과학
SciPy, NumPy, matplotlib
scipy, numpy, matplotlib 같은
라이브러리가 이 방면에서 아주 유명합니다.
데이터분석
Jupyter, Pandas, PySpark
요즘은 Scala와 더불어서
데이터분석에도 많이 쓰인다고 해요.
데이터분석
Jupyter, Pandas, PySpark
Jupyter와 Pandas를 이용해서
데이터에 쿼리를 날려보고
데이터분석
Jupyter, Pandas, PySpark
결과를 읽어서 새로운 통찰을 얻은 다음
다시 새로운 쿼리를 날려보는
데이터분석
Jupyter, Pandas, PySpark
“탐색적 자료 분석”을
많이 추천하더라고요.
기계학습
Theano, TensorFlow
김영호, 2016
과학계의 연장인지 최근엔
기계학습 쪽에서도 많이 쓰이고 있어요.
기계학습
Theano, TensorFlow
김영호, 2016
이 영상은 저희 스튜디오 김영호 님이
기계학습
Theano, TensorFlow
김영호, 2016
Google에서 만든 딥러닝 라이브러리인
TensorFlow를 이용해서
기계학습
Theano, TensorFlow
김영호, 2016
알파고처럼 AI에게
총알 피하기 게임을 학습시킨 모습이에요.
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
Ruby에 Rails가 있듯이
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
파이썬에도 Flask나 Django 같은
훌륭한 웹 프레임워크들이 있는데
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
우리나라 스타트업에서
많이 쓰이고 있는 것 같아요.
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
제 홈페이지도 Flask로 만들어져 있죠.
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
또 Requests라고 정말 쓰기 편한
HTTP 클라이언트 라이브러리가 있는데
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
이게 너무 편해서 표준 라이브러리를 꺾고
사실상 표준이 됐어요.
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
저 같은 경우 여러가지
RESTful API 클라이언트 만들 때
웹
Flask, Django, Requests
@app.route('/')
def hello():
return 'Hello, world'
Requests 덕을 많이 봤습니다.
게임
이브온라인, 페리아연대기, pygame
게임 쪽 사례로는
《이브온라인》이 가장 유명한데
게임
이브온라인, 페리아연대기, pygame
코루틴을 지원하는 변종 파이썬인
Stackless Python으로 서버를 만들었어요.
게임
이브온라인, 페리아연대기, pygame
《페리아연대기》에서는 유저 스크립트 언어로
파이썬을 쓰는 것 같았습니다.
게임
이브온라인, 페리아연대기, pygame
pygame이라는
아주 작은 게임 엔진도 있는데
게임
이브온라인, 페리아연대기, pygame
복잡한 건 만들기 어렵지만
간단한 게임 클라이언트 만들기에는 좋아요.
게임
이브온라인, 페리아연대기, pygame
아까 보신 김영호 님의 총알피하기 게임도
클라이언트를 이것으로 만들었어요.
데스크톱 자동화
SikuliX, AutoPy
click( )
type('cmd')
type(Key.ENTER)
그밖에 데스크탑 매크로를
데스크톱 자동화
SikuliX, AutoPy
click( )
type('cmd')
type(Key.ENTER)
스크립트로 짤 수 있게 해주는
프레임워크도 몇 가지 있으니
데스크톱 자동화
SikuliX, AutoPy
click( )
type('cmd')
type(Key.ENTER)
프로그래밍과는 거리가 먼 사람도,
특히 사무직이라면
데스크톱 자동화
SikuliX, AutoPy
click( )
type('cmd')
type(Key.ENTER)
이런 것으로 업무 생산성을
많이 높일 수 있을 것 같아요.
Awesome Python
http://awesome-python.com/
이곳에 들어가시면
제가 소개한 분야 외에도
Awesome Python
http://awesome-python.com/
파이썬이 어떻게 쓰이고 있는지
살펴볼 수 있습니다.
•듀랑고 서버
•유닛테스트
•빌드/배포
•각종 CLI 툴
이제 본론으로 들어가 볼게요.
•듀랑고 서버
•유닛테스트
•빌드/배포
•각종 CLI 툴
저희 스튜디오에선 파이썬을
《듀랑고》 게임서버와 유닛테스트,
•듀랑고 서버
•유닛테스트
•빌드/배포
•각종 CLI 툴
빌드와 배포, 그리고 각종 커맨드라인 툴을
만드는 데 쓰고 있습니다.
1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
...
1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
...
1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
...
1. 파이썬, 인터프리터
2. 동시성
3. 웹 서버
4. 터미널 앱
5. 빌드와 배포
6. 컨벤션 통일
7. REPL
8. 디버깅
9. 자료구조
10. 제너레이터
11. 성능 측정
12. 로드타임
13. 메모리 최적화
...
파이썬,
인터프리터
파이썬,
인터프리터
저희가 쓰는 파이썬 버전과
인터프리터에 대해 먼저 얘기해 볼게요.
CPython, PyPy, Jython,
IronPython, Python for .NET
2.6, 2.7, 3.4, 3.5
파이썬엔 여러가지
인터프리터 구현이 있고
CPython, PyPy, Jython,
IronPython, Python for .NET
2.6, 2.7, 3.4, 3.5
언어 버전에도
여러가지가 있어요.
CPython, PyPy, Jython,
IronPython, Python for .NET
2.6, 2.7, 3.4, 3.5
그 중 저희가 사용하는 인터프리터는
C로 만든 공식 구현체인 CPython,
CPython, PyPy, Jython,
IronPython, Python for .NET
2.6, 2.7, 3.4, 3.5
그리고 언어 버전은 2.7이에요.
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
파이썬 2는 3와
완전히 호환되진 않는데
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
2.7은 그런 파이썬 2 중에서
마지막 버전이죠.
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
파이썬 언어 개발자들은 지금
3에 주력하고 있어서
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
3에서 이룬 수 많은 개선점을
2에는 제공해주지 않고
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
버그픽스와 보안패치
정도만 해주고 있습니다.
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
그래서 지금은 굳이
2를 쓸 이유가 없지만
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
저희가 서버를 만들기 시작한
2013년에만 해도
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
3를 지원하는 써드파티 라이브러리가
그렇게 많지 않아서 2.7을 선택했었는데
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
몇 년 새 상황이 많이
달라졌더라고요.
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
요즘은 써드파티 라이브러리도
어지간해선 3를 지원해요.
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
저희도 넘어가고 싶지만
파이썬 2.7
•파이썬 2 마지막 마이너 버전
•신기능 패치 중단, 버그/보안 패치는 유지
•2013년엔 라이브러리 호환성이 가장 좋았음.
이미 2에 맞춰서 짠 코드가 많아서
쉽게 넘어가진 못 하고 있습니다.
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
파이썬 3를 2와 비교해보면
우선 문자열과 바이트열 구분이 명확해져서
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
둘이 섞어 써서 인코딩 오류가 나는 참사는
발생하지 않아요.
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
또 기존에 있던 표준 라이브러리는
깔끔하게 정돈됐고
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
다만 이 과정에서
하위호환성이 많이 깨졌죠.
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
또 새로운 표준 라이브러리도
여러가지 생겼는데
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
특히 비동기 I/O 라이브러리인 asyncio나
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
파이썬에서 정적 타입힌트를
달 수 있게 해주는 typing 같은 게
파이썬 3?
•명확한 문자열/바이트열 구분
•정돈되고 강해진 표준 라이브러리
•특히 asyncio, typing
아주 탐나더라고요.
못 써서 아쉽습니다.
from __future__ import unicode_literals
import six
아쉬운 대로 저희는 다음 파이썬 버전의
특성 중 일부를 차용할 수 있게 해주는
from __future__ import unicode_literals
import six
__future__ 모듈로 3의 특성 중
유용한 몇 가지를 가져와서 쓰고 있어요.
from __future__ import unicode_literals
import six
한편 저희 내부 코드가 아닌
외부에 공개할 오픈소스 라이브러리를 만들 땐
from __future__ import unicode_literals
import six
같은 코드로 파이썬 2와 3를
함께 지원하기 위해서
from __future__ import unicode_literals
import six
six라는 라이브러리로
호환성을 갖추고 있어요.
from __future__ import unicode_literals
import six=6=2×3
여기서 six는
2 곱하기 3을 뜻한다네요.
PyPy?
파이썬으로 만든 파이썬 인터프리터
공식 구현체인 CPython 말고
PyPy?
파이썬으로 만든 파이썬 인터프리터
파이썬으로 만든 파이썬 인터프리터인
PyPy도 추천합니다.
PyPy?
파이썬으로 만든 파이썬 인터프리터
PyPy는 JavaScript의 V8 처럼
파이썬 코드를 JIT 컴파일 해주는데요
PyPy > CPython
7.5x
여기서 내세우는 비교자료를 보면
CPython보다 7.5배 정도 빠르다고 합니다.
PyPy > CPython
7.5x
다만 C로 구현된 라이브러리 같은 경우는
호환되지 않을 때도 종종 있어서
PyPy > CPython
7.5x
저희는 아직 채택하지 못 했어요.
파이썬 3나 PyPy
저희는 파이썬 2.7과 CPython
둘 다 벗어나지 못 했지만
파이썬 3나 PyPy
그래도 언젠가는
파이썬 3나 PyPy로 넘어가고자 합니다.
파이썬 3나 PyPy
여러분이 만약 새 프로젝트를 시작하신다면
저희보다 나은 선택을 하시길 바랄게요.
동시성
동시성
다음 주제는 동시성이에요.
gevent
코루틴 기반 비동기 I/O
저흰 gevent라는 코루틴 기반
비동기 I/O 라이브러리를 쓰고 있습니다.
greenlet
코루틴
http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
원래 파이썬엔 코루틴이 없지만
greenlet이라는 라이브러리가
greenlet
코루틴
파이썬을 마개조해서
코루틴을 쓸 수 있게 해줬어요.
http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
greenlet
코루틴
이 라이브러리는
PyPy 만든 Armin Rigo와
http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
greenlet
코루틴
Stackless Python 만든
Christian Tismer의 합작이에요.
http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
greenlet
코루틴
아래 링크는 예전에
데브캣스튜디오 이승재 님께서
http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
greenlet
코루틴
greenlet에 대해 쓰셨던 글인데
기억에 남아서 한 번 가져와봤습니다.
http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.j...
g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.j...
g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.j...
g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.j...
g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.j...
g1 = gevent.spawn(requests.get, url1)
g2 = gevent.spawn(requests.get, url2)
g3 = gevent.spawn(requests.get, url3)
gevent.j...
Greenlet
Thread
gevent 스레드는
방금 얘기한 라이브러리 이름과 같은
Greenlet
Thread
“Greenlet”이라고 불러요.
Greenlet
Thread
Greenlet은 유저스페이스에서 도는
경량 스레드라서
Greenlet
Thread
표준 스레드보다
오버헤드가 훨씬 적고 가벼워요.
Greenlet
Thread
그래서 수천 개씩
띄워서 쓸 수 있는데
Greenlet
Thread
특히 네트워크를 많이 다룰 때
높은 동시성을 얻을 수 있죠.
if key in GLOBAL_DICT:
gevent.sleep(0.001)
del GLOBAL_DICT[key]
쓰는 법이 멀티스레딩과 다르지 않다 보니
코딩할 때 고달플 때도 있어요.
if key in GLOBAL_DICT:
gevent.sleep(0.001)
del GLOBAL_DICT[key]
이렇게 첫 줄에서
아무리 조건을 확보해 놔도
if key in GLOBAL_DICT:
gevent.sleep(0.001)
del GLOBAL_DICT[key]
스레드 봉쇄가 발생한 이후에는
if key in GLOBAL_DICT:
gevent.sleep(0.001)
del GLOBAL_DICT[key] KeyError!
조건이 무효해질 수 있거든요.
if key in GLOBAL_DICT:
gevent.sleep(0.001)
del GLOBAL_DICT[key] KeyError!
그럼에도 저희가
gevent를 택했던 이유는
import gevent.monkey
gevent.monkey.patch_all()
이미 동기식으로 작성돼있는
import gevent.monkey
gevent.monkey.patch_all()
표준 라이브러리를 비롯한
수 많은 라이브러리를
import gevent.monkey
gevent.monkey.patch_all()
모두 그대로 쓸 수 있다는 점 때문이었어요.
import gevent.monkey
gevent.monkey.patch_all()
gevent가 제공하는 멍키패칭을 돌리면
import gevent.monkey
gevent.monkey.patch_all()
threading이나 socket 같은
표준 라이브러리가
import gevent.monkey
gevent.monkey.patch_all()
전부 gevent 용으로 갈아치워져서
import gevent.monkey
gevent.monkey.patch_all()
비동기 I/O를 고려하지 않던 기존 코드까지도
모두 동시에 실행할 수 있게 되거든요.
import gevent.monkey
gevent.monkey.patch_all()
JavaScript처럼 콜백을 등록한다거나
C#처럼 async, await 키워드를 쓴다거나
import gevent.monkey
gevent.monkey.patch_all()
그런 특별한 코딩법을 필요로 하는
다른 동시성 모델로는
import gevent.monkey
gevent.monkey.patch_all()
달성할 수 없는 강점이죠.
동시성 (Concurrency)
병렬성 (Parallelism)
단 gevent로 확보할 수 있는 건
동시성이지 병렬성은 아닌데
동시성 (Concurrency)
병렬성 (Parallelism)
수 많은 I/O를 동시에 수행할 순 있지만
실제로는 싱글스레디드로 돌아서
동시성 (Concurrency)
병렬성 (Parallelism)
여러 CPU 코어를 활용할 순 없어요.
GIL
사실 이 점은 파이썬
표준 멀티스레딩도 마찬가지입니다.
GIL
파이썬엔 역사적인 이유로
Global Interpreter Lock이란 게 있는데
GIL
이 때문에 파이썬 바이트코드는
동시에 여러 스레드에서 실행될 수 없거든요.
+멀티프로세싱
그래서 저희는 부족한 병렬성을 보완하기 위해
멀티프로세싱도 같이 쓰고 있습니다.
def hang():
while True:
pass
gevent.spawn(hang)
표준 멀티스레딩과 달리
gevent를 쓸 땐 주의해야 할 점이 있는데요
def hang():
while True:
pass
gevent.spawn(hang)
선점형 멀티태스킹이 아니다 보니
한 스레드가 쉴 새 없이 돌면
def hang():
while True:
pass
gevent.spawn(hang)
다른 스레드로는
기회가 넘어가지 않는다는 거예요.
def hang():
while True:
pass
gevent.spawn(hang)
특히 게임서버에서는
이런 일이 절대 생기면 안 되겠죠.
def hang():
while True:
gevent.idle()
gevent.spawn(hang)
꼭 sleep이나 idle이라도 넣어서
def hang():
while True:
gevent.idle()
gevent.spawn(hang)
스레드가 잠시나마 봉쇄되게끔 만들어야
이런 일을 방지할 수 있어요.
asyncio?
파이썬 3.4~
한편 파이썬 3.4부터는
asyncio라는
asyncio?
파이썬 3.4~
차세대 비동기 I/O 라이브러리를
쓸 수 있는데
@asyncio.coroutine
def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3...
@asyncio.coroutine
def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3...
async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
...
async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
...
async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
...
async def main():
f1 = aiohttp.get(url1)
f2 = aiohttp.get(url2)
f3 = aiohttp.get(url3)
for fut in [f1, f2, f3]:
await fut
...
aiobotocore, aiohttp, aiokafka,
aiomcache, aiomysql, aioredis,
aiorwlock, aiozmq
그래도 보통 이름에 “aio”라는 접두사가
붙어있으니까 쉽게 구별할 순 있...
•멀티프로세싱
•멀티스레딩
•asyncio
•gevent
•eventlet
•Twisted
제가 소개한 것 외에도 파이썬에선
여러가지 방법으로 동시성을 확보할 수 있어요.
•멀티프로세싱
•멀티스레딩
•asyncio
•gevent
•eventlet
•Twisted
각 방법에는 서로 다른
트레이드오프가 있죠.
•멀티프로세싱
•멀티스레딩
•asyncio
•gevent
•eventlet
•Twisted
만약 여러분도
파이썬으로 서버를 만드신다면
•멀티프로세싱
•멀티스레딩
•asyncio
•gevent
•eventlet
•Twisted
이런 방법들을 한 번 비교해보고
가장 적합한 걸 고르시면 좋을 것 같습니다.
웹 서버
웹 서버
저희 게임서버는 대부분이
소켓을 직접 다루는 데디케이티드 서버지만
웹 서버
일부는 RESTful한 웹 서버로 돼있어요.
Flask
거기엔 앞에서도 소개했던 Flask라는
간단한 웹 서버 프레임워크를 쓰는데
•Werkzeug 웹 서버 유틸리티
•Jinja2 템플릿 엔진
Flask는 Werkzeug라는
웹 서버 유틸리티와
•Werkzeug 웹 서버 유틸리티
•Jinja2 템플릿 엔진
Jinja2라고, 보통 HTML 페이지 찍어낼 때
쓰는 템플릿엔진 위에서
•템플릿 엔진
•URL 라우팅
•세션
•서브도메인
•JSON 지원
•디버깅 콘솔
몇 가지 편의 기능을 함께 제공하는
작은 웹 서버 프레임워크예요.
•템플릿 엔진
•URL 라우팅
•세션
•서브도메인
•JSON 지원
•디버깅 콘솔
작은 프레임워크이긴 하지만
•템플릿 엔진
•URL 라우팅
•세션
•서브도메인
•JSON 지원
•디버깅 콘솔
일반적으로 웹 개발할 때 필요한
간단한 기능들은 대부분 갖추고 있고
•템플릿 엔진
•URL 라우팅
•세션
•서브도메인
•JSON 지원
•디버깅 콘솔
좀 더 복잡한 기능도
플러그인으로 쉽게 구할 수 있어요.
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, world', 200
간단한 Flask 예제예요.
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, world', 200
데코레이터를 이용해서 URL 라우팅 테이블을
직관적으로 만들어내는 게 특징이죠.
#include "crow.h"
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")([](){
return "Hello world";
});
}
우리 회사 하재승 님이 만드신
#include "crow.h"
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")([](){
return "Hello world";
});
}
C++ 웹 프레임워크 Cro...
WSGI
Web Server Gateway Interface
파이썬엔 WSGI라는 표준화된
웹 애플리케이션 인터페이스가 있어요.
WSGI
Web Server Gateway Interface
Flask를 비롯해 아마도 모든
파이썬 웹 서버 라이브러리가
WSGI
Web Server Gateway Interface
이 인터페이스를 따르고 있을 거예요.
app = Flask(__name__)
server = gevent.pywsgi.WSGIServer(socket, app)
server.serve_forever()
이 표준 인터페이스 덕분에
Flask를 gevent에도...
app = Flask(__name__)
server = gevent.pywsgi.WSGIServer(socket, app)
server.serve_forever()
gevent엔 고성능 WSGI 서버가 들어있는데
app = Flask(__name__)
server = gevent.pywsgi.WSGIServer(socket, app)
server.serve_forever()
이것으로 Flask 웹 서버를
바로 서빙할 수 있는 거...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
yield ...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
yield ...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
yield ...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
yield ...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
yield ...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
return...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
return...
def wsgi_app(environ, start_response):
headers = [('Content-Type', 'text/plain')]
start_response('200 OK', headers)
return...
HTTP/2.0, WebSocket
아쉽게도 서버사이드 푸시 같은
HTTP/2.0 기능이나
HTTP/2.0, WebSocket
웹소켓 같은 요즘 프로토콜을
구현하진 못해요.
HTTP/2.0, WebSocket
그래도 메일링리스트에서
논의는 되고 있으니까
HTTP/2.0, WebSocket
언젠가 차세대 WSGI가
나오길 기대해봅니다.
Django?
파이썬 웹 프레임워크 중에선
저는 안 써봤지만
Django?
Flask보단 Django가
훨씬 유명하고 많이 쓰여요.
Django
Flask
구글 트렌드에서 찾아보니까
인기도가 Flask의 4배 정도는 되더라고요.
Instagram과 Pinterest가
Django 쓰는 것으로 유명하다고 합니다.
• ORM
• 템플릿 엔진
• URL 라우팅
• 폼 검증
• 캐싱
• 국제화
• XML/JSON 지원
Django는 MVC 구조를 내장하고 있어요.
• ORM
• 템플릿 엔진
• URL 라우팅
• 폼 검증
• 캐싱
• 국제화
• XML/JSON 지원
그래서 Flask와 달리
ORM까지 포함하고 있죠.
• ORM
• 템플릿 엔진
• URL 라우팅
• 폼 검증
• 캐싱
• 국제화
• XML/JSON 지원
그 밖에도 통합돼있는 기능이 매우 많은데
• ORM
• 템플릿 엔진
• URL 라우팅
• 폼 검증
• 캐싱
• 국제화
• XML/JSON 지원
아마 2000년대 후반에 유행했던
Ruby on Rails랑 비슷하지 않을까 싶어요.
•WSGI
•Flask
•Django
혹시 파이썬으로 웹 서버를
만들려는 분이 계신다면
•WSGI
•Flask
•Django
이 두 프레임워크와
WSGI 지원하는 도구를 한 번 검토해보세요.
터미널 앱
터미널 앱
저희는 서버 개발을 보조하기 위해서
터미널 앱
터미널에서 쓸 수 있는 도구도
많이 만들고 있어요.
Click
CLI 프레임워크
Click은 CLI를 쉽게,
또 잘 만들 수 있게 해주는 프레임워크예요.
Click
CLI 프레임워크
앞에서 소개했던 Flask와 이것은
모두 Armin Ronacher가 만들었죠.
import click
@click.command()
@click.option('-n', '--name', help='Your name.')
def cli(name):
click.echo('Hi %s!' % (name ...
import click
@click.command()
@click.option('-n', '--name', help='Your name.')
def cli(name):
click.echo('Hi %s!' % (name ...
import click
@click.command()
@click.option('-n', '--name', help='Your name.')
def cli(name):
click.echo('Hi %s!' % (name ...
import click
@click.command()
@click.option('-n', '--name', help='Your name.')
def cli(name):
click.echo('Hi %s!' % (name ...
❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT...
❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT...
❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT...
❯ python cli.py -n "Heungsub Lee"
Hi Heungsub Lee!
❯ python cli.py --help
Usage: cli.py [OPTIONS]
Options:
-n, --name TEXT...
Click을 이용하면
이렇게 사용자입력을 받거나
프로그레스바를 띄우는 것도 쉽게 할 수 있고
색깔도 깔끔하게 다룰 수 있어요.
argparse?
Click이 나오기 전에는
argparse라는 표준 라이브러리를 썼었는데
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-n', '--name', help='Your name.')
def cli(name):
p...
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-n', '--name', help='Your name.')
def cli(name):
p...
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-n', '--name', help='Your name.')
def cli(name):
p...
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-n', '--name', help='Your name.')
def cli(name):
p...
Urwid
TUI 프레임워크
단순한 CLI 말고
리눅스의 top이나 aptitude 같이
Urwid
TUI 프레임워크
텍스트로 된 UI를 만들고 싶을 땐
Urwid라는 TUI 프레임워크를 쓰고 있어요.
Urwid를 쓰면 위젯을 조합하는 방식으로
꽤 미려한 텍스트 UI를 만들 수 있죠.
https://excess.org/article/2012/01/urwid-python-malaysia/
이건 Urwid로 만든 Speedometer라는 건데
멋지지 않나요?
https://excess.org/article/2012/01/urwid-python-malaysia/
저희 스튜디오에서 만든
파이썬 용 프로파일러에도 Urwid를 사용했는데
https://excess.org/article/2012/01/urwid-python-malaysia/
그건 뒤에서 성능 측정에 대해
얘기할 때 보여드릴게요.
curses?
Urwid 말고는 curses라는
표준 라이브러리가 대안이 될 수 있어요.
curses?
하지만 저는 정말 쓰기 불편했어요.
curses?
API가 너무 저수준이고
조금만 실수해도 에러가 빵빵 터졌었거든요.
win.addstr(y, x, 'Hello, world')
심지어 사용하는 좌표계도
win.addstr(y, x, 'Hello, world')
x, y가 아니라 y, x예요.
win.addstr(y, x, 'Hello, world')
“curse”가 “저주”란 뜻이잖아요?
그 이름값을 하는 거라고 생각합니다.
•Click
•Urwid
•argparse
•curses
혹시 bash 같은 쉘 스크립트를 대신하기 위해서
파이썬을 검토 중이시라면
•Click
•Urwid
•argparse
•curses
Click이랑 Urwid도 같이 검토해보세요.
•Click
•Urwid
•argparse
•curses
표준 라이브러리인 argparse나
curses보다 분명히 좋습니다.
빌드와 배포
빌드와 배포
저희는 빌드와 배포를 자동화하는 데에도
노력을 많이 기울여왔어요.
빌드와 배포
여기에도 파이썬을 많이 쓰고 있죠.
build passing
작업자가 Git 저장소에 push하면
CI가 자동으로 빌드 스크립트를 돌리면서
build passing
컴파일해야 할 건 컴파일하고,
코드 품질을 검사한다든가,
build passing
유닛테스트를 돌린다든가,
뭐 그런 일을 합니다.
build passing
빌드에 성공하면 브랜치에 따라서
서버에 배포하는 것까지 연계하고 있어요.
pytest
이때 유닛테스트 프레임워크로
pytest를 쓰는데
def test_answer():
assert answer() == 42
pytest를 쓰면
딸랑 assert 문만 적어놔도
❯ pytest test.py
========= FAILURES =========
E assert 24 == 42
E + where 24 = answer()
def test_answer():
assert answer()...
❯ pytest test.py
========= FAILURES =========
E assert 24 == 42
E + where 24 = answer()
def test_answer():
assert answer()...
def test_answer(self):
self.assertEqual(answer(), 42)
unittest
파이썬엔 unittest라는
표준 유닛테스트 프레임워크도 있지만
def test_answer(self):
self.assertEqual(answer(), 42)
unittest
이것으로 같은 결과를 얻으려면
assertEqual 같은 전용 메소드를 써야만 해요.
assertEqual(a, b)
assertNotEqual(a, b)
assertIs(a, b)
assertIn(a, b)
assertGreater(a, b)
unittest
assert a == b
assert a !...
assertEqual(a, b)
assertNotEqual(a, b)
assertIs(a, b)
assertIn(a, b)
assertGreater(a, b)
unittest
assert a == b
assert a !...
assertEqual(a, b)
assertNotEqual(a, b)
assertIs(a, b)
assertIn(a, b)
assertGreater(a, b)
unittest
assert a == b
assert a !...
assertEqual(a, b)
assertNotEqual(a, b)
assertIs(a, b)
assertIn(a, b)
assertGreater(a, b)
unittest
assert a == b
assert a !...
@pytest.mark.parametrize('x, y', [
(42, 1), (21, 2), (7, 6)
])
def test_answers(x, y):
assert x * y == 42
가령 테스트케이스에 매개변수를...
@pytest.mark.parametrize('x, y', [
(42, 1), (21, 2), (7, 6)
])
def test_answers(x, y):
assert x * y == 42
알아서 인자를 바꿔가면서 테스...
@pytest.fixture
def db(request):
db = Database()
request.addfinalizer(db.close)
return db
def test_db(db):
db.insert('answ...
@pytest.fixture
def db(request):
db = Database()
request.addfinalizer(db.close)
return db
def test_db(db):
db.insert('answ...
@pytest.fixture
def db(request):
db = Database()
request.addfinalizer(db.close)
return db
def test_db(db):
db.insert('answ...
def test_answer_pid(monkeypatch):
monkeypatch.setattr(time, 'time', answer)
assert time.time() == 42
monkeypatch라는 픽스처도
기본...
def test_answer_pid(monkeypatch):
monkeypatch.setattr(time, 'time', answer)
assert time.time() == 42
이것을 쓰면 이 테스트에 한해서
다른 ...
def test_answer_pid(monkeypatch):
monkeypatch.setattr(time, 'time', answer)
assert time.time() == 42
시간을 다루거나 외부 API를 쓰는
코...
pytest-cov
pytest에도 역시
플러그인이 무척 많은데
pytest-cov
그 중 pytest-cov를 쓰면
줄 단위 테스트 커버리지를 구할 수 있어요.
❯ pytest test.py --cov=durango
---------- coverage ----------
TOTAL 42 42 100%
테스트 돌릴 때
❯ pytest test.py --cov=durango
---------- coverage ----------
TOTAL 42 42 100%
커버리지 측정할
모듈을 같이 적어주면
❯ pytest test.py --cov=durango
---------- coverage ----------
TOTAL 42 42 100%
그 모듈에 총 몇 줄이 있고
유닛테스트가 그 중에서
❯ pytest test.py --cov=durango
---------- coverage ----------
TOTAL 42 42 100%
얼마나 건드렸는지
정확하게 알 수 있죠.
coverage 71%
저희는 이 방법으로 테스트 커버리지를
지속적으로 관리해왔고
coverage 71%
충분히 높은 건 아니지만
약 71% 정도를 확보해둔 상태입니다.
•pytest-rerunfailures
•pytest-xdist
그 밖에도 테스트케이스가 실패했을 때
•pytest-rerunfailures
•pytest-xdist
몇 번은 재시도할 수 있게끔 해주는
pytest-rerunfailures란 게 있는데
•pytest-rerunfailures
•pytest-xdist
보통 시간에 민감한 테스트케이스는
미묘한 시간차로 실패할 때가 있거든요.
•pytest-rerunfailures
•pytest-xdist
이때 쓰면 좋아요.
•pytest-rerunfailures
•pytest-xdist
또 테스트 케이스들을 나눠서
여러 스레드나 머신에서 병렬적으로 돌려주는
•pytest-rerunfailures
•pytest-xdist
pytest-xdist 같은
유용한 플러그인이 많이 있으니까
•pytest-rerunfailures
•pytest-xdist
이것저것 찾아보고
함께 도입해보시면 좋을 것 같습니다.
Fabric
서버 소스코드가 빌드를 통과하면
적당한 머신에 자동으로 배포되는데
Fabric
여기엔 Fabric를 이용하고 있어요.
빌드머신
원격
원격
원격
원격
원격
원격
원격
원격
원격
명령어
보통 여러 원격 머신에
한 번에 명령어를 날리려면
빌드머신
원격
원격
원격
원격
원격
원격
원격
원격
원격
에이전트
명령어
명령어를 받아줄
에이전트를 띄워 둬야 하는데요
빌드머신
원격
원격
원격
원격
원격
원격
원격
원격
원격
명령어를 SSH로
보통은 sshd가 기본으로 깔려 있잖아요?
빌드머신
원격
원격
원격
원격
원격
원격
원격
원격
원격
명령어를 SSH로
Fabric은 SSH를 명령 채널로 써서
원격 머신에 추가적으로 아무것도 깔지 않아도
빌드머신
원격
원격
원격
원격
원격
원격
원격
원격
원격
명령어를 SSH로
바로 명령어를 날릴 수 있어요.
@task
def setup_durango():
if files.exists('durango'):
run('cd durango')
run('git pull')
else:
run('git clone %s durango' ...
@task
def setup_durango():
if files.exists('durango'):
run('cd durango')
run('git pull')
else:
run('git clone %s durango' ...
@task
def setup_durango():
if files.exists('durango'):
run('cd durango')
run('git pull')
else:
run('git clone %s durango' ...
@task
def setup_durango():
if files.exists('durango'):
run('cd durango')
run('git pull')
else:
run('git clone %s durango' ...
@task
def setup_durango():
if files.exists('durango'):
run('cd durango')
run('git pull')
else:
run('git clone %s durango' ...
fabtools
바로 Fabric의 보조도구인
fabtools입니다.
@task
def setup_durango():
require.git.working_copy(git_url, 'durango', update=True)
fabtools는 방금 본 것과 같은
각종 예외처리 분기를
@task
def setup_durango():
require.git.working_copy(git_url, 'durango', update=True)
무엇무엇이 필요하다는 형태로
추상화 해줘요.
@task
def setup_durango():
require.git.working_copy(git_url, 'durango', update=True)
이 코드는
원격 머신이 어떤 상황이든 간에
@task
def setup_durango():
require.git.working_copy(git_url, 'durango', update=True)
Git 저장소가 필요하니
알아서 만들어 달라는 뜻이에요.
@task
def setup_durango():
require.git.working_copy(git_url, 'durango', update=True)
fabtools 덕분에
절차적이던 저희 배포 스크립트를
@task
def setup_durango():
require.git.working_copy(git_url, 'durango', update=True)
선언적으로 만들 수 있게 됐죠.
coverage 71%build passing
저희에게 지속적인 빌드와 배포는
coverage 71%build passing
병합요청과 코드리뷰,
그리고 QA 절차에 필수적이었어요.
coverage 71%build passing
제가 소개한 도구들을
적당한 CI에서 이용하면
coverage 71%build passing
여러분의 개발환경을 개선하는 데에도
도움이 될 것 같습니다.
컨벤션 통일
컨벤션 통일
다른 모든 언어와 마찬가지로
컨벤션 통일
파이썬에서도 코딩 컨벤션을
통일해두는 게
컨벤션 통일
좋은 코드를 짜기에도 좋고
나중에 유지보수 하기에도 편리해요.
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
파이썬엔 표준 컨벤션이 있어요.
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
PEP8이라는
8번째 제안서에서 규정하고 있죠.
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
하드탭 대신 4칸짜리 스페이스를
써야 한다든가
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
코드의 최대 폭이
79칸을 넘지 말아야 한다든가
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
import문 종류에 따른
순서를 지켜야 한다든가
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
snake_case나 PascalCase 등을
용도에 맞춰서 써야 한다든가
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
뭐 그런 식이에요.
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
여기엔 아무리 파이썬에 익숙해져도
PEP8
•4칸 소프트탭
•79칸 제한
•import 순서
•snake_case, PascalCase
손으로 일일이 맞추기는
어려운 규칙도 많아요.
Flake8
그래서 저희는 Flake8이라는
파이썬 코드 품질 검사기를 쓰고 있어요.
❯ 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 inde...
❯ 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 inde...
❯ 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 inde...
❯ 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 inde...
•flake8-import-order
•flake8-print
•flake8-commas
Flake8에도 플러그인이 무척 많아서
•flake8-import-order
•flake8-print
•flake8-commas
프로젝트 내부 컨벤션에도
맞춰서 쓸 수 있어요.
•flake8-import-order
•flake8-print
•flake8-commas
flake8-import-order는
아까 얘기한 import문의
•flake8-import-order
•flake8-print
•flake8-commas
종류별 순서 뿐 아니라
ABC 순 정렬까지도 맞춰주고
•flake8-import-order
•flake8-print
•flake8-commas
flake8-print는 정식 코드에
print문이 들어가는 걸 막아주며
•flake8-import-order
•flake8-print
•flake8-commas
flake8-commas는 list나 dict 등의
리터럴을 여러 줄에 걸쳐서 쓸 때
•flake8-import-order
•flake8-print
•flake8-commas
항상 마지막 콤마를 찍게끔
강제해줘요.
•flake8-import-order
•flake8-print
•flake8-commas
물론 이 외에도
아주 많은 플러그인이 있죠.
Vim+Syntastic|pymode
Vim 쓰시는 분 많죠?
Vim+Syntastic|pymode
Vim 플러그인인
Syntastic이나 pymode를 쓰면
Vim+Syntastic|pymode
이렇게 에디터에서도
실수를 바로바로 확인할 수 있어요.
Vim+Syntastic|pymode
저는 이렇게 작업합니다.
Emacs+Flycheck
저는 Vim파지만 저희 스튜디오에
Emacs 쓰시는 분도 있는데
Emacs+Flycheck
그분은 같은 용도로
Flycheck라는 걸 쓴다고 해요.
Emacs+Flycheck
마찬가지로 Flake8 결과를
에디터에서 바로 볼 수 있어요.
• PEP8
• The Pocoo Style Guide
PEP8은 파이썬 커뮤니티에선
절대적이라서
• PEP8
• The Pocoo Style Guide
특히 오픈소스 활동을 할 때는
반드시 지키는 게 좋아요.
• PEP8
• The Pocoo Style Guide
하지만 PEP8만으로는
간혹 모호한 경우도 있는데
• PEP8
• The Pocoo Style Guide
그래서 저희는 PEP8을 기반으로
좀 더 구체적이고 실용적인 규칙을 제시하는
• PEP8
• The Pocoo Style Guide
Pocoo 스타일을 따르고 있습니다.
REPL
REPL
저는 파이썬으로 코딩할 때
REPL을 굉장히 많이 써요.
REPL
REPL은
Read-Evaluate-Print Loop의 약자인데
REPL
터미널에서 인자 없이 python 치면 나오는
대화형 인터프리터 콘솔 있잖아요?
REPL
그런 걸 가리켜요.
REPL
코드를 파일에 적어서
실행하는 것도 물론 좋지만
REPL
문제를 탐색해나가는
REPL만의 방식에도 매력이 있습니다.
❯ python
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
[GCC 5.3.1 20160413] on linux2
Type "help", "copyright", "credits...
❯ ipython
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
Type "copyright", "credits" or "license" for
more information.
I...
❯ ipython
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
Type "copyright", "credits" or "license" for
more information.
I...
❯ ipython
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
Type "copyright", "credits" or "license" for
more information.
I...
Jupyter Notebook인데
중간중간 문서도 집어넣을 수도 있고
데이터를 터미널에서보다
훨씬 풍부하게 시각화할 수 있어요.
그래서 데이터 과학 쪽에선
앞에서 소개했듯이
탐색적 분석하는 데에
이것을 많이 쓴다고 합니다.
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> world_id = db.find_all(World.id)[0]
>>> world = db.load(World, world_id)
>>> islands = world.all_islands()
>>> for isl...
>>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nea...
>>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nea...
>>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nea...
>>> lake = find_lake('*/42')
>>> p = make_player(1, mock=True)
>>> p.move_to(lake)
Player-1 moved to lake-42!
>>> p.is_nea...
def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
복붙해서 테...
def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
이렇듯 우리...
def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
REPL을 ...
def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
API를 직...
def test_move_to():
lake = find_lake('*/42')
p = make_player(1, mock=True)
p.move_to(lake)
assert p.is_nearby(lake)
API를 더...
디버깅
디버깅
이번엔 파이썬 코드를
디버깅하는 방법이에요.
Pdb
파이썬 표준 라이브러리엔 Pdb라고
gdb 같은 콘솔 디버거가 들어있어요.
def hello_world():
hello = 'world'
pdb.set_trace()
return hello
이렇게 코드 사이에서
pdb.set_trace()를 불러주면
def hello_world():
hello = 'world'
pdb.set_trace()
return hello
다른 IDE에서 많이 보셨을
브레이크포인트가 걸려요.
> helloworld.py(7)hello_world()
-> return hello
(Pdb) hello
'world'
(Pdb) l
4 def hello_world():
5 hello = 'world'
6 pdb.s...
> helloworld.py(7)hello_world()
-> return hello
(Pdb) hello
'world'
(Pdb) l
4 def hello_world():
5 hello = 'world'
6 pdb.s...
> helloworld.py(7)hello_world()
-> return hello
(Pdb) hello
'world'
(Pdb) l
4 def hello_world():
5 hello = 'world'
6 pdb.s...
> helloworld.py(7)hello_world()
-> return hello
(Pdb) hello
'world'
(Pdb) l
4 def hello_world():
5 hello = 'world'
6 pdb.s...
> helloworld.py(7)hello_world()
-> return hello
(Pdb) hello
'world'
(Pdb) l
4 def hello_world():
5 hello = 'world'
6 pdb.s...
> helloworld.py(7)hello_world()
-> return hello
(Pdb++) hello
'world'
(Pdb++) l
4 def hello_world():
5 hello = 'world'
6 p...
>>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb....
>>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb....
>>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb....
>>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb....
>>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb....
>>> x = 0
>>> x / x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: ...
>>> pdb....
(Pdb) ?
Documented commands (type help <topic>):
========================================
EOF bt cont enable jump pp run u...
(Pdb) ?
Documented commands (type help <topic>):
========================================
EOF bt cont enable jump pp run u...
PTVS
저희 스튜디오에서
비주얼스튜디오를 선호하시는 분들은
PTVS
마이크로소프트에서 만든
파이썬 개발 플러그인인 PTVS를 씁니다.
PTVS 디버거도 굉장히 훌륭해서
서버에 에이전트를
같이 띄워주기만 하면
비주얼스튜디오 IDE를 한껏 활용하면서
GUI로 편하게 디버깅할 수 있어요.
print(strange_value)
이렇듯 파이썬에서 print로만
디버깅할 수 있는 건 아니니까
print(strange_value)
여러가지 편한 도구를 찾아서
사용해보시면 좋겠습니다.
자료구조
자료구조
프로그램 만들 땐 언제나
가장 적절한 자료구조를 골라야 하죠.
자료구조
그래야 올바른 코드를 짤 수 있고
또 성능을 높이는 데도 도움 되니까요.
자료구조
list나 set 같은 빌트인 자료구조는
이미 다 아실 테니까
자료구조
그 밖의 유용한
파이썬 자료구조를 소개해볼게요.
collections
우선 collections
표준 라이브러리부터 살펴볼게요.
list.insert(0, x)
deque.appendleft(x)
collections 안에 있는 deque은
list.insert(0, x)
deque.appendleft(x)
상수시간에 앞뒤 양 끝 요소를
조작할 수 있는 자료구조예요.
list.insert(0, x)
deque.appendleft(x)
반면 list는 맨 뒤가 아니면
상수시간에 추가하거나 뺄 수 없죠.
stack = []
while f:
stack.append(f)
f = f.f_back
stack.reverse()
stack = deque()
while f:
stack.appendleft(f)
이런 식으로 list에...
stack = []
while f:
stack.append(f)
f = f.f_back
stack.reverse()
stack = deque()
while f:
stack.appendleft(f)
deque을 쓰면 처음...
>>> sub = OrderedDict([('S', 1), ('U', 2), ('B', 3)])
>>> sub['S']
1
>>> sub.keys()
['S', 'U', 'B']
OrderedDict는 dict의 서브클...
>>> sub = OrderedDict([('S', 1), ('U', 2), ('B', 3)])
>>> sub['S']
1
>>> sub.keys()
['S', 'U', 'B']
순서도 중요하고
키로 접근하는 것도 중요...
>>> Vector2 = namedtuple('Vector2', ['x', 'y'])
>>> xy = Vector2(20, 59)
>>> xy.x
20
>>> x, y = xy
namedtuple은 tuple의 서브클래...
>>> Vector2 = namedtuple('Vector2', ['x', 'y'])
>>> xy = Vector2(20, 59)
>>> xy.x
20
>>> x, y = xy
이때 각 원소에
이름을 붙일 수 있어요.
>>> Vector2 = namedtuple('Vector2', ['x', 'y'])
>>> xy = Vector2(20, 59)
>>> xy.x
20
>>> x, y = xy
이것으로 만든 클래스도 tuple 처럼 불...
>>> Vector2 = namedtuple('Vector2', ['x', 'y'])
>>> xy = Vector2(20, 59)
>>> xy.x
20
>>> x, y = xy
저는 사소한 클래스 만들 땐
class문을...
>>> Vector2 = namedtuple('Vector2', ['x', 'y'])
>>> xy = Vector2(20, 59)
>>> xy.x
20
>>> x, y = xy
이 쪽을 더 선호하는 편이에요.
>>> c = Counter('nexon')
>>> c['n']
2
>>> c['x']
1
Counter는 dict의 서브클래스고
이렇게 뭔가 간단히 셀 때 쓰기 좋아요.
>>> d = defaultdict(int)
>>> d['yo']
0
defaultdict는 없는 키에 접근했을 때
KeyError를 내는 대신
>>> d = defaultdict(int)
>>> d['yo']
0
팩토리함수 실행 결과를
채워주는 dict예요.
def inf_dict():
return defaultdict(inf_dict)
이건 파이콘 KR 2014
구종만 님 발표에서 봤던 코드인데
def inf_dict():
return defaultdict(inf_dict)
defaultdict 만드는 함수 자체를
defaultdict의 팩토리함수로 쓰고 있죠?
root = inf_dict()
root[0][1][2][3][4][5][6][7] = 'So deep!'
그래서 아무 키나
끊임 없이 써내려 갈 수 있어요.
써드파티
collections에만 해도
유용한 자료구조가 많이 있지만
써드파티
써드파티에는 더 재밌는 게 많아요.
foo = [1, 10, 100, 1000]
x = bisect_left(foo, 42)
foo.insert(x, 42)
from sortedcontainer import SortedList
bar = SortedLis...
foo = [1, 10, 100, 1000]
x = bisect_left(foo, 42)
foo.insert(x, 42)
from sortedcontainer import SortedList
bar = SortedLis...
foo = [1, 10, 100, 1000]
x = bisect_left(foo, 42)
foo.insert(x, 42)
from sortedcontainer import SortedList
bar = SortedLis...
>>> from bidict import bidict
>>> type_table = bidict()
>>> type_table[1] = Ping
>>> type_table[2] = Pong
>>> type_table.i...
>>> from bidict import bidict
>>> type_table = bidict()
>>> type_table[1] = Ping
>>> type_table[2] = Pong
>>> type_table.i...
>>> from bidict import bidict
>>> type_table = bidict()
>>> type_table[1] = Ping
>>> type_table[2] = Pong
>>> type_table.i...
from cachetools import LRUCache
cache = LRUCache(256)
cachetools에는
LRU나 TTL을 비롯한
from cachetools import LRUCache
cache = LRUCache(256)
여러가지 캐싱 정책을 구현한
dict의 서브클래스들이 들어있어요.
from cachetools import LRUCache
cache = LRUCache(256)
서버에 캐싱을 구현할 일이 종종 있는데
그럴 때 쓰기 편하더라고요.
namedtuple, deque, OrderedDict,
SortedList, bidict, LRUCache, ...
이렇게 빌트인 말고도 여러가지 자료구조를
라이브러리에서 구할 수 있으니까
namedtuple, deque, OrderedDict,
SortedList, bidict, LRUCache, ...
각각의 용도와 특성을 파악해서
더 효율적인 코드를 쉽게 짜는 데 써보세요.
제너레이터
제너레이터
파이썬에는 함수 실행을 도중에 멈췄다가
재개시킬 수 있는 제너레이터가 있죠.
제너레이터
앞서 소개한 asyncio의
재료가 되기도 했고요.
제너레이터
C# 쓰시는 분들은
이미 익숙하실 거예요.
def only_odds(nums):
for n in nums:
if n % 2 == 1:
yield n
함수 중간에 return 대신 yield를 넣으면
그 지점에서 함수 실행이 중단돼요.
>>> odds = only_odds(range(100))
>>> next(odds)
1
>>> next(odds)
3
호출한 쪽에선
yield된 값을 받아올 수 있고
>>> odds = only_odds(range(100))
>>> next(odds)
1
>>> next(odds)
3
next()를 이용해서
다음 이터레이션으로 넘어갈 수 있어요.
def only_odds(nums):
for n in nums:
if n % 2 == 1:
yield n
next()
next()
next()
next()
next()
이런 식으로 next()하는 만큼
함수가 실행되게 ...
def only_odds(nums):
for n in nums:
if n % 2 == 1:
yield n
next()
next()
next()
next()
next()
레이지 이터레이터를
아주 쉽게 구현할 수 있죠.
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq)
특히 한 시퀀스를
여러 번 변형하는 경우
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq)
제너레이터를 쓰면 실제 이터레이션을
한 바퀴로 줄일 수 있는데
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq)
이 코드는
0부터 999,999까지의 숫자 중에서
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq)
홀수만 취한 다음
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq)
그 제곱을 구하는 코드입니다.
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq)
세 줄 모두 이터레이터나
제너레이터만 만들고 있고
>>> seq
<generator object <genexpr>>
실제로 이터레이션을 돌리진 않아요.
>>> list(seq)
[1, 9, 25, 49, 81, 121, ...]
결과를 보기 위해 list 등으로 감싸면
그때야 비로소 이터레이션이 돌죠.
>>> list(seq)
[1, 9, 25, 49, 81, 121, ...]
레이지하지 않았다면 이터레이션이
250만 바퀴 돌았을 로직인데
>>> list(seq)
[1, 9, 25, 49, 81, 121, ...]
이 방법으로
딱 100만 바퀴만 돌 수 있게 됩니다.
❯ python -m timeit 
-s 'from exm import only_odds' '
seq = xrange(1000000)
seq = only_odds(seq)
seq = (x ** 2 for x in seq...
❯ python -m timeit 
-s 'from exm import only_odds' '
seq = list(xrange(1000000))
seq = list(only_odds(seq))
seq = [x ** 2 ...
>>> list(gen)
[1, 2, 3]
>>> list(gen)
[]
def gen():
yield 1
yield 2
yield 3
제너레이터는 딱 한 바퀴만
돌릴 수 있어서
>>> list(gen)
[1, 2, 3]
>>> list(gen)
[]
def gen():
yield 1
yield 2
yield 3
여러 번 돌려야 하는 경우에
당황스러울 수 있어요.
seq = list(gen)
do_something(seq)
do_another(seq)
이럴 땐 list 등으로
미리 한번 평가해놓고 쓰거나
gen1, gen2 = itertools.tee(gen)
do_something(gen1)
do_another(gen2)
아니면 표준 라이브러리
itertools에 있는 tee를 이용해서
gen1, gen2 = itertools.tee(gen)
do_something(gen1)
do_another(gen2)
제너레이터를 여러 개의 뷰로
나눠서 쓸 수 있어요.
gen1, gen2 = itertools.tee(gen)
do_something(gen1)
do_another(gen2)
tee는 리눅스 커맨드에서 따온 이름인데
T자형 파이프를 뜻합니다.
enemy = player.nearest_player()
while player.alive() or enemy.alive():
player.move_to(enemy)
player.attack(enemy)
yield
제너...
enemy = player.nearest_player()
while player.alive() or enemy.alive():
player.move_to(enemy)
player.attack(enemy)
yield
지금...
enemy = player.nearest_player()
while player.alive() or enemy.alive():
player.move_to(enemy)
player.attack(enemy)
yield
고민...
if self.state is not Fighting:
self.enemy = player.nearest_player()
self.state = Fighting
elif player.alive() or self.enem...
yield
파이썬에서 yield는
raise나 return만큼
yield
많이 쓰이는 제어구문이고
그만큼 쓰기도 쉽습니다.
yield
제너레이터로 이터레이션을 줄일 수 있거나
복잡도를 낮출 수 있는 곳이 없는지
yield
한 번 확인해보시면 좋을 것 같아요.
성능 측정
성능 측정
프로그램을 최적화하려면
일단 성능부터 제대로 측정해야 합니다.
성능 측정
이번엔 저희가 사용하는 몇 가지
성능 측정 방법을 소개해 볼게요.
❯ python -m timeit 
-s 한 번 실행할 코드 벤치마킹할 코드
timeit은 표준 라이브러리예요.
❯ python -m timeit 
-s 한 번 실행할 코드 벤치마킹할 코드
앞에서 제너레이터 벤치마킹할 때
이것을 썼었죠.
❯ python -m timeit 
-s 한 번 실행할 코드 벤치마킹할 코드
그냥 쉘에다가 이렇게 치면
❯ python -m timeit 
-s 한 번 실행할 코드 벤치마킹할 코드
적당히 너무 오래 걸리지 않는 선에서
반복실행한 다음
❯ python -m timeit 
-s 한 번 실행할 코드 벤치마킹할 코드
한 번 돌릴 때 실행시간이
얼마나 걸리는지 알려줍니다.
❯ python -m timeit 
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
이런 식으로요.
❯ python -m timeit 
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
이건 math.log10()...
❯ python -m timeit 
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
여기서 math 모듈을 im...
❯ python -m timeit 
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
그래서 -s 옵션에 넣어서
...
❯ python -m timeit 
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
1,000만 번 씩 3번 돌...
❯ python -m timeit 
-s 'import math' 'math.log10(99999999)'
10000000 loops, best of 3: 0.108 usec per loop
그 중 가장 빨랐을 때 lo...
•호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m profile 파이썬 파일
profile 역시 표준 라이브러리인데
•호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m profile 파이썬 파일
함수에 진입하고 나가는 이벤트를
모두 기록해서
•호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m profile 파이썬 파일
함수 별로 호출 횟수와
실행시간을 측정해주죠.
•호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m cProfile 파이썬 파일
표준 라이브러리에 cProfile이라고
C로 만들어서 오버헤드가 적은 버전도 있으니까
•호출 횟수
•고유 실행시간
•하위 콜스택 포함 실행시간
❯ python -m cProfile 파이썬 파일
저희처럼 PyPy가 아닌
CPython을 쓰신다면 이걸 쓰세요.
❯ python -m cProfile log10.py
10000002 function calls in 2.269 seconds
Ordered by: standard name
ncalls tottime percall cu...
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
Nächste SlideShare
Wird geladen in …5
×

von

파이썬 생존 안내서 (자막) Slide 1 파이썬 생존 안내서 (자막) Slide 2 파이썬 생존 안내서 (자막) Slide 3 파이썬 생존 안내서 (자막) Slide 4 파이썬 생존 안내서 (자막) Slide 5 파이썬 생존 안내서 (자막) Slide 6 파이썬 생존 안내서 (자막) Slide 7 파이썬 생존 안내서 (자막) Slide 8 파이썬 생존 안내서 (자막) Slide 9 파이썬 생존 안내서 (자막) Slide 10 파이썬 생존 안내서 (자막) Slide 11 파이썬 생존 안내서 (자막) Slide 12 파이썬 생존 안내서 (자막) Slide 13 파이썬 생존 안내서 (자막) Slide 14 파이썬 생존 안내서 (자막) Slide 15 파이썬 생존 안내서 (자막) Slide 16 파이썬 생존 안내서 (자막) Slide 17 파이썬 생존 안내서 (자막) Slide 18 파이썬 생존 안내서 (자막) Slide 19 파이썬 생존 안내서 (자막) Slide 20 파이썬 생존 안내서 (자막) Slide 21 파이썬 생존 안내서 (자막) Slide 22 파이썬 생존 안내서 (자막) Slide 23 파이썬 생존 안내서 (자막) Slide 24 파이썬 생존 안내서 (자막) Slide 25 파이썬 생존 안내서 (자막) Slide 26 파이썬 생존 안내서 (자막) Slide 27 파이썬 생존 안내서 (자막) Slide 28 파이썬 생존 안내서 (자막) Slide 29 파이썬 생존 안내서 (자막) Slide 30 파이썬 생존 안내서 (자막) Slide 31 파이썬 생존 안내서 (자막) Slide 32 파이썬 생존 안내서 (자막) Slide 33 파이썬 생존 안내서 (자막) Slide 34 파이썬 생존 안내서 (자막) Slide 35 파이썬 생존 안내서 (자막) Slide 36 파이썬 생존 안내서 (자막) Slide 37 파이썬 생존 안내서 (자막) Slide 38 파이썬 생존 안내서 (자막) Slide 39 파이썬 생존 안내서 (자막) Slide 40 파이썬 생존 안내서 (자막) Slide 41 파이썬 생존 안내서 (자막) Slide 42 파이썬 생존 안내서 (자막) Slide 43 파이썬 생존 안내서 (자막) Slide 44 파이썬 생존 안내서 (자막) Slide 45 파이썬 생존 안내서 (자막) Slide 46 파이썬 생존 안내서 (자막) Slide 47 파이썬 생존 안내서 (자막) Slide 48 파이썬 생존 안내서 (자막) Slide 49 파이썬 생존 안내서 (자막) Slide 50 파이썬 생존 안내서 (자막) Slide 51 파이썬 생존 안내서 (자막) Slide 52 파이썬 생존 안내서 (자막) Slide 53 파이썬 생존 안내서 (자막) Slide 54 파이썬 생존 안내서 (자막) Slide 55 파이썬 생존 안내서 (자막) Slide 56 파이썬 생존 안내서 (자막) Slide 57 파이썬 생존 안내서 (자막) Slide 58 파이썬 생존 안내서 (자막) Slide 59 파이썬 생존 안내서 (자막) Slide 60 파이썬 생존 안내서 (자막) Slide 61 파이썬 생존 안내서 (자막) Slide 62 파이썬 생존 안내서 (자막) Slide 63 파이썬 생존 안내서 (자막) Slide 64 파이썬 생존 안내서 (자막) Slide 65 파이썬 생존 안내서 (자막) Slide 66 파이썬 생존 안내서 (자막) Slide 67 파이썬 생존 안내서 (자막) Slide 68 파이썬 생존 안내서 (자막) Slide 69 파이썬 생존 안내서 (자막) Slide 70 파이썬 생존 안내서 (자막) Slide 71 파이썬 생존 안내서 (자막) Slide 72 파이썬 생존 안내서 (자막) Slide 73 파이썬 생존 안내서 (자막) Slide 74 파이썬 생존 안내서 (자막) Slide 75 파이썬 생존 안내서 (자막) Slide 76 파이썬 생존 안내서 (자막) Slide 77 파이썬 생존 안내서 (자막) Slide 78 파이썬 생존 안내서 (자막) Slide 79 파이썬 생존 안내서 (자막) Slide 80 파이썬 생존 안내서 (자막) Slide 81 파이썬 생존 안내서 (자막) Slide 82 파이썬 생존 안내서 (자막) Slide 83 파이썬 생존 안내서 (자막) Slide 84 파이썬 생존 안내서 (자막) Slide 85 파이썬 생존 안내서 (자막) Slide 86 파이썬 생존 안내서 (자막) Slide 87 파이썬 생존 안내서 (자막) Slide 88 파이썬 생존 안내서 (자막) Slide 89 파이썬 생존 안내서 (자막) Slide 90 파이썬 생존 안내서 (자막) Slide 91 파이썬 생존 안내서 (자막) Slide 92 파이썬 생존 안내서 (자막) Slide 93 파이썬 생존 안내서 (자막) Slide 94 파이썬 생존 안내서 (자막) Slide 95 파이썬 생존 안내서 (자막) Slide 96 파이썬 생존 안내서 (자막) Slide 97 파이썬 생존 안내서 (자막) Slide 98 파이썬 생존 안내서 (자막) Slide 99 파이썬 생존 안내서 (자막) Slide 100 파이썬 생존 안내서 (자막) Slide 101 파이썬 생존 안내서 (자막) Slide 102 파이썬 생존 안내서 (자막) Slide 103 파이썬 생존 안내서 (자막) Slide 104 파이썬 생존 안내서 (자막) Slide 105 파이썬 생존 안내서 (자막) Slide 106 파이썬 생존 안내서 (자막) Slide 107 파이썬 생존 안내서 (자막) Slide 108 파이썬 생존 안내서 (자막) Slide 109 파이썬 생존 안내서 (자막) Slide 110 파이썬 생존 안내서 (자막) Slide 111 파이썬 생존 안내서 (자막) Slide 112 파이썬 생존 안내서 (자막) Slide 113 파이썬 생존 안내서 (자막) Slide 114 파이썬 생존 안내서 (자막) Slide 115 파이썬 생존 안내서 (자막) Slide 116 파이썬 생존 안내서 (자막) Slide 117 파이썬 생존 안내서 (자막) Slide 118 파이썬 생존 안내서 (자막) Slide 119 파이썬 생존 안내서 (자막) Slide 120 파이썬 생존 안내서 (자막) Slide 121 파이썬 생존 안내서 (자막) Slide 122 파이썬 생존 안내서 (자막) Slide 123 파이썬 생존 안내서 (자막) Slide 124 파이썬 생존 안내서 (자막) Slide 125 파이썬 생존 안내서 (자막) Slide 126 파이썬 생존 안내서 (자막) Slide 127 파이썬 생존 안내서 (자막) Slide 128 파이썬 생존 안내서 (자막) Slide 129 파이썬 생존 안내서 (자막) Slide 130 파이썬 생존 안내서 (자막) Slide 131 파이썬 생존 안내서 (자막) Slide 132 파이썬 생존 안내서 (자막) Slide 133 파이썬 생존 안내서 (자막) Slide 134 파이썬 생존 안내서 (자막) Slide 135 파이썬 생존 안내서 (자막) Slide 136 파이썬 생존 안내서 (자막) Slide 137 파이썬 생존 안내서 (자막) Slide 138 파이썬 생존 안내서 (자막) Slide 139 파이썬 생존 안내서 (자막) Slide 140 파이썬 생존 안내서 (자막) Slide 141 파이썬 생존 안내서 (자막) Slide 142 파이썬 생존 안내서 (자막) Slide 143 파이썬 생존 안내서 (자막) Slide 144 파이썬 생존 안내서 (자막) Slide 145 파이썬 생존 안내서 (자막) Slide 146 파이썬 생존 안내서 (자막) Slide 147 파이썬 생존 안내서 (자막) Slide 148 파이썬 생존 안내서 (자막) Slide 149 파이썬 생존 안내서 (자막) Slide 150 파이썬 생존 안내서 (자막) Slide 151 파이썬 생존 안내서 (자막) Slide 152 파이썬 생존 안내서 (자막) Slide 153 파이썬 생존 안내서 (자막) Slide 154 파이썬 생존 안내서 (자막) Slide 155 파이썬 생존 안내서 (자막) Slide 156 파이썬 생존 안내서 (자막) Slide 157 파이썬 생존 안내서 (자막) Slide 158 파이썬 생존 안내서 (자막) Slide 159 파이썬 생존 안내서 (자막) Slide 160 파이썬 생존 안내서 (자막) Slide 161 파이썬 생존 안내서 (자막) Slide 162 파이썬 생존 안내서 (자막) Slide 163 파이썬 생존 안내서 (자막) Slide 164 파이썬 생존 안내서 (자막) Slide 165 파이썬 생존 안내서 (자막) Slide 166 파이썬 생존 안내서 (자막) Slide 167 파이썬 생존 안내서 (자막) Slide 168 파이썬 생존 안내서 (자막) Slide 169 파이썬 생존 안내서 (자막) Slide 170 파이썬 생존 안내서 (자막) Slide 171 파이썬 생존 안내서 (자막) Slide 172 파이썬 생존 안내서 (자막) Slide 173 파이썬 생존 안내서 (자막) Slide 174 파이썬 생존 안내서 (자막) Slide 175 파이썬 생존 안내서 (자막) Slide 176 파이썬 생존 안내서 (자막) Slide 177 파이썬 생존 안내서 (자막) Slide 178 파이썬 생존 안내서 (자막) Slide 179 파이썬 생존 안내서 (자막) Slide 180 파이썬 생존 안내서 (자막) Slide 181 파이썬 생존 안내서 (자막) Slide 182 파이썬 생존 안내서 (자막) Slide 183 파이썬 생존 안내서 (자막) Slide 184 파이썬 생존 안내서 (자막) Slide 185 파이썬 생존 안내서 (자막) Slide 186 파이썬 생존 안내서 (자막) Slide 187 파이썬 생존 안내서 (자막) Slide 188 파이썬 생존 안내서 (자막) Slide 189 파이썬 생존 안내서 (자막) Slide 190 파이썬 생존 안내서 (자막) Slide 191 파이썬 생존 안내서 (자막) Slide 192 파이썬 생존 안내서 (자막) Slide 193 파이썬 생존 안내서 (자막) Slide 194 파이썬 생존 안내서 (자막) Slide 195 파이썬 생존 안내서 (자막) Slide 196 파이썬 생존 안내서 (자막) Slide 197 파이썬 생존 안내서 (자막) Slide 198 파이썬 생존 안내서 (자막) Slide 199 파이썬 생존 안내서 (자막) Slide 200 파이썬 생존 안내서 (자막) Slide 201 파이썬 생존 안내서 (자막) Slide 202 파이썬 생존 안내서 (자막) Slide 203 파이썬 생존 안내서 (자막) Slide 204 파이썬 생존 안내서 (자막) Slide 205 파이썬 생존 안내서 (자막) Slide 206 파이썬 생존 안내서 (자막) Slide 207 파이썬 생존 안내서 (자막) Slide 208 파이썬 생존 안내서 (자막) Slide 209 파이썬 생존 안내서 (자막) Slide 210 파이썬 생존 안내서 (자막) Slide 211 파이썬 생존 안내서 (자막) Slide 212 파이썬 생존 안내서 (자막) Slide 213 파이썬 생존 안내서 (자막) Slide 214 파이썬 생존 안내서 (자막) Slide 215 파이썬 생존 안내서 (자막) Slide 216 파이썬 생존 안내서 (자막) Slide 217 파이썬 생존 안내서 (자막) Slide 218 파이썬 생존 안내서 (자막) Slide 219 파이썬 생존 안내서 (자막) Slide 220 파이썬 생존 안내서 (자막) Slide 221 파이썬 생존 안내서 (자막) Slide 222 파이썬 생존 안내서 (자막) Slide 223 파이썬 생존 안내서 (자막) Slide 224 파이썬 생존 안내서 (자막) Slide 225 파이썬 생존 안내서 (자막) Slide 226 파이썬 생존 안내서 (자막) Slide 227 파이썬 생존 안내서 (자막) Slide 228 파이썬 생존 안내서 (자막) Slide 229 파이썬 생존 안내서 (자막) Slide 230 파이썬 생존 안내서 (자막) Slide 231 파이썬 생존 안내서 (자막) Slide 232 파이썬 생존 안내서 (자막) Slide 233 파이썬 생존 안내서 (자막) Slide 234 파이썬 생존 안내서 (자막) Slide 235 파이썬 생존 안내서 (자막) Slide 236 파이썬 생존 안내서 (자막) Slide 237 파이썬 생존 안내서 (자막) Slide 238 파이썬 생존 안내서 (자막) Slide 239 파이썬 생존 안내서 (자막) Slide 240 파이썬 생존 안내서 (자막) Slide 241 파이썬 생존 안내서 (자막) Slide 242 파이썬 생존 안내서 (자막) Slide 243 파이썬 생존 안내서 (자막) Slide 244 파이썬 생존 안내서 (자막) Slide 245 파이썬 생존 안내서 (자막) Slide 246 파이썬 생존 안내서 (자막) Slide 247 파이썬 생존 안내서 (자막) Slide 248 파이썬 생존 안내서 (자막) Slide 249 파이썬 생존 안내서 (자막) Slide 250 파이썬 생존 안내서 (자막) Slide 251 파이썬 생존 안내서 (자막) Slide 252 파이썬 생존 안내서 (자막) Slide 253 파이썬 생존 안내서 (자막) Slide 254 파이썬 생존 안내서 (자막) Slide 255 파이썬 생존 안내서 (자막) Slide 256 파이썬 생존 안내서 (자막) Slide 257 파이썬 생존 안내서 (자막) Slide 258 파이썬 생존 안내서 (자막) Slide 259 파이썬 생존 안내서 (자막) Slide 260 파이썬 생존 안내서 (자막) Slide 261 파이썬 생존 안내서 (자막) Slide 262 파이썬 생존 안내서 (자막) Slide 263 파이썬 생존 안내서 (자막) Slide 264 파이썬 생존 안내서 (자막) Slide 265 파이썬 생존 안내서 (자막) Slide 266 파이썬 생존 안내서 (자막) Slide 267 파이썬 생존 안내서 (자막) Slide 268 파이썬 생존 안내서 (자막) Slide 269 파이썬 생존 안내서 (자막) Slide 270 파이썬 생존 안내서 (자막) Slide 271 파이썬 생존 안내서 (자막) Slide 272 파이썬 생존 안내서 (자막) Slide 273 파이썬 생존 안내서 (자막) Slide 274 파이썬 생존 안내서 (자막) Slide 275 파이썬 생존 안내서 (자막) Slide 276 파이썬 생존 안내서 (자막) Slide 277 파이썬 생존 안내서 (자막) Slide 278 파이썬 생존 안내서 (자막) Slide 279 파이썬 생존 안내서 (자막) Slide 280 파이썬 생존 안내서 (자막) Slide 281 파이썬 생존 안내서 (자막) Slide 282 파이썬 생존 안내서 (자막) Slide 283 파이썬 생존 안내서 (자막) Slide 284 파이썬 생존 안내서 (자막) Slide 285 파이썬 생존 안내서 (자막) Slide 286 파이썬 생존 안내서 (자막) Slide 287 파이썬 생존 안내서 (자막) Slide 288 파이썬 생존 안내서 (자막) Slide 289 파이썬 생존 안내서 (자막) Slide 290 파이썬 생존 안내서 (자막) Slide 291 파이썬 생존 안내서 (자막) Slide 292 파이썬 생존 안내서 (자막) Slide 293 파이썬 생존 안내서 (자막) Slide 294 파이썬 생존 안내서 (자막) Slide 295 파이썬 생존 안내서 (자막) Slide 296 파이썬 생존 안내서 (자막) Slide 297 파이썬 생존 안내서 (자막) Slide 298 파이썬 생존 안내서 (자막) Slide 299 파이썬 생존 안내서 (자막) Slide 300 파이썬 생존 안내서 (자막) Slide 301 파이썬 생존 안내서 (자막) Slide 302 파이썬 생존 안내서 (자막) Slide 303 파이썬 생존 안내서 (자막) Slide 304 파이썬 생존 안내서 (자막) Slide 305 파이썬 생존 안내서 (자막) Slide 306 파이썬 생존 안내서 (자막) Slide 307 파이썬 생존 안내서 (자막) Slide 308 파이썬 생존 안내서 (자막) Slide 309 파이썬 생존 안내서 (자막) Slide 310 파이썬 생존 안내서 (자막) Slide 311 파이썬 생존 안내서 (자막) Slide 312 파이썬 생존 안내서 (자막) Slide 313 파이썬 생존 안내서 (자막) Slide 314 파이썬 생존 안내서 (자막) Slide 315 파이썬 생존 안내서 (자막) Slide 316 파이썬 생존 안내서 (자막) Slide 317 파이썬 생존 안내서 (자막) Slide 318 파이썬 생존 안내서 (자막) Slide 319 파이썬 생존 안내서 (자막) Slide 320 파이썬 생존 안내서 (자막) Slide 321 파이썬 생존 안내서 (자막) Slide 322 파이썬 생존 안내서 (자막) Slide 323 파이썬 생존 안내서 (자막) Slide 324 파이썬 생존 안내서 (자막) Slide 325 파이썬 생존 안내서 (자막) Slide 326 파이썬 생존 안내서 (자막) Slide 327 파이썬 생존 안내서 (자막) Slide 328 파이썬 생존 안내서 (자막) Slide 329 파이썬 생존 안내서 (자막) Slide 330 파이썬 생존 안내서 (자막) Slide 331 파이썬 생존 안내서 (자막) Slide 332 파이썬 생존 안내서 (자막) Slide 333 파이썬 생존 안내서 (자막) Slide 334 파이썬 생존 안내서 (자막) Slide 335 파이썬 생존 안내서 (자막) Slide 336 파이썬 생존 안내서 (자막) Slide 337 파이썬 생존 안내서 (자막) Slide 338 파이썬 생존 안내서 (자막) Slide 339 파이썬 생존 안내서 (자막) Slide 340 파이썬 생존 안내서 (자막) Slide 341 파이썬 생존 안내서 (자막) Slide 342 파이썬 생존 안내서 (자막) Slide 343 파이썬 생존 안내서 (자막) Slide 344 파이썬 생존 안내서 (자막) Slide 345 파이썬 생존 안내서 (자막) Slide 346 파이썬 생존 안내서 (자막) Slide 347 파이썬 생존 안내서 (자막) Slide 348 파이썬 생존 안내서 (자막) Slide 349 파이썬 생존 안내서 (자막) Slide 350 파이썬 생존 안내서 (자막) Slide 351 파이썬 생존 안내서 (자막) Slide 352 파이썬 생존 안내서 (자막) Slide 353 파이썬 생존 안내서 (자막) Slide 354 파이썬 생존 안내서 (자막) Slide 355 파이썬 생존 안내서 (자막) Slide 356 파이썬 생존 안내서 (자막) Slide 357 파이썬 생존 안내서 (자막) Slide 358 파이썬 생존 안내서 (자막) Slide 359 파이썬 생존 안내서 (자막) Slide 360 파이썬 생존 안내서 (자막) Slide 361 파이썬 생존 안내서 (자막) Slide 362 파이썬 생존 안내서 (자막) Slide 363 파이썬 생존 안내서 (자막) Slide 364 파이썬 생존 안내서 (자막) Slide 365 파이썬 생존 안내서 (자막) Slide 366 파이썬 생존 안내서 (자막) Slide 367 파이썬 생존 안내서 (자막) Slide 368 파이썬 생존 안내서 (자막) Slide 369 파이썬 생존 안내서 (자막) Slide 370 파이썬 생존 안내서 (자막) Slide 371 파이썬 생존 안내서 (자막) Slide 372 파이썬 생존 안내서 (자막) Slide 373 파이썬 생존 안내서 (자막) Slide 374 파이썬 생존 안내서 (자막) Slide 375 파이썬 생존 안내서 (자막) Slide 376 파이썬 생존 안내서 (자막) Slide 377 파이썬 생존 안내서 (자막) Slide 378 파이썬 생존 안내서 (자막) Slide 379 파이썬 생존 안내서 (자막) Slide 380 파이썬 생존 안내서 (자막) Slide 381 파이썬 생존 안내서 (자막) Slide 382 파이썬 생존 안내서 (자막) Slide 383 파이썬 생존 안내서 (자막) Slide 384 파이썬 생존 안내서 (자막) Slide 385 파이썬 생존 안내서 (자막) Slide 386 파이썬 생존 안내서 (자막) Slide 387 파이썬 생존 안내서 (자막) Slide 388 파이썬 생존 안내서 (자막) Slide 389 파이썬 생존 안내서 (자막) Slide 390 파이썬 생존 안내서 (자막) Slide 391 파이썬 생존 안내서 (자막) Slide 392 파이썬 생존 안내서 (자막) Slide 393 파이썬 생존 안내서 (자막) Slide 394 파이썬 생존 안내서 (자막) Slide 395 파이썬 생존 안내서 (자막) Slide 396 파이썬 생존 안내서 (자막) Slide 397 파이썬 생존 안내서 (자막) Slide 398 파이썬 생존 안내서 (자막) Slide 399 파이썬 생존 안내서 (자막) Slide 400 파이썬 생존 안내서 (자막) Slide 401 파이썬 생존 안내서 (자막) Slide 402 파이썬 생존 안내서 (자막) Slide 403 파이썬 생존 안내서 (자막) Slide 404 파이썬 생존 안내서 (자막) Slide 405 파이썬 생존 안내서 (자막) Slide 406 파이썬 생존 안내서 (자막) Slide 407 파이썬 생존 안내서 (자막) Slide 408 파이썬 생존 안내서 (자막) Slide 409 파이썬 생존 안내서 (자막) Slide 410 파이썬 생존 안내서 (자막) Slide 411 파이썬 생존 안내서 (자막) Slide 412 파이썬 생존 안내서 (자막) Slide 413 파이썬 생존 안내서 (자막) Slide 414 파이썬 생존 안내서 (자막) Slide 415 파이썬 생존 안내서 (자막) Slide 416 파이썬 생존 안내서 (자막) Slide 417 파이썬 생존 안내서 (자막) Slide 418 파이썬 생존 안내서 (자막) Slide 419 파이썬 생존 안내서 (자막) Slide 420 파이썬 생존 안내서 (자막) Slide 421 파이썬 생존 안내서 (자막) Slide 422 파이썬 생존 안내서 (자막) Slide 423 파이썬 생존 안내서 (자막) Slide 424 파이썬 생존 안내서 (자막) Slide 425 파이썬 생존 안내서 (자막) Slide 426 파이썬 생존 안내서 (자막) Slide 427 파이썬 생존 안내서 (자막) Slide 428 파이썬 생존 안내서 (자막) Slide 429 파이썬 생존 안내서 (자막) Slide 430 파이썬 생존 안내서 (자막) Slide 431 파이썬 생존 안내서 (자막) Slide 432 파이썬 생존 안내서 (자막) Slide 433 파이썬 생존 안내서 (자막) Slide 434 파이썬 생존 안내서 (자막) Slide 435 파이썬 생존 안내서 (자막) Slide 436 파이썬 생존 안내서 (자막) Slide 437 파이썬 생존 안내서 (자막) Slide 438 파이썬 생존 안내서 (자막) Slide 439 파이썬 생존 안내서 (자막) Slide 440 파이썬 생존 안내서 (자막) Slide 441 파이썬 생존 안내서 (자막) Slide 442 파이썬 생존 안내서 (자막) Slide 443 파이썬 생존 안내서 (자막) Slide 444 파이썬 생존 안내서 (자막) Slide 445 파이썬 생존 안내서 (자막) Slide 446 파이썬 생존 안내서 (자막) Slide 447 파이썬 생존 안내서 (자막) Slide 448 파이썬 생존 안내서 (자막) Slide 449 파이썬 생존 안내서 (자막) Slide 450 파이썬 생존 안내서 (자막) Slide 451 파이썬 생존 안내서 (자막) Slide 452 파이썬 생존 안내서 (자막) Slide 453 파이썬 생존 안내서 (자막) Slide 454 파이썬 생존 안내서 (자막) Slide 455 파이썬 생존 안내서 (자막) Slide 456 파이썬 생존 안내서 (자막) Slide 457 파이썬 생존 안내서 (자막) Slide 458 파이썬 생존 안내서 (자막) Slide 459 파이썬 생존 안내서 (자막) Slide 460 파이썬 생존 안내서 (자막) Slide 461 파이썬 생존 안내서 (자막) Slide 462 파이썬 생존 안내서 (자막) Slide 463 파이썬 생존 안내서 (자막) Slide 464 파이썬 생존 안내서 (자막) Slide 465 파이썬 생존 안내서 (자막) Slide 466 파이썬 생존 안내서 (자막) Slide 467 파이썬 생존 안내서 (자막) Slide 468 파이썬 생존 안내서 (자막) Slide 469 파이썬 생존 안내서 (자막) Slide 470 파이썬 생존 안내서 (자막) Slide 471 파이썬 생존 안내서 (자막) Slide 472 파이썬 생존 안내서 (자막) Slide 473 파이썬 생존 안내서 (자막) Slide 474 파이썬 생존 안내서 (자막) Slide 475 파이썬 생존 안내서 (자막) Slide 476 파이썬 생존 안내서 (자막) Slide 477 파이썬 생존 안내서 (자막) Slide 478 파이썬 생존 안내서 (자막) Slide 479 파이썬 생존 안내서 (자막) Slide 480 파이썬 생존 안내서 (자막) Slide 481 파이썬 생존 안내서 (자막) Slide 482 파이썬 생존 안내서 (자막) Slide 483 파이썬 생존 안내서 (자막) Slide 484 파이썬 생존 안내서 (자막) Slide 485 파이썬 생존 안내서 (자막) Slide 486 파이썬 생존 안내서 (자막) Slide 487 파이썬 생존 안내서 (자막) Slide 488 파이썬 생존 안내서 (자막) Slide 489 파이썬 생존 안내서 (자막) Slide 490 파이썬 생존 안내서 (자막) Slide 491 파이썬 생존 안내서 (자막) Slide 492 파이썬 생존 안내서 (자막) Slide 493 파이썬 생존 안내서 (자막) Slide 494 파이썬 생존 안내서 (자막) Slide 495 파이썬 생존 안내서 (자막) Slide 496 파이썬 생존 안내서 (자막) Slide 497 파이썬 생존 안내서 (자막) Slide 498 파이썬 생존 안내서 (자막) Slide 499 파이썬 생존 안내서 (자막) Slide 500 파이썬 생존 안내서 (자막) Slide 501 파이썬 생존 안내서 (자막) Slide 502 파이썬 생존 안내서 (자막) Slide 503 파이썬 생존 안내서 (자막) Slide 504 파이썬 생존 안내서 (자막) Slide 505 파이썬 생존 안내서 (자막) Slide 506 파이썬 생존 안내서 (자막) Slide 507 파이썬 생존 안내서 (자막) Slide 508 파이썬 생존 안내서 (자막) Slide 509 파이썬 생존 안내서 (자막) Slide 510 파이썬 생존 안내서 (자막) Slide 511 파이썬 생존 안내서 (자막) Slide 512 파이썬 생존 안내서 (자막) Slide 513 파이썬 생존 안내서 (자막) Slide 514 파이썬 생존 안내서 (자막) Slide 515 파이썬 생존 안내서 (자막) Slide 516 파이썬 생존 안내서 (자막) Slide 517 파이썬 생존 안내서 (자막) Slide 518 파이썬 생존 안내서 (자막) Slide 519 파이썬 생존 안내서 (자막) Slide 520 파이썬 생존 안내서 (자막) Slide 521 파이썬 생존 안내서 (자막) Slide 522 파이썬 생존 안내서 (자막) Slide 523 파이썬 생존 안내서 (자막) Slide 524 파이썬 생존 안내서 (자막) Slide 525 파이썬 생존 안내서 (자막) Slide 526 파이썬 생존 안내서 (자막) Slide 527 파이썬 생존 안내서 (자막) Slide 528 파이썬 생존 안내서 (자막) Slide 529 파이썬 생존 안내서 (자막) Slide 530 파이썬 생존 안내서 (자막) Slide 531 파이썬 생존 안내서 (자막) Slide 532 파이썬 생존 안내서 (자막) Slide 533 파이썬 생존 안내서 (자막) Slide 534 파이썬 생존 안내서 (자막) Slide 535 파이썬 생존 안내서 (자막) Slide 536 파이썬 생존 안내서 (자막) Slide 537 파이썬 생존 안내서 (자막) Slide 538 파이썬 생존 안내서 (자막) Slide 539 파이썬 생존 안내서 (자막) Slide 540 파이썬 생존 안내서 (자막) Slide 541 파이썬 생존 안내서 (자막) Slide 542 파이썬 생존 안내서 (자막) Slide 543 파이썬 생존 안내서 (자막) Slide 544 파이썬 생존 안내서 (자막) Slide 545 파이썬 생존 안내서 (자막) Slide 546 파이썬 생존 안내서 (자막) Slide 547 파이썬 생존 안내서 (자막) Slide 548 파이썬 생존 안내서 (자막) Slide 549 파이썬 생존 안내서 (자막) Slide 550 파이썬 생존 안내서 (자막) Slide 551 파이썬 생존 안내서 (자막) Slide 552 파이썬 생존 안내서 (자막) Slide 553 파이썬 생존 안내서 (자막) Slide 554 파이썬 생존 안내서 (자막) Slide 555 파이썬 생존 안내서 (자막) Slide 556 파이썬 생존 안내서 (자막) Slide 557 파이썬 생존 안내서 (자막) Slide 558 파이썬 생존 안내서 (자막) Slide 559 파이썬 생존 안내서 (자막) Slide 560 파이썬 생존 안내서 (자막) Slide 561 파이썬 생존 안내서 (자막) Slide 562 파이썬 생존 안내서 (자막) Slide 563 파이썬 생존 안내서 (자막) Slide 564 파이썬 생존 안내서 (자막) Slide 565 파이썬 생존 안내서 (자막) Slide 566 파이썬 생존 안내서 (자막) Slide 567 파이썬 생존 안내서 (자막) Slide 568 파이썬 생존 안내서 (자막) Slide 569 파이썬 생존 안내서 (자막) Slide 570 파이썬 생존 안내서 (자막) Slide 571 파이썬 생존 안내서 (자막) Slide 572 파이썬 생존 안내서 (자막) Slide 573 파이썬 생존 안내서 (자막) Slide 574 파이썬 생존 안내서 (자막) Slide 575 파이썬 생존 안내서 (자막) Slide 576 파이썬 생존 안내서 (자막) Slide 577 파이썬 생존 안내서 (자막) Slide 578 파이썬 생존 안내서 (자막) Slide 579 파이썬 생존 안내서 (자막) Slide 580 파이썬 생존 안내서 (자막) Slide 581 파이썬 생존 안내서 (자막) Slide 582 파이썬 생존 안내서 (자막) Slide 583 파이썬 생존 안내서 (자막) Slide 584 파이썬 생존 안내서 (자막) Slide 585 파이썬 생존 안내서 (자막) Slide 586 파이썬 생존 안내서 (자막) Slide 587 파이썬 생존 안내서 (자막) Slide 588 파이썬 생존 안내서 (자막) Slide 589 파이썬 생존 안내서 (자막) Slide 590 파이썬 생존 안내서 (자막) Slide 591 파이썬 생존 안내서 (자막) Slide 592 파이썬 생존 안내서 (자막) Slide 593 파이썬 생존 안내서 (자막) Slide 594 파이썬 생존 안내서 (자막) Slide 595 파이썬 생존 안내서 (자막) Slide 596 파이썬 생존 안내서 (자막) Slide 597 파이썬 생존 안내서 (자막) Slide 598 파이썬 생존 안내서 (자막) Slide 599 파이썬 생존 안내서 (자막) Slide 600 파이썬 생존 안내서 (자막) Slide 601 파이썬 생존 안내서 (자막) Slide 602 파이썬 생존 안내서 (자막) Slide 603 파이썬 생존 안내서 (자막) Slide 604 파이썬 생존 안내서 (자막) Slide 605 파이썬 생존 안내서 (자막) Slide 606 파이썬 생존 안내서 (자막) Slide 607 파이썬 생존 안내서 (자막) Slide 608 파이썬 생존 안내서 (자막) Slide 609 파이썬 생존 안내서 (자막) Slide 610 파이썬 생존 안내서 (자막) Slide 611 파이썬 생존 안내서 (자막) Slide 612 파이썬 생존 안내서 (자막) Slide 613 파이썬 생존 안내서 (자막) Slide 614 파이썬 생존 안내서 (자막) Slide 615 파이썬 생존 안내서 (자막) Slide 616 파이썬 생존 안내서 (자막) Slide 617 파이썬 생존 안내서 (자막) Slide 618 파이썬 생존 안내서 (자막) Slide 619 파이썬 생존 안내서 (자막) Slide 620 파이썬 생존 안내서 (자막) Slide 621 파이썬 생존 안내서 (자막) Slide 622 파이썬 생존 안내서 (자막) Slide 623 파이썬 생존 안내서 (자막) Slide 624 파이썬 생존 안내서 (자막) Slide 625 파이썬 생존 안내서 (자막) Slide 626 파이썬 생존 안내서 (자막) Slide 627 파이썬 생존 안내서 (자막) Slide 628 파이썬 생존 안내서 (자막) Slide 629 파이썬 생존 안내서 (자막) Slide 630 파이썬 생존 안내서 (자막) Slide 631 파이썬 생존 안내서 (자막) Slide 632 파이썬 생존 안내서 (자막) Slide 633 파이썬 생존 안내서 (자막) Slide 634 파이썬 생존 안내서 (자막) Slide 635 파이썬 생존 안내서 (자막) Slide 636 파이썬 생존 안내서 (자막) Slide 637 파이썬 생존 안내서 (자막) Slide 638 파이썬 생존 안내서 (자막) Slide 639 파이썬 생존 안내서 (자막) Slide 640 파이썬 생존 안내서 (자막) Slide 641 파이썬 생존 안내서 (자막) Slide 642 파이썬 생존 안내서 (자막) Slide 643 파이썬 생존 안내서 (자막) Slide 644 파이썬 생존 안내서 (자막) Slide 645 파이썬 생존 안내서 (자막) Slide 646 파이썬 생존 안내서 (자막) Slide 647 파이썬 생존 안내서 (자막) Slide 648 파이썬 생존 안내서 (자막) Slide 649 파이썬 생존 안내서 (자막) Slide 650 파이썬 생존 안내서 (자막) Slide 651 파이썬 생존 안내서 (자막) Slide 652 파이썬 생존 안내서 (자막) Slide 653 파이썬 생존 안내서 (자막) Slide 654 파이썬 생존 안내서 (자막) Slide 655 파이썬 생존 안내서 (자막) Slide 656 파이썬 생존 안내서 (자막) Slide 657 파이썬 생존 안내서 (자막) Slide 658 파이썬 생존 안내서 (자막) Slide 659 파이썬 생존 안내서 (자막) Slide 660 파이썬 생존 안내서 (자막) Slide 661 파이썬 생존 안내서 (자막) Slide 662 파이썬 생존 안내서 (자막) Slide 663 파이썬 생존 안내서 (자막) Slide 664 파이썬 생존 안내서 (자막) Slide 665 파이썬 생존 안내서 (자막) Slide 666 파이썬 생존 안내서 (자막) Slide 667 파이썬 생존 안내서 (자막) Slide 668 파이썬 생존 안내서 (자막) Slide 669 파이썬 생존 안내서 (자막) Slide 670 파이썬 생존 안내서 (자막) Slide 671 파이썬 생존 안내서 (자막) Slide 672 파이썬 생존 안내서 (자막) Slide 673 파이썬 생존 안내서 (자막) Slide 674 파이썬 생존 안내서 (자막) Slide 675 파이썬 생존 안내서 (자막) Slide 676 파이썬 생존 안내서 (자막) Slide 677 파이썬 생존 안내서 (자막) Slide 678 파이썬 생존 안내서 (자막) Slide 679 파이썬 생존 안내서 (자막) Slide 680 파이썬 생존 안내서 (자막) Slide 681 파이썬 생존 안내서 (자막) Slide 682 파이썬 생존 안내서 (자막) Slide 683 파이썬 생존 안내서 (자막) Slide 684 파이썬 생존 안내서 (자막) Slide 685 파이썬 생존 안내서 (자막) Slide 686 파이썬 생존 안내서 (자막) Slide 687 파이썬 생존 안내서 (자막) Slide 688 파이썬 생존 안내서 (자막) Slide 689 파이썬 생존 안내서 (자막) Slide 690 파이썬 생존 안내서 (자막) Slide 691 파이썬 생존 안내서 (자막) Slide 692 파이썬 생존 안내서 (자막) Slide 693 파이썬 생존 안내서 (자막) Slide 694 파이썬 생존 안내서 (자막) Slide 695 파이썬 생존 안내서 (자막) Slide 696 파이썬 생존 안내서 (자막) Slide 697 파이썬 생존 안내서 (자막) Slide 698 파이썬 생존 안내서 (자막) Slide 699 파이썬 생존 안내서 (자막) Slide 700 파이썬 생존 안내서 (자막) Slide 701 파이썬 생존 안내서 (자막) Slide 702 파이썬 생존 안내서 (자막) Slide 703 파이썬 생존 안내서 (자막) Slide 704 파이썬 생존 안내서 (자막) Slide 705 파이썬 생존 안내서 (자막) Slide 706 파이썬 생존 안내서 (자막) Slide 707 파이썬 생존 안내서 (자막) Slide 708 파이썬 생존 안내서 (자막) Slide 709 파이썬 생존 안내서 (자막) Slide 710 파이썬 생존 안내서 (자막) Slide 711 파이썬 생존 안내서 (자막) Slide 712 파이썬 생존 안내서 (자막) Slide 713 파이썬 생존 안내서 (자막) Slide 714 파이썬 생존 안내서 (자막) Slide 715 파이썬 생존 안내서 (자막) Slide 716 파이썬 생존 안내서 (자막) Slide 717 파이썬 생존 안내서 (자막) Slide 718 파이썬 생존 안내서 (자막) Slide 719 파이썬 생존 안내서 (자막) Slide 720 파이썬 생존 안내서 (자막) Slide 721 파이썬 생존 안내서 (자막) Slide 722 파이썬 생존 안내서 (자막) Slide 723 파이썬 생존 안내서 (자막) Slide 724 파이썬 생존 안내서 (자막) Slide 725 파이썬 생존 안내서 (자막) Slide 726 파이썬 생존 안내서 (자막) Slide 727 파이썬 생존 안내서 (자막) Slide 728 파이썬 생존 안내서 (자막) Slide 729 파이썬 생존 안내서 (자막) Slide 730 파이썬 생존 안내서 (자막) Slide 731 파이썬 생존 안내서 (자막) Slide 732 파이썬 생존 안내서 (자막) Slide 733
Nächste SlideShare
[120316] node.js 프로그래밍 5장
Weiter
Herunterladen, um offline zu lesen und im Vollbildmodus anzuzeigen.

373 Gefällt mir

Teilen

Herunterladen, um offline zu lesen

파이썬 생존 안내서 (자막)

Herunterladen, um offline zu lesen

넥슨코리아 사내 발표자료로 왓 스튜디오에서 파이썬으로 《야생의 땅: 듀랑고》 서버를 비롯한 여러가지 도구를 만든 경험을 공유합니다.

- 게임서버와 각종 툴, 테스트/빌드/배포 시스템을 만들 때 사용한 재료
- 파이썬 코드 품질 개선, 디버깅, 프로파일링, 최적화
- 파이썬 오픈소스 생태계와 왓 스튜디오가 하는 오픈소스 활동

Ähnliche Bücher

Kostenlos mit einer 30-tägigen Testversion von Scribd

Alle anzeigen

Ähnliche Hörbücher

Kostenlos mit einer 30-tägigen Testversion von Scribd

Alle anzeigen

파이썬 생존 안내서 (자막)

  1. 1. 파이썬 생존 안내서 이흥섭 @ 왓 스튜디오 2016년 10월 11일 넥슨토크
  2. 2. 파이썬 생존 안내서 이흥섭 @ 왓 스튜디오 2016년 10월 11일 넥슨토크 안녕하세요.
  3. 3. 이흥섭 게임서버 아키텍트 @ 왓 스튜디오 파이썬을 주제로 발표하게 된 왓 스튜디오의 이흥섭입니다.
  4. 4. 이흥섭 게임서버 아키텍트 @ 왓 스튜디오 현재 《야생의 땅: 듀랑고》 프로젝트에서 게임서버 아키텍트로 일하고 있습니다.
  5. 5. • 《야생의 땅: 듀랑고》 • 《카트라이더 대시》 • 《카트라이더 코인러시》 • 한글라이즈 • Profiling • TrueSkill 파이썬 구현 이전엔 《카트라이더 대시》와 《코인러시》 시리즈의 서버를 만들었고
  6. 6. • 《야생의 땅: 듀랑고》 • 《카트라이더 대시》 • 《카트라이더 코인러시》 • 한글라이즈 • Profiling • TrueSkill 파이썬 구현 틈틈이 GitHub 같은 곳에서 오픈소스 활동도 하고 있어요.
  7. 7. • NDC 2014 ― 《야생의 땅: 듀랑고》 서버 아키텍처 • 파이콘 KR 2015 ― Profiling • NDC 2016 ― 《야생의 땅: 듀랑고》 서버 아키텍처 Vol. 2 오늘이 제 4번째 발표인데요
  8. 8. • NDC 2014 ― 《야생의 땅: 듀랑고》 서버 아키텍처 • 파이콘 KR 2015 ― Profiling • NDC 2016 ― 《야생의 땅: 듀랑고》 서버 아키텍처 Vol. 2 재작년과 올해 NDC에서 《듀랑고》 서버 아키텍처에 대한 얘기를 했었고
  9. 9. • NDC 2014 ― 《야생의 땅: 듀랑고》 서버 아키텍처 • 파이콘 KR 2015 ― Profiling • NDC 2016 ― 《야생의 땅: 듀랑고》 서버 아키텍처 Vol. 2 작년 파이콘 KR에선 저희가 만든 파이썬 프로파일러에 대해 얘기한 적이 있습니다.
  10. 10. 2006년 파이썬 2.4~ 저는 2006년, 최신 파이썬 버전이 2.4이던 시절부터 파이썬을 써왔어요.
  11. 11. 코드 의도 특히 프로그래머의 의도에 비해서
  12. 12. 코드 의도 써야 되는 코드의 양이 적다는 점이 매력으로 느껴졌었는데
  13. 13. 코드 의도 아무래도 문법도 C 스타일 언어에 비해선 짧은 코드에 좀 더 맞춰져 있고
  14. 14. 코드 의도 표준 라이브러리나 성숙한 써드파티 라이브러리도 풍부해서
  15. 15. 코드 의도 어지간한 기능은 직접 짜는 대신 끌어다 쓸 수 있기 때문인 것 같아요.
  16. 16. 재작년 NDC 발표에서 소개했듯이
  17. 17. • 《야생의 땅: 듀랑고》 서버는 파이썬으로 만들고 있습니다.
  18. 18. 파이썬
  19. 19. 파이썬 그럼 본격적인 이야기에 앞서 간단히 파이썬을 소개해 볼게요.
  20. 20. 1989년 12월 Guido van Rossum 제가 1989년 12월 생인데 마침 파이썬도 같은 달에 시작됐더라고요.
  21. 21. 1989년 12월 Guido van Rossum 그때부터 Guido van Rossum이 만든 언어입니다.
  22. 22. • https://github.com/python • PEP https://www.python.org/dev/peps/ • 파이콘 파이썬은 오픈소스고
  23. 23. • https://github.com/python • PEP https://www.python.org/dev/peps/ • 파이콘 사람들이 PEP이라는 파이썬 개선 제안서를 제출하면
  24. 24. • https://github.com/python • PEP https://www.python.org/dev/peps/ • 파이콘 토론한 후 채택하거나 반려하는 식으로 발전해왔어요.
  25. 25. • https://github.com/python • PEP https://www.python.org/dev/peps/ • 파이콘 파이콘은 파이썬 공식 컨퍼런스인데
  26. 26. • https://github.com/python • PEP https://www.python.org/dev/peps/ • 파이콘 전세계에서 정기적으로 열리고 있고 2014년부터는 한국에서도 진행되고 있습니다.
  27. 27. import this class Example(object): pass def example(): return Example() print 'Hello, world' 파이썬 코드는 대충 이렇게 생겼어요.
  28. 28. import this class Example(object): pass def example(): return Example() print 'Hello, world' 모듈 체계가 있고
  29. 29. import this class Example(object): pass def example(): return Example() print 'Hello, world' 클래스와 함수가 있고
  30. 30. import this class Example(object): pass def example(): return Example() print 'Hello, world' Java나 C# 등과 다르게 꼭 메소드 속이 아니더라도 코드를 실행할 수 있어요.
  31. 31. for x in numbers: if 2 < x < 4: break else: print 'not found' 그리고 은근히 편한 문법이 많은데
  32. 32. for x in numbers: if 2 < x < 4: break else: print 'not found' 가령 숫자 범위를 검사할 때
  33. 33. for x in numbers: if 2 < x and x < 4: break else: print 'not found' 부등식 2개를 and로 잇는 대신
  34. 34. for x in numbers: if 2 < x < 4: break else: print 'not found' 부등호를 숫자 양 옆에 바로 쓸 수도 있고
  35. 35. for x in numbers: if 2 < x < 4: break else: print 'not found' if 문 뿐만 아니라 루프에도 else 절을 달 수 있어서
  36. 36. for x in numbers: if 2 < x < 4: break else: print 'not found' 루프가 중간에 멈췄는지 아니면 끝까지 무사히 돌았는지
  37. 37. for x in numbers: if 2 < x < 4: break else: print 'not found' 표시해둘 변수를 따로 두지 않아도 되죠.
  38. 38. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 다들 아시다시피 파이썬은 동적 타입 언어예요.
  39. 39. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 컴파일되는 언어도 아니고 타입을 결정한 채 런타임을 돌릴 수도 없죠.
  40. 40. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 그렇다고 타입개념이 아주 희박하진 않은데
  41. 41. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 이 사분면은 〈 〉란 글에서 따왔어요. Dynamic Typing is NOT Weak Typing
  42. 42. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 여기서 “강타입”과 “약타입”은
  43. 43. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 얼마나 코드에 명시적으로 쓰지 않고도
  44. 44. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 저절로 타입캐스팅 되는 경우가 많은지를 가리켜요.
  45. 45. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 이 사분면에서 파이썬은 이쯤 위치한다고 하네요.
  46. 46. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 아주 강타입인 것도 아니지만 그렇다고 JavaScript나 PHP처럼
  47. 47. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 나도 모르게 숫자와 문자열 사이를 왔다 갔다 하지도 않습니다.
  48. 48. Erlang Clojure Python Groovy Ruby Magik C# F# Scala Haskell C C++ Perl VB PHP JavaScript Java 동적 정적 약 강 대신 런타임 에러가 나죠.
  49. 49. 과학 SciPy, NumPy, matplotlib 파이썬은 과학계에서 오랫동안 깊게 쓰여왔는데
  50. 50. 과학 SciPy, NumPy, matplotlib scipy, numpy, matplotlib 같은 라이브러리가 이 방면에서 아주 유명합니다.
  51. 51. 데이터분석 Jupyter, Pandas, PySpark 요즘은 Scala와 더불어서 데이터분석에도 많이 쓰인다고 해요.
  52. 52. 데이터분석 Jupyter, Pandas, PySpark Jupyter와 Pandas를 이용해서 데이터에 쿼리를 날려보고
  53. 53. 데이터분석 Jupyter, Pandas, PySpark 결과를 읽어서 새로운 통찰을 얻은 다음 다시 새로운 쿼리를 날려보는
  54. 54. 데이터분석 Jupyter, Pandas, PySpark “탐색적 자료 분석”을 많이 추천하더라고요.
  55. 55. 기계학습 Theano, TensorFlow 김영호, 2016 과학계의 연장인지 최근엔 기계학습 쪽에서도 많이 쓰이고 있어요.
  56. 56. 기계학습 Theano, TensorFlow 김영호, 2016 이 영상은 저희 스튜디오 김영호 님이
  57. 57. 기계학습 Theano, TensorFlow 김영호, 2016 Google에서 만든 딥러닝 라이브러리인 TensorFlow를 이용해서
  58. 58. 기계학습 Theano, TensorFlow 김영호, 2016 알파고처럼 AI에게 총알 피하기 게임을 학습시킨 모습이에요.
  59. 59. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' Ruby에 Rails가 있듯이
  60. 60. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' 파이썬에도 Flask나 Django 같은 훌륭한 웹 프레임워크들이 있는데
  61. 61. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' 우리나라 스타트업에서 많이 쓰이고 있는 것 같아요.
  62. 62. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' 제 홈페이지도 Flask로 만들어져 있죠.
  63. 63. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' 또 Requests라고 정말 쓰기 편한 HTTP 클라이언트 라이브러리가 있는데
  64. 64. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' 이게 너무 편해서 표준 라이브러리를 꺾고 사실상 표준이 됐어요.
  65. 65. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' 저 같은 경우 여러가지 RESTful API 클라이언트 만들 때
  66. 66. 웹 Flask, Django, Requests @app.route('/') def hello(): return 'Hello, world' Requests 덕을 많이 봤습니다.
  67. 67. 게임 이브온라인, 페리아연대기, pygame 게임 쪽 사례로는 《이브온라인》이 가장 유명한데
  68. 68. 게임 이브온라인, 페리아연대기, pygame 코루틴을 지원하는 변종 파이썬인 Stackless Python으로 서버를 만들었어요.
  69. 69. 게임 이브온라인, 페리아연대기, pygame 《페리아연대기》에서는 유저 스크립트 언어로 파이썬을 쓰는 것 같았습니다.
  70. 70. 게임 이브온라인, 페리아연대기, pygame pygame이라는 아주 작은 게임 엔진도 있는데
  71. 71. 게임 이브온라인, 페리아연대기, pygame 복잡한 건 만들기 어렵지만 간단한 게임 클라이언트 만들기에는 좋아요.
  72. 72. 게임 이브온라인, 페리아연대기, pygame 아까 보신 김영호 님의 총알피하기 게임도 클라이언트를 이것으로 만들었어요.
  73. 73. 데스크톱 자동화 SikuliX, AutoPy click( ) type('cmd') type(Key.ENTER) 그밖에 데스크탑 매크로를
  74. 74. 데스크톱 자동화 SikuliX, AutoPy click( ) type('cmd') type(Key.ENTER) 스크립트로 짤 수 있게 해주는 프레임워크도 몇 가지 있으니
  75. 75. 데스크톱 자동화 SikuliX, AutoPy click( ) type('cmd') type(Key.ENTER) 프로그래밍과는 거리가 먼 사람도, 특히 사무직이라면
  76. 76. 데스크톱 자동화 SikuliX, AutoPy click( ) type('cmd') type(Key.ENTER) 이런 것으로 업무 생산성을 많이 높일 수 있을 것 같아요.
  77. 77. Awesome Python http://awesome-python.com/ 이곳에 들어가시면 제가 소개한 분야 외에도
  78. 78. Awesome Python http://awesome-python.com/ 파이썬이 어떻게 쓰이고 있는지 살펴볼 수 있습니다.
  79. 79. •듀랑고 서버 •유닛테스트 •빌드/배포 •각종 CLI 툴 이제 본론으로 들어가 볼게요.
  80. 80. •듀랑고 서버 •유닛테스트 •빌드/배포 •각종 CLI 툴 저희 스튜디오에선 파이썬을 《듀랑고》 게임서버와 유닛테스트,
  81. 81. •듀랑고 서버 •유닛테스트 •빌드/배포 •각종 CLI 툴 빌드와 배포, 그리고 각종 커맨드라인 툴을 만드는 데 쓰고 있습니다.
  82. 82. 1. 파이썬, 인터프리터 2. 동시성 3. 웹 서버 4. 터미널 앱 5. 빌드와 배포 6. 컨벤션 통일 7. REPL 8. 디버깅 9. 자료구조 10. 제너레이터 11. 성능 측정 12. 로드타임 13. 메모리 최적화 14. 흔한 실수 15. 오픈소스 저희가 이런 걸 만들 때 어떤 재료를 써왔고
  83. 83. 1. 파이썬, 인터프리터 2. 동시성 3. 웹 서버 4. 터미널 앱 5. 빌드와 배포 6. 컨벤션 통일 7. REPL 8. 디버깅 9. 자료구조 10. 제너레이터 11. 성능 측정 12. 로드타임 13. 메모리 최적화 14. 흔한 실수 15. 오픈소스 또 어떤 재료를 안 썼거나 혹은 못 썼는지
  84. 84. 1. 파이썬, 인터프리터 2. 동시성 3. 웹 서버 4. 터미널 앱 5. 빌드와 배포 6. 컨벤션 통일 7. REPL 8. 디버깅 9. 자료구조 10. 제너레이터 11. 성능 측정 12. 로드타임 13. 메모리 최적화 14. 흔한 실수 15. 오픈소스 그리고 코드 품질은 어떻게 관리하고 개선하고 있는지
  85. 85. 1. 파이썬, 인터프리터 2. 동시성 3. 웹 서버 4. 터미널 앱 5. 빌드와 배포 6. 컨벤션 통일 7. REPL 8. 디버깅 9. 자료구조 10. 제너레이터 11. 성능 측정 12. 로드타임 13. 메모리 최적화 14. 흔한 실수 15. 오픈소스 주제가 꽤 다양하긴 한데 하나씩 살펴보겠습니다.
  86. 86. 파이썬, 인터프리터
  87. 87. 파이썬, 인터프리터 저희가 쓰는 파이썬 버전과 인터프리터에 대해 먼저 얘기해 볼게요.
  88. 88. CPython, PyPy, Jython, IronPython, Python for .NET 2.6, 2.7, 3.4, 3.5 파이썬엔 여러가지 인터프리터 구현이 있고
  89. 89. CPython, PyPy, Jython, IronPython, Python for .NET 2.6, 2.7, 3.4, 3.5 언어 버전에도 여러가지가 있어요.
  90. 90. CPython, PyPy, Jython, IronPython, Python for .NET 2.6, 2.7, 3.4, 3.5 그 중 저희가 사용하는 인터프리터는 C로 만든 공식 구현체인 CPython,
  91. 91. CPython, PyPy, Jython, IronPython, Python for .NET 2.6, 2.7, 3.4, 3.5 그리고 언어 버전은 2.7이에요.
  92. 92. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 파이썬 2는 3와 완전히 호환되진 않는데
  93. 93. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 2.7은 그런 파이썬 2 중에서 마지막 버전이죠.
  94. 94. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 파이썬 언어 개발자들은 지금 3에 주력하고 있어서
  95. 95. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 3에서 이룬 수 많은 개선점을 2에는 제공해주지 않고
  96. 96. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 버그픽스와 보안패치 정도만 해주고 있습니다.
  97. 97. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 그래서 지금은 굳이 2를 쓸 이유가 없지만
  98. 98. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 저희가 서버를 만들기 시작한 2013년에만 해도
  99. 99. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 3를 지원하는 써드파티 라이브러리가 그렇게 많지 않아서 2.7을 선택했었는데
  100. 100. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 몇 년 새 상황이 많이 달라졌더라고요.
  101. 101. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 요즘은 써드파티 라이브러리도 어지간해선 3를 지원해요.
  102. 102. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 저희도 넘어가고 싶지만
  103. 103. 파이썬 2.7 •파이썬 2 마지막 마이너 버전 •신기능 패치 중단, 버그/보안 패치는 유지 •2013년엔 라이브러리 호환성이 가장 좋았음. 이미 2에 맞춰서 짠 코드가 많아서 쉽게 넘어가진 못 하고 있습니다.
  104. 104. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 파이썬 3를 2와 비교해보면 우선 문자열과 바이트열 구분이 명확해져서
  105. 105. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 둘이 섞어 써서 인코딩 오류가 나는 참사는 발생하지 않아요.
  106. 106. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 또 기존에 있던 표준 라이브러리는 깔끔하게 정돈됐고
  107. 107. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 다만 이 과정에서 하위호환성이 많이 깨졌죠.
  108. 108. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 또 새로운 표준 라이브러리도 여러가지 생겼는데
  109. 109. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 특히 비동기 I/O 라이브러리인 asyncio나
  110. 110. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 파이썬에서 정적 타입힌트를 달 수 있게 해주는 typing 같은 게
  111. 111. 파이썬 3? •명확한 문자열/바이트열 구분 •정돈되고 강해진 표준 라이브러리 •특히 asyncio, typing 아주 탐나더라고요. 못 써서 아쉽습니다.
  112. 112. from __future__ import unicode_literals import six 아쉬운 대로 저희는 다음 파이썬 버전의 특성 중 일부를 차용할 수 있게 해주는
  113. 113. from __future__ import unicode_literals import six __future__ 모듈로 3의 특성 중 유용한 몇 가지를 가져와서 쓰고 있어요.
  114. 114. from __future__ import unicode_literals import six 한편 저희 내부 코드가 아닌 외부에 공개할 오픈소스 라이브러리를 만들 땐
  115. 115. from __future__ import unicode_literals import six 같은 코드로 파이썬 2와 3를 함께 지원하기 위해서
  116. 116. from __future__ import unicode_literals import six six라는 라이브러리로 호환성을 갖추고 있어요.
  117. 117. from __future__ import unicode_literals import six=6=2×3 여기서 six는 2 곱하기 3을 뜻한다네요.
  118. 118. PyPy? 파이썬으로 만든 파이썬 인터프리터 공식 구현체인 CPython 말고
  119. 119. PyPy? 파이썬으로 만든 파이썬 인터프리터 파이썬으로 만든 파이썬 인터프리터인 PyPy도 추천합니다.
  120. 120. PyPy? 파이썬으로 만든 파이썬 인터프리터 PyPy는 JavaScript의 V8 처럼 파이썬 코드를 JIT 컴파일 해주는데요
  121. 121. PyPy > CPython 7.5x 여기서 내세우는 비교자료를 보면 CPython보다 7.5배 정도 빠르다고 합니다.
  122. 122. PyPy > CPython 7.5x 다만 C로 구현된 라이브러리 같은 경우는 호환되지 않을 때도 종종 있어서
  123. 123. PyPy > CPython 7.5x 저희는 아직 채택하지 못 했어요.
  124. 124. 파이썬 3나 PyPy 저희는 파이썬 2.7과 CPython 둘 다 벗어나지 못 했지만
  125. 125. 파이썬 3나 PyPy 그래도 언젠가는 파이썬 3나 PyPy로 넘어가고자 합니다.
  126. 126. 파이썬 3나 PyPy 여러분이 만약 새 프로젝트를 시작하신다면 저희보다 나은 선택을 하시길 바랄게요.
  127. 127. 동시성
  128. 128. 동시성 다음 주제는 동시성이에요.
  129. 129. gevent 코루틴 기반 비동기 I/O 저흰 gevent라는 코루틴 기반 비동기 I/O 라이브러리를 쓰고 있습니다.
  130. 130. greenlet 코루틴 http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011 원래 파이썬엔 코루틴이 없지만 greenlet이라는 라이브러리가
  131. 131. greenlet 코루틴 파이썬을 마개조해서 코루틴을 쓸 수 있게 해줬어요. http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
  132. 132. greenlet 코루틴 이 라이브러리는 PyPy 만든 Armin Rigo와 http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
  133. 133. greenlet 코루틴 Stackless Python 만든 Christian Tismer의 합작이에요. http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
  134. 134. greenlet 코루틴 아래 링크는 예전에 데브캣스튜디오 이승재 님께서 http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
  135. 135. greenlet 코루틴 greenlet에 대해 쓰셨던 글인데 기억에 남아서 한 번 가져와봤습니다. http://lee-seungjae.github.io/greenlet.html ― 이승재, 2011
  136. 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. 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. 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. 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. 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. 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]]) 평범하죠?
  142. 142. Greenlet Thread gevent 스레드는 방금 얘기한 라이브러리 이름과 같은
  143. 143. Greenlet Thread “Greenlet”이라고 불러요.
  144. 144. Greenlet Thread Greenlet은 유저스페이스에서 도는 경량 스레드라서
  145. 145. Greenlet Thread 표준 스레드보다 오버헤드가 훨씬 적고 가벼워요.
  146. 146. Greenlet Thread 그래서 수천 개씩 띄워서 쓸 수 있는데
  147. 147. Greenlet Thread 특히 네트워크를 많이 다룰 때 높은 동시성을 얻을 수 있죠.
  148. 148. if key in GLOBAL_DICT: gevent.sleep(0.001) del GLOBAL_DICT[key] 쓰는 법이 멀티스레딩과 다르지 않다 보니 코딩할 때 고달플 때도 있어요.
  149. 149. if key in GLOBAL_DICT: gevent.sleep(0.001) del GLOBAL_DICT[key] 이렇게 첫 줄에서 아무리 조건을 확보해 놔도
  150. 150. if key in GLOBAL_DICT: gevent.sleep(0.001) del GLOBAL_DICT[key] 스레드 봉쇄가 발생한 이후에는
  151. 151. if key in GLOBAL_DICT: gevent.sleep(0.001) del GLOBAL_DICT[key] KeyError! 조건이 무효해질 수 있거든요.
  152. 152. if key in GLOBAL_DICT: gevent.sleep(0.001) del GLOBAL_DICT[key] KeyError! 그럼에도 저희가 gevent를 택했던 이유는
  153. 153. import gevent.monkey gevent.monkey.patch_all() 이미 동기식으로 작성돼있는
  154. 154. import gevent.monkey gevent.monkey.patch_all() 표준 라이브러리를 비롯한 수 많은 라이브러리를
  155. 155. import gevent.monkey gevent.monkey.patch_all() 모두 그대로 쓸 수 있다는 점 때문이었어요.
  156. 156. import gevent.monkey gevent.monkey.patch_all() gevent가 제공하는 멍키패칭을 돌리면
  157. 157. import gevent.monkey gevent.monkey.patch_all() threading이나 socket 같은 표준 라이브러리가
  158. 158. import gevent.monkey gevent.monkey.patch_all() 전부 gevent 용으로 갈아치워져서
  159. 159. import gevent.monkey gevent.monkey.patch_all() 비동기 I/O를 고려하지 않던 기존 코드까지도 모두 동시에 실행할 수 있게 되거든요.
  160. 160. import gevent.monkey gevent.monkey.patch_all() JavaScript처럼 콜백을 등록한다거나 C#처럼 async, await 키워드를 쓴다거나
  161. 161. import gevent.monkey gevent.monkey.patch_all() 그런 특별한 코딩법을 필요로 하는 다른 동시성 모델로는
  162. 162. import gevent.monkey gevent.monkey.patch_all() 달성할 수 없는 강점이죠.
  163. 163. 동시성 (Concurrency) 병렬성 (Parallelism) 단 gevent로 확보할 수 있는 건 동시성이지 병렬성은 아닌데
  164. 164. 동시성 (Concurrency) 병렬성 (Parallelism) 수 많은 I/O를 동시에 수행할 순 있지만 실제로는 싱글스레디드로 돌아서
  165. 165. 동시성 (Concurrency) 병렬성 (Parallelism) 여러 CPU 코어를 활용할 순 없어요.
  166. 166. GIL 사실 이 점은 파이썬 표준 멀티스레딩도 마찬가지입니다.
  167. 167. GIL 파이썬엔 역사적인 이유로 Global Interpreter Lock이란 게 있는데
  168. 168. GIL 이 때문에 파이썬 바이트코드는 동시에 여러 스레드에서 실행될 수 없거든요.
  169. 169. +멀티프로세싱 그래서 저희는 부족한 병렬성을 보완하기 위해 멀티프로세싱도 같이 쓰고 있습니다.
  170. 170. def hang(): while True: pass gevent.spawn(hang) 표준 멀티스레딩과 달리 gevent를 쓸 땐 주의해야 할 점이 있는데요
  171. 171. def hang(): while True: pass gevent.spawn(hang) 선점형 멀티태스킹이 아니다 보니 한 스레드가 쉴 새 없이 돌면
  172. 172. def hang(): while True: pass gevent.spawn(hang) 다른 스레드로는 기회가 넘어가지 않는다는 거예요.
  173. 173. def hang(): while True: pass gevent.spawn(hang) 특히 게임서버에서는 이런 일이 절대 생기면 안 되겠죠.
  174. 174. def hang(): while True: gevent.idle() gevent.spawn(hang) 꼭 sleep이나 idle이라도 넣어서
  175. 175. def hang(): while True: gevent.idle() gevent.spawn(hang) 스레드가 잠시나마 봉쇄되게끔 만들어야 이런 일을 방지할 수 있어요.
  176. 176. asyncio? 파이썬 3.4~ 한편 파이썬 3.4부터는 asyncio라는
  177. 177. asyncio? 파이썬 3.4~ 차세대 비동기 I/O 라이브러리를 쓸 수 있는데
  178. 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. 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. 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. 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. 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. 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에 맞춰진 라이브러리만 써야해서 선택의 폭이 좁아지긴 해요.
  184. 184. aiobotocore, aiohttp, aiokafka, aiomcache, aiomysql, aioredis, aiorwlock, aiozmq 그래도 보통 이름에 “aio”라는 접두사가 붙어있으니까 쉽게 구별할 순 있을 겁니다.
  185. 185. •멀티프로세싱 •멀티스레딩 •asyncio •gevent •eventlet •Twisted 제가 소개한 것 외에도 파이썬에선 여러가지 방법으로 동시성을 확보할 수 있어요.
  186. 186. •멀티프로세싱 •멀티스레딩 •asyncio •gevent •eventlet •Twisted 각 방법에는 서로 다른 트레이드오프가 있죠.
  187. 187. •멀티프로세싱 •멀티스레딩 •asyncio •gevent •eventlet •Twisted 만약 여러분도 파이썬으로 서버를 만드신다면
  188. 188. •멀티프로세싱 •멀티스레딩 •asyncio •gevent •eventlet •Twisted 이런 방법들을 한 번 비교해보고 가장 적합한 걸 고르시면 좋을 것 같습니다.
  189. 189. 웹 서버
  190. 190. 웹 서버 저희 게임서버는 대부분이 소켓을 직접 다루는 데디케이티드 서버지만
  191. 191. 웹 서버 일부는 RESTful한 웹 서버로 돼있어요.
  192. 192. Flask 거기엔 앞에서도 소개했던 Flask라는 간단한 웹 서버 프레임워크를 쓰는데
  193. 193. •Werkzeug 웹 서버 유틸리티 •Jinja2 템플릿 엔진 Flask는 Werkzeug라는 웹 서버 유틸리티와
  194. 194. •Werkzeug 웹 서버 유틸리티 •Jinja2 템플릿 엔진 Jinja2라고, 보통 HTML 페이지 찍어낼 때 쓰는 템플릿엔진 위에서
  195. 195. •템플릿 엔진 •URL 라우팅 •세션 •서브도메인 •JSON 지원 •디버깅 콘솔 몇 가지 편의 기능을 함께 제공하는 작은 웹 서버 프레임워크예요.
  196. 196. •템플릿 엔진 •URL 라우팅 •세션 •서브도메인 •JSON 지원 •디버깅 콘솔 작은 프레임워크이긴 하지만
  197. 197. •템플릿 엔진 •URL 라우팅 •세션 •서브도메인 •JSON 지원 •디버깅 콘솔 일반적으로 웹 개발할 때 필요한 간단한 기능들은 대부분 갖추고 있고
  198. 198. •템플릿 엔진 •URL 라우팅 •세션 •서브도메인 •JSON 지원 •디버깅 콘솔 좀 더 복잡한 기능도 플러그인으로 쉽게 구할 수 있어요.
  199. 199. app = Flask(__name__) @app.route('/') def index(): return 'Hello, world', 200 간단한 Flask 예제예요.
  200. 200. app = Flask(__name__) @app.route('/') def index(): return 'Hello, world', 200 데코레이터를 이용해서 URL 라우팅 테이블을 직관적으로 만들어내는 게 특징이죠.
  201. 201. #include "crow.h" int main() { crow::SimpleApp app; CROW_ROUTE(app, "/")([](){ return "Hello world"; }); } 우리 회사 하재승 님이 만드신
  202. 202. #include "crow.h" int main() { crow::SimpleApp app; CROW_ROUTE(app, "/")([](){ return "Hello world"; }); } C++ 웹 프레임워크 Crow가 여기서 영감을 얻었다고 합니다.
  203. 203. WSGI Web Server Gateway Interface 파이썬엔 WSGI라는 표준화된 웹 애플리케이션 인터페이스가 있어요.
  204. 204. WSGI Web Server Gateway Interface Flask를 비롯해 아마도 모든 파이썬 웹 서버 라이브러리가
  205. 205. WSGI Web Server Gateway Interface 이 인터페이스를 따르고 있을 거예요.
  206. 206. app = Flask(__name__) server = gevent.pywsgi.WSGIServer(socket, app) server.serve_forever() 이 표준 인터페이스 덕분에 Flask를 gevent에도 잘 붙일 수 있었어요.
  207. 207. app = Flask(__name__) server = gevent.pywsgi.WSGIServer(socket, app) server.serve_forever() gevent엔 고성능 WSGI 서버가 들어있는데
  208. 208. app = Flask(__name__) server = gevent.pywsgi.WSGIServer(socket, app) server.serve_forever() 이것으로 Flask 웹 서버를 바로 서빙할 수 있는 거죠.
  209. 209. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) yield 'Hello, worldn' WSGI 애플리케이션은 단순히
  210. 210. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) yield 'Hello, worldn' HTTP 요청정보를 담는 environ 인자와
  211. 211. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) yield 'Hello, worldn' HTTP 응답시작을 개시하는 함수인 start_response 인자를 받아서
  212. 212. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) yield 'Hello, worldn' 응답 헤더를 결정한 후 start_response를 불러주고
  213. 213. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) yield 'Hello, worldn' 내용을 브러우저로 스트리밍할 수 있게끔 yield하거나
  214. 214. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) return ['Hello, worldn'] 시퀀스 형태로 바로 return하는 함수예요.
  215. 215. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) return ['Hello, worldn'] 이 인터페이스에만 맞추면 파이썬의 거의 모든 웹 도구를 같이 쓸 수 있어요.
  216. 216. def wsgi_app(environ, start_response): headers = [('Content-Type', 'text/plain')] start_response('200 OK', headers) return ['Hello, worldn'] 하지만 생긴 걸 보면 알 수 있듯이
  217. 217. HTTP/2.0, WebSocket 아쉽게도 서버사이드 푸시 같은 HTTP/2.0 기능이나
  218. 218. HTTP/2.0, WebSocket 웹소켓 같은 요즘 프로토콜을 구현하진 못해요.
  219. 219. HTTP/2.0, WebSocket 그래도 메일링리스트에서 논의는 되고 있으니까
  220. 220. HTTP/2.0, WebSocket 언젠가 차세대 WSGI가 나오길 기대해봅니다.
  221. 221. Django? 파이썬 웹 프레임워크 중에선 저는 안 써봤지만
  222. 222. Django? Flask보단 Django가 훨씬 유명하고 많이 쓰여요.
  223. 223. Django Flask 구글 트렌드에서 찾아보니까 인기도가 Flask의 4배 정도는 되더라고요.
  224. 224. Instagram과 Pinterest가 Django 쓰는 것으로 유명하다고 합니다.
  225. 225. • ORM • 템플릿 엔진 • URL 라우팅 • 폼 검증 • 캐싱 • 국제화 • XML/JSON 지원 Django는 MVC 구조를 내장하고 있어요.
  226. 226. • ORM • 템플릿 엔진 • URL 라우팅 • 폼 검증 • 캐싱 • 국제화 • XML/JSON 지원 그래서 Flask와 달리 ORM까지 포함하고 있죠.
  227. 227. • ORM • 템플릿 엔진 • URL 라우팅 • 폼 검증 • 캐싱 • 국제화 • XML/JSON 지원 그 밖에도 통합돼있는 기능이 매우 많은데
  228. 228. • ORM • 템플릿 엔진 • URL 라우팅 • 폼 검증 • 캐싱 • 국제화 • XML/JSON 지원 아마 2000년대 후반에 유행했던 Ruby on Rails랑 비슷하지 않을까 싶어요.
  229. 229. •WSGI •Flask •Django 혹시 파이썬으로 웹 서버를 만들려는 분이 계신다면
  230. 230. •WSGI •Flask •Django 이 두 프레임워크와 WSGI 지원하는 도구를 한 번 검토해보세요.
  231. 231. 터미널 앱
  232. 232. 터미널 앱 저희는 서버 개발을 보조하기 위해서
  233. 233. 터미널 앱 터미널에서 쓸 수 있는 도구도 많이 만들고 있어요.
  234. 234. Click CLI 프레임워크 Click은 CLI를 쉽게, 또 잘 만들 수 있게 해주는 프레임워크예요.
  235. 235. Click CLI 프레임워크 앞에서 소개했던 Flask와 이것은 모두 Armin Ronacher가 만들었죠.
  236. 236. import click @click.command() @click.option('-n', '--name', help='Your name.') def cli(name): click.echo('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': cli() Click 애플리케이션 코드는 이렇게 생겼는데
  237. 237. import click @click.command() @click.option('-n', '--name', help='Your name.') def cli(name): click.echo('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': cli() Flask처럼 데코레이터를 적극적으로 활용하고 있어요.
  238. 238. import click @click.command() @click.option('-n', '--name', help='Your name.') def cli(name): click.echo('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': cli() 이렇게 데코레이터로 커맨드 옵션을 선언하면
  239. 239. import click @click.command() @click.option('-n', '--name', help='Your name.') def cli(name): click.echo('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': cli() 같은 이름의 매개변수로 값이 들어오게 돼요.
  240. 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. 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. 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. 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. 상식과 미묘하게 다르게 동작하는 프레임워크도 여럿 봐왔거든요.
  244. 244. Click을 이용하면 이렇게 사용자입력을 받거나
  245. 245. 프로그레스바를 띄우는 것도 쉽게 할 수 있고 색깔도 깔끔하게 다룰 수 있어요.
  246. 246. argparse? Click이 나오기 전에는 argparse라는 표준 라이브러리를 썼었는데
  247. 247. import argparse parser = argparse.ArgumentParser() parser.add_argument('-n', '--name', help='Your name.') def cli(name): print('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': args = parser.parse_args() cli(name=args.name) 이렇게
  248. 248. import argparse parser = argparse.ArgumentParser() parser.add_argument('-n', '--name', help='Your name.') def cli(name): print('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': args = parser.parse_args() cli(name=args.name) 옵션을 정의하는 곳과
  249. 249. import argparse parser = argparse.ArgumentParser() parser.add_argument('-n', '--name', help='Your name.') def cli(name): print('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': args = parser.parse_args() cli(name=args.name) 파싱하고 넘겨주는 곳이 따로 떨어져 있어서
  250. 250. import argparse parser = argparse.ArgumentParser() parser.add_argument('-n', '--name', help='Your name.') def cli(name): print('Hi %s!' % (name or 'NONAME')) if __name__ == '__main__': args = parser.parse_args() cli(name=args.name) Click에 비해 불편했습니다.
  251. 251. Urwid TUI 프레임워크 단순한 CLI 말고 리눅스의 top이나 aptitude 같이
  252. 252. Urwid TUI 프레임워크 텍스트로 된 UI를 만들고 싶을 땐 Urwid라는 TUI 프레임워크를 쓰고 있어요.
  253. 253. Urwid를 쓰면 위젯을 조합하는 방식으로 꽤 미려한 텍스트 UI를 만들 수 있죠.
  254. 254. https://excess.org/article/2012/01/urwid-python-malaysia/ 이건 Urwid로 만든 Speedometer라는 건데 멋지지 않나요?
  255. 255. https://excess.org/article/2012/01/urwid-python-malaysia/ 저희 스튜디오에서 만든 파이썬 용 프로파일러에도 Urwid를 사용했는데
  256. 256. https://excess.org/article/2012/01/urwid-python-malaysia/ 그건 뒤에서 성능 측정에 대해 얘기할 때 보여드릴게요.
  257. 257. curses? Urwid 말고는 curses라는 표준 라이브러리가 대안이 될 수 있어요.
  258. 258. curses? 하지만 저는 정말 쓰기 불편했어요.
  259. 259. curses? API가 너무 저수준이고 조금만 실수해도 에러가 빵빵 터졌었거든요.
  260. 260. win.addstr(y, x, 'Hello, world') 심지어 사용하는 좌표계도
  261. 261. win.addstr(y, x, 'Hello, world') x, y가 아니라 y, x예요.
  262. 262. win.addstr(y, x, 'Hello, world') “curse”가 “저주”란 뜻이잖아요? 그 이름값을 하는 거라고 생각합니다.
  263. 263. •Click •Urwid •argparse •curses 혹시 bash 같은 쉘 스크립트를 대신하기 위해서 파이썬을 검토 중이시라면
  264. 264. •Click •Urwid •argparse •curses Click이랑 Urwid도 같이 검토해보세요.
  265. 265. •Click •Urwid •argparse •curses 표준 라이브러리인 argparse나 curses보다 분명히 좋습니다.
  266. 266. 빌드와 배포
  267. 267. 빌드와 배포 저희는 빌드와 배포를 자동화하는 데에도 노력을 많이 기울여왔어요.
  268. 268. 빌드와 배포 여기에도 파이썬을 많이 쓰고 있죠.
  269. 269. build passing 작업자가 Git 저장소에 push하면 CI가 자동으로 빌드 스크립트를 돌리면서
  270. 270. build passing 컴파일해야 할 건 컴파일하고, 코드 품질을 검사한다든가,
  271. 271. build passing 유닛테스트를 돌린다든가, 뭐 그런 일을 합니다.
  272. 272. build passing 빌드에 성공하면 브랜치에 따라서 서버에 배포하는 것까지 연계하고 있어요.
  273. 273. pytest 이때 유닛테스트 프레임워크로 pytest를 쓰는데
  274. 274. def test_answer(): assert answer() == 42 pytest를 쓰면 딸랑 assert 문만 적어놔도
  275. 275. ❯ pytest test.py ========= FAILURES ========= E assert 24 == 42 E + where 24 = answer() def test_answer(): assert answer() == 42 테스트가 실패했을 때 왜 실패했는지 단서까지 함께 보고받을 수 있어요.
  276. 276. ❯ pytest test.py ========= FAILURES ========= E assert 24 == 42 E + where 24 = answer() def test_answer(): assert answer() == 42 여기 보시면 answer의 결과값이 42가 아니라 24였다는 걸 알 수 있죠.
  277. 277. def test_answer(self): self.assertEqual(answer(), 42) unittest 파이썬엔 unittest라는 표준 유닛테스트 프레임워크도 있지만
  278. 278. def test_answer(self): self.assertEqual(answer(), 42) unittest 이것으로 같은 결과를 얻으려면 assertEqual 같은 전용 메소드를 써야만 해요.
  279. 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 이런 식으로 비교연산 별로 메소드가 따로 마련돼있죠.
  280. 280. 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. 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를 쓰기 시작했던 이유는 이 점이 전부였는데