Python 개발자를 위한 Go Gophercon Korea 2015 이경찬
발표자 소개 • 이경찬 (leekchan.com) • 주로 Python으로 서버 개발 • Newbie Gopher • Go로 취미삼아 오픈소스 라이브러리 제작중 • accounting - currency formatting (github.com/leekchan/ accounting) • timeutil - Timedelta, Strftime 구현 (github.com/leekchan/timeutil) • gtf - 유용한 template function 모음 (github.com/leekchan/gtf)
Python
극강의 생산성!
대략 이정도... ???
하지만... • 대규모 환경에서는 조금 아쉬운 성능 • dynamic typing • GIL • C10K problem 해결을 위해 event loop 를 사용하지만 한계가 있는 성능
Go
Go의 장점 • Static typing • 성능 • compile 단계에서 error 확인 가능 • Goroutine (외계기술) • event loop 없이도 C10K problem 해결 • 멀티코어 지원 • 게다가 훌륭한 생산성까지!
Control structures
if • [Python]
 if x > 0: • [Go]
 if x > 0 {
 } • initialization statement
 if ok := check(); ok {
for • [Python]
 for value in value_list: • [Go] • for i, value := range array { // array, slice, map 순회 • for init; condition; post { // c style
 for i := 0; i < 10; i++ { • for condition { // while 문 처럼 사용 • for { // 무한 루프
switch • switch x {
 case 0:
 …
 case 1:
 …
 default:
 … 
 }
Type
Dynamic typing vs Static typing • [Python] Dynamic typing • foo = 1
 foo = ‘bar’
 foo = 1.3 • [Go] Static typing • var foo int = 1
 var foo string = “bar”
 var foo float64 = 1.3
다양한 선언 방법 • var foo int = 1
 var foo string = “bar” • var foo = 1
 var foo = “bar” • foo := 1
 foo := “bar” • type 생략시 대입하는 변수의 type을 따름
다양한 타입 • int, int8, int16, int32, int64 • uint, uint8, uint16, uint32, uint64 • float32, float64 • uintptr (uint와 크기 동일) • complex64, complex128 • byte (uint8과 크기 동일) • rune
Function
함수 선언 • [Python] • def sum(x, y):
 return x + y • [Go] • func sum(x, y int) int {
 return x + y
 } • 매개변수, 리턴값 모두 type 명시 필요! • func sum(x, y int) (int, err) { 와 같이 여러개의 리턴값도 리턴 가능!
이쯤에서 생기는 궁금증... • int 말고 다른 type을 더하고 싶으면 어떻게 해야 할까... ??? • method overloading을 하면 되려나??? • func sum(x, y int) int {
 return x + y
 }
 func sum(x, y float64) float64 {
 return x + y
 } • compile error .. - “sum redeclared in this block”
https://golang.org/doc/faq#overloading Go는 안타깝게도 method overloading을 지원하지 않습니다...
interface{} • interface는 method의 집합 (단, 메서드 자체를 구현하 지는 않음.) • method가 정의되지 않은 빈 인터페이스(interface{})는 어떠한 조건도 없기 때문에 모든 type의 값 대입이 가능. • func sum (x, y interface{})
type switch • switch x.(type) {
 case int:
 …
 case float64:
 … • interface는 내부적으로 실제 값의 type에 관한 정보들을 저장 하고 있는 itable 에 대한 pointer를 가지고 있음. • type switch를 사용하면 Go compiler가 itable 을 체크해서 원 하는 type이 맞는지 비교하는 코드를 생성해줌.

type assertion • value := x.(int) • type을 명시적으로 지정 • 맞지 않는 type으로 assertion을 하면 panic! (runtime error) • 따라서 type switch와 같이 쓰는 것이 일반적
 switch x.(type) {
 case int:
 value := x.(int)
 …
 case float64:
 value := x.(float64)
 …
reflect package • run-time reflection 기능 제공 • reflect 패키지는 앞서 살펴본 type switch & type assertion 에 해당하는 기능들과 여러가지 부가 기능을 가지고 있음. • reflect 패키지의 기능들은 편리하기는 하지만, type switch & type assertion에 비해 더 무거운 연산들이 동 반될 수 있으므로 고성능 어플리케이션 작성시에는 세심 하게 사용할 필요가 있음.
array, slice, map
array • var x [10]int // 길이가 10인 int형 배열 선언 • x := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 
 // 생성과 동시에 초기화 • x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 // 배열 길이가 10으로 자동으로 설정됨 • y := x // 새로운 배열에 대입시 값 전체가 복사됨
slice • Python의 list와 쓰임새가 유사함. • var x []int // 선언 • x := make([]int, 5, 10) // 길이(length)는 6, 용량(capacity)은 10 • 길이 : index로 접근할 수 있는 범위 / 용량 : 실제 할당된 data array 공간 • x := make([]int, 5) // 용량은 생략 가능 • x := []int{0, 1, 2, 3, 4} // 생성과 동시에 초기화 • x = append(x, 1) // 새로운 값 추가
 x = append(x, 1, 2, 3) // 여러 값을 동시에 추가도 가능
slice 내부 구현 • slice의 내부 구현에는 앞서 살펴본 array가 사용됨 • slice 생성시 지정된 용량만큼의 data array를 생성하고 해당 array에 대한 pointer를 가짐. • 이 구현을 제대로 이해하는 것이 중요! ptr
 *Elem len
 int cap
 int []int [5]int
slice 내부 구현 (2) • x := []int{0, 1, 2, 3, 4} // 길이가 5인 slice 생성 • x = append(x, 1) // 해당 slice에 새로운 값을 추가하면? • append는 slice 용량을 먼저 확인하고 용량이 넘치면 새로운 data array allocation 후 값을 추가한다. (용량이 남아 있으면 바로 값만 대입.) • 기존 slice 용량이 5 이므로 새로운 allocation이 발생! • 다행히 append 할때마다 새로 allocation 하지는 않고 future growth 를 고려하여 필요한 용량의 2배를 할당. • append를 매우 빈번하게 할 예정이라면 용량을 예측해서 미리 지정하 면 성능에 도움이 됨.
slice 내부 구현 (3) • array는 새로운 변수에 대입시 값이 모두 복사되던데, slice도 혹시? • slice는 앞서 살펴본 바와 같이 실제 data array에 대한 pointer 이다. 따라서 대입시 pointer만 복사된다. • x := []int{0, 1, 2, 3, 4}
 y := x // y와 x는 같은 data array를 참조. y의 데이터 조작시 x도 변 경됨. ptr
 *Elem len
 int cap
 int []int [5]int
slice 내부 구현 (4) • slice는 아래와 같이 부분 slice 생성이 가능하다. (slicing)
 x = x[2:4]
 이때는 내부적으로 어떻게 동작할까? • slice는 앞서 살펴본 바와 같이 실제 데이터 array에 대한 pointer 이다. slicing시에도 데이터 array는 그대로 존재하고 해당 데이터를 가리키는 pointer를 가진 slice가 생성된다. ptr
 *Elem len
 int cap
 int []int [5]int
map • Python의 dict와 쓰임새가 유사함. • hash table 구현 • var x map[string]int // 선언 (key type : string / value type : int) • x := make(map[string]int) // slice와 마찬가지로 make로 할당 가능 • x := map[string]int{“foo”: 1, “bar”: 2} // 할당과 동시에 초기화 • x[“new”] = 2 // 새로운 key로 값 대입
 x[“foo”] = 4 // 기존 키의 key의 값 변경 • y := x // slice와 마찬가지로 데이터에 대한 pointer만 복사됨. 따라서 y 의 데이터 조작시 x도 변경됨.
map key 존재 여부 체크하기 • map에 존재하지 않는 key를 조회하게 되면 각 type의 zero value가 리턴됨. (int => 0, string => “”, bool => false 등)
 
 x := map[string]int{“foo”: 1, “bar”: 2} 
 value := x[“hello”] // value = 0 • 또는, 아래와 같은 형태로 key 존재 여부 체크도 가능함.
 
 value, ok := x[“hello”] // value = 0 (zero value) , ok = false • if 문에서는 initialization statement를 key 존재 여부 체크 가능.
 
 if value, ok := x[“hello”]; ok { 

map 사용시 주의사항 • Concurrency • map은 여러 goroutine에서 접근시 동시성을 보장하지 않 음. • map을 여러 goroutine에서 접근할 때는 sync.RWMutex 사용 필요 • Iteration order • for range loop로 데이터를 가져올때 순서를 보장하지 않 음.
struct
struct • Python의 class와 유사하게 사용 가능 • 선언
 type Accounting struct {
 Symbol string
 Precision int
 Thousand string
 Decimal string
 Format string
 } • 할당
 accounting := Accounting{Symbol: "$", Precision: 2}
struct method • class 가 없는 대신 struct에 method 연결 가능
 
 type Accounting struct {
 Symbol string
 Precision int
 Thousand string
 Decimal string
 Format string
 }
 
 func (accounting *Accounting) FormatMoney(value interface{}) string {
 …
 } • method 호출
 
 accounting := Accounting{Symbol: "$", Precision: 2}
 accounting.FormatMoney(1234567)
panic / recover
panic • run-time error • Python의 Exception과 유사 • Python의 Exception 처럼 직접 발생시키는 것도 가능
 panic(“unsupported type”) • panic 발생시 적절하게 handling 하지 못하면 어플리케 이션이 중단됨.
recover • defer - 함수 종료 직전에 실행. defer를 여러번 호출하면 LIFO로 실 행 • First Class Function => 익명 함수 호출 가능 • func handler() {
 defer func() {
 if err := recover(); err != nil {
 // panic handling
 } 
 }()
 …
 }
package
package • 소스코드 첫줄에 package명 명시
 package accounting • Python의 module 과 다르게 파일명은 아무런 역할도 하지 않는다. • 패키지 내부에 있는 함수, 변수, 상수들은 아래 규칙에 따라 역할이 결정됨. • 첫 글자가 대문자이면 외부에서 import시 접근 가능 • 첫 글자가 소문자이면 package 내부에서만 접근 가능 • 심지어 struct의 경우에는 struct 이름 첫글자가 대문자여도 필드 이름이 소문자이 면 외부에서 접근 못함. 외부에서 접근이 필요한 필드들은 대문자를 사용해야 한다.
 type Timedelta struct {
 Days, Seconds, Microseconds, Milliseconds, Minutes, Hours, Weeks time.Duration
 }
package 공유 • github 에 repo 생성시 아래 명령어로 바로 패키지 다운로드 및 설치 가능 (bitbucket 등 다른 저장소도 지원)
 go get github.com/leekchan/accounting • 처음에는 좋아 보였으나, PyPI에 비해 단점이 많음. • 무조건 master branch 최신 커밋 소스를 다운로드 • 기능 개발 및 테스트 후 원하는 시점에 versioning을 하고 릴리즈 할 수가 없음 • 해당 단점을 보완하는 다양한 오픈소스 라이브러가 생겨나 고 있는 중
gofmt
gofmt • formatting을 style guide에 맞춰서 알아서 해주는 gofmt 라는 기능을 bult-in 으로 제공 • 덕분에, 일단 코드를 쓰고 커밋하기 전에 gofmt을 한번 실행시켜 주면 style guide에 맞춰서 아름답게 formatting 이 됨. • Python에서 PEP 8 준수를 위해 많은 linter들이 있지만 Go에서는 이미 built-in 기능인 gofmt가 끝판왕.
감사합니다.

GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)

  • 1.
    Python 개발자를 위한Go Gophercon Korea 2015 이경찬
  • 2.
    발표자 소개 • 이경찬(leekchan.com) • 주로 Python으로 서버 개발 • Newbie Gopher • Go로 취미삼아 오픈소스 라이브러리 제작중 • accounting - currency formatting (github.com/leekchan/ accounting) • timeutil - Timedelta, Strftime 구현 (github.com/leekchan/timeutil) • gtf - 유용한 template function 모음 (github.com/leekchan/gtf)
  • 3.
  • 4.
  • 5.
  • 6.
    하지만... • 대규모 환경에서는조금 아쉬운 성능 • dynamic typing • GIL • C10K problem 해결을 위해 event loop 를 사용하지만 한계가 있는 성능
  • 7.
  • 8.
    Go의 장점 • Statictyping • 성능 • compile 단계에서 error 확인 가능 • Goroutine (외계기술) • event loop 없이도 C10K problem 해결 • 멀티코어 지원 • 게다가 훌륭한 생산성까지!
  • 9.
  • 10.
    if • [Python]
 if x> 0: • [Go]
 if x > 0 {
 } • initialization statement
 if ok := check(); ok {
  • 11.
    for • [Python]
 for valuein value_list: • [Go] • for i, value := range array { // array, slice, map 순회 • for init; condition; post { // c style
 for i := 0; i < 10; i++ { • for condition { // while 문 처럼 사용 • for { // 무한 루프
  • 12.
    switch • switch x{
 case 0:
 …
 case 1:
 …
 default:
 … 
 }
  • 13.
  • 14.
    Dynamic typing vsStatic typing • [Python] Dynamic typing • foo = 1
 foo = ‘bar’
 foo = 1.3 • [Go] Static typing • var foo int = 1
 var foo string = “bar”
 var foo float64 = 1.3
  • 15.
    다양한 선언 방법 •var foo int = 1
 var foo string = “bar” • var foo = 1
 var foo = “bar” • foo := 1
 foo := “bar” • type 생략시 대입하는 변수의 type을 따름
  • 16.
    다양한 타입 • int,int8, int16, int32, int64 • uint, uint8, uint16, uint32, uint64 • float32, float64 • uintptr (uint와 크기 동일) • complex64, complex128 • byte (uint8과 크기 동일) • rune
  • 17.
  • 18.
    함수 선언 • [Python] •def sum(x, y):
 return x + y • [Go] • func sum(x, y int) int {
 return x + y
 } • 매개변수, 리턴값 모두 type 명시 필요! • func sum(x, y int) (int, err) { 와 같이 여러개의 리턴값도 리턴 가능!
  • 19.
    이쯤에서 생기는 궁금증... •int 말고 다른 type을 더하고 싶으면 어떻게 해야 할까... ??? • method overloading을 하면 되려나??? • func sum(x, y int) int {
 return x + y
 }
 func sum(x, y float64) float64 {
 return x + y
 } • compile error .. - “sum redeclared in this block”
  • 20.
  • 21.
    interface{} • interface는 method의집합 (단, 메서드 자체를 구현하 지는 않음.) • method가 정의되지 않은 빈 인터페이스(interface{})는 어떠한 조건도 없기 때문에 모든 type의 값 대입이 가능. • func sum (x, y interface{})
  • 22.
    type switch • switchx.(type) {
 case int:
 …
 case float64:
 … • interface는 내부적으로 실제 값의 type에 관한 정보들을 저장 하고 있는 itable 에 대한 pointer를 가지고 있음. • type switch를 사용하면 Go compiler가 itable 을 체크해서 원 하는 type이 맞는지 비교하는 코드를 생성해줌.

  • 23.
    type assertion • value:= x.(int) • type을 명시적으로 지정 • 맞지 않는 type으로 assertion을 하면 panic! (runtime error) • 따라서 type switch와 같이 쓰는 것이 일반적
 switch x.(type) {
 case int:
 value := x.(int)
 …
 case float64:
 value := x.(float64)
 …
  • 24.
    reflect package • run-timereflection 기능 제공 • reflect 패키지는 앞서 살펴본 type switch & type assertion 에 해당하는 기능들과 여러가지 부가 기능을 가지고 있음. • reflect 패키지의 기능들은 편리하기는 하지만, type switch & type assertion에 비해 더 무거운 연산들이 동 반될 수 있으므로 고성능 어플리케이션 작성시에는 세심 하게 사용할 필요가 있음.
  • 25.
  • 26.
    array • var x[10]int // 길이가 10인 int형 배열 선언 • x := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 
 // 생성과 동시에 초기화 • x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 // 배열 길이가 10으로 자동으로 설정됨 • y := x // 새로운 배열에 대입시 값 전체가 복사됨
  • 27.
    slice • Python의 list와쓰임새가 유사함. • var x []int // 선언 • x := make([]int, 5, 10) // 길이(length)는 6, 용량(capacity)은 10 • 길이 : index로 접근할 수 있는 범위 / 용량 : 실제 할당된 data array 공간 • x := make([]int, 5) // 용량은 생략 가능 • x := []int{0, 1, 2, 3, 4} // 생성과 동시에 초기화 • x = append(x, 1) // 새로운 값 추가
 x = append(x, 1, 2, 3) // 여러 값을 동시에 추가도 가능
  • 28.
    slice 내부 구현 •slice의 내부 구현에는 앞서 살펴본 array가 사용됨 • slice 생성시 지정된 용량만큼의 data array를 생성하고 해당 array에 대한 pointer를 가짐. • 이 구현을 제대로 이해하는 것이 중요! ptr
 *Elem len
 int cap
 int []int [5]int
  • 29.
    slice 내부 구현(2) • x := []int{0, 1, 2, 3, 4} // 길이가 5인 slice 생성 • x = append(x, 1) // 해당 slice에 새로운 값을 추가하면? • append는 slice 용량을 먼저 확인하고 용량이 넘치면 새로운 data array allocation 후 값을 추가한다. (용량이 남아 있으면 바로 값만 대입.) • 기존 slice 용량이 5 이므로 새로운 allocation이 발생! • 다행히 append 할때마다 새로 allocation 하지는 않고 future growth 를 고려하여 필요한 용량의 2배를 할당. • append를 매우 빈번하게 할 예정이라면 용량을 예측해서 미리 지정하 면 성능에 도움이 됨.
  • 30.
    slice 내부 구현(3) • array는 새로운 변수에 대입시 값이 모두 복사되던데, slice도 혹시? • slice는 앞서 살펴본 바와 같이 실제 data array에 대한 pointer 이다. 따라서 대입시 pointer만 복사된다. • x := []int{0, 1, 2, 3, 4}
 y := x // y와 x는 같은 data array를 참조. y의 데이터 조작시 x도 변 경됨. ptr
 *Elem len
 int cap
 int []int [5]int
  • 31.
    slice 내부 구현(4) • slice는 아래와 같이 부분 slice 생성이 가능하다. (slicing)
 x = x[2:4]
 이때는 내부적으로 어떻게 동작할까? • slice는 앞서 살펴본 바와 같이 실제 데이터 array에 대한 pointer 이다. slicing시에도 데이터 array는 그대로 존재하고 해당 데이터를 가리키는 pointer를 가진 slice가 생성된다. ptr
 *Elem len
 int cap
 int []int [5]int
  • 32.
    map • Python의 dict와쓰임새가 유사함. • hash table 구현 • var x map[string]int // 선언 (key type : string / value type : int) • x := make(map[string]int) // slice와 마찬가지로 make로 할당 가능 • x := map[string]int{“foo”: 1, “bar”: 2} // 할당과 동시에 초기화 • x[“new”] = 2 // 새로운 key로 값 대입
 x[“foo”] = 4 // 기존 키의 key의 값 변경 • y := x // slice와 마찬가지로 데이터에 대한 pointer만 복사됨. 따라서 y 의 데이터 조작시 x도 변경됨.
  • 33.
    map key 존재여부 체크하기 • map에 존재하지 않는 key를 조회하게 되면 각 type의 zero value가 리턴됨. (int => 0, string => “”, bool => false 등)
 
 x := map[string]int{“foo”: 1, “bar”: 2} 
 value := x[“hello”] // value = 0 • 또는, 아래와 같은 형태로 key 존재 여부 체크도 가능함.
 
 value, ok := x[“hello”] // value = 0 (zero value) , ok = false • if 문에서는 initialization statement를 key 존재 여부 체크 가능.
 
 if value, ok := x[“hello”]; ok { 

  • 34.
    map 사용시 주의사항 •Concurrency • map은 여러 goroutine에서 접근시 동시성을 보장하지 않 음. • map을 여러 goroutine에서 접근할 때는 sync.RWMutex 사용 필요 • Iteration order • for range loop로 데이터를 가져올때 순서를 보장하지 않 음.
  • 35.
  • 36.
    struct • Python의 class와유사하게 사용 가능 • 선언
 type Accounting struct {
 Symbol string
 Precision int
 Thousand string
 Decimal string
 Format string
 } • 할당
 accounting := Accounting{Symbol: "$", Precision: 2}
  • 37.
    struct method • class가 없는 대신 struct에 method 연결 가능
 
 type Accounting struct {
 Symbol string
 Precision int
 Thousand string
 Decimal string
 Format string
 }
 
 func (accounting *Accounting) FormatMoney(value interface{}) string {
 …
 } • method 호출
 
 accounting := Accounting{Symbol: "$", Precision: 2}
 accounting.FormatMoney(1234567)
  • 38.
  • 39.
    panic • run-time error •Python의 Exception과 유사 • Python의 Exception 처럼 직접 발생시키는 것도 가능
 panic(“unsupported type”) • panic 발생시 적절하게 handling 하지 못하면 어플리케 이션이 중단됨.
  • 40.
    recover • defer -함수 종료 직전에 실행. defer를 여러번 호출하면 LIFO로 실 행 • First Class Function => 익명 함수 호출 가능 • func handler() {
 defer func() {
 if err := recover(); err != nil {
 // panic handling
 } 
 }()
 …
 }
  • 41.
  • 42.
    package • 소스코드 첫줄에package명 명시
 package accounting • Python의 module 과 다르게 파일명은 아무런 역할도 하지 않는다. • 패키지 내부에 있는 함수, 변수, 상수들은 아래 규칙에 따라 역할이 결정됨. • 첫 글자가 대문자이면 외부에서 import시 접근 가능 • 첫 글자가 소문자이면 package 내부에서만 접근 가능 • 심지어 struct의 경우에는 struct 이름 첫글자가 대문자여도 필드 이름이 소문자이 면 외부에서 접근 못함. 외부에서 접근이 필요한 필드들은 대문자를 사용해야 한다.
 type Timedelta struct {
 Days, Seconds, Microseconds, Milliseconds, Minutes, Hours, Weeks time.Duration
 }
  • 43.
    package 공유 • github에 repo 생성시 아래 명령어로 바로 패키지 다운로드 및 설치 가능 (bitbucket 등 다른 저장소도 지원)
 go get github.com/leekchan/accounting • 처음에는 좋아 보였으나, PyPI에 비해 단점이 많음. • 무조건 master branch 최신 커밋 소스를 다운로드 • 기능 개발 및 테스트 후 원하는 시점에 versioning을 하고 릴리즈 할 수가 없음 • 해당 단점을 보완하는 다양한 오픈소스 라이브러가 생겨나 고 있는 중
  • 44.
  • 45.
    gofmt • formatting을 styleguide에 맞춰서 알아서 해주는 gofmt 라는 기능을 bult-in 으로 제공 • 덕분에, 일단 코드를 쓰고 커밋하기 전에 gofmt을 한번 실행시켜 주면 style guide에 맞춰서 아름답게 formatting 이 됨. • Python에서 PEP 8 준수를 위해 많은 linter들이 있지만 Go에서는 이미 built-in 기능인 gofmt가 끝판왕.
  • 46.