반응형

 

Map 은 key, value 로 저장할 수 있는 타입이다. HashMap 을 사용하기 때문에 key 는 유일한 값으로 key 를 사용하면 value 를 바로 찾아서 쓸 수 있다.

앞서 slice 와 동일하게 zero 값은 nil 이다. 그러므로 map 변수를 선언만 하면 nil 값으로 비교할 수 있다. slice 는 nil 값이라도 append 를 사용하면 값을 추가하면서 새로운 slice 가 생성되어 return 되었다. 하지만 map 은 nil 값에는 어떠한 key, value 도 추가할 수 있다.

 

Map

map 은 key, value 쌍으로 이루어진 데이터 구조이다. 파이썬에서는 dictionary 와 비슷하다고 생각하면 된다. 단지 다른 것은 하나의 map 에는 사전에 지정된 타입의 key, value 만 저장될 수 있다.

다음은 string 타입을 key 로하고 int 를 value 로 저장하는 map 을 선언한 것이다. map[key타입]value타입 으로 선언할 수 있다.

이렇게 선언만 한 map 변수의 zero 값은 nil 이다.

var m map[string]int
fmt.Println(m == nil)

--- output ---
true

 

이렇게 nil 값이 상태의 map 에는 key, value 를 추가할 수 없다(slice 와는 다르다).

var m map[string]int
m["one"] = 0

--- output ---
panic: assignment to entry in nil map

 

아래와 같이 empty map 을 생성할 수 있으며, 여기에는 key, value 를 추가할 수 있다.

var m1 = map[string]int{}
fmt.Println(m1 == nil)
m1["one"] = 0
fmt.Println(m1)

--- output ---
false
map[one:0]

 

초기 값을 아래와 같이 생성할 수 도 있다. 초기화 할 때 key, 와 value 는 : 로 구분하고 연속적으로 key, value 를 입력하기 위해서는 , 구분자를 사용한다.

m2 := map[string]int{
    "one":  1,
    "two": 2,
}
fmt.Println(m2)

--- output ---
map[one:1 two:2]

 

make 함수

slice 와 동일하게 make 내장 함수를 사용하여 empty map 을 생성할 수 있다. 이렇게 생성된 map 은 길이는 0, 사이즈는 5 가 되고, 길이가 사이즈 보다 커질 때에도 사이즈가 확장되어 key, value 를 추가할 수 있다. (사이즈 확장은 때에 따라 성능 감소의 요인이 될 수 있다)

출력 결과와 같이 map 에 추가된 key, value 는 순서가 없다.

m := make(map[string]int, 5)
fmt.Println(len(m))

m["one"] = 1
m["two"] = 2
m["three"] = 3
m["four"] = 4
m["five"] = 5
fmt.Println(m, len(m))

m["six"] = 6
fmt.Println(m, len(m))

--- output ---
0
map[five:5 four:4 one:1 three:3 two:2] 5
map[five:5 four:4 one:1 six:6 three:3 two:2] 6

 

comma ok idiom

map 에서 없는 key 값으로 value 를 요청하면 value 타입의 zero 값이 리턴된다. 즉 에러가 발생하지 않는다.

m := map[string]int{
    "one": 1,
    "two": 2,
}
fmt.Println(m["three"])

--- output ---
0

 

그렇기 때문에 해당 key 값의 value 가 제대로 넘어왔는지 알 수 있게 , 로 구분되어 value 와 boolen 값이 리턴된다.

m := map[string]int{
    "one": 1,
    "two": 2,
}

v, ok := m["one"]
fmt.Println(v, ok)

v, ok = m["three"]
fmt.Println(v, ok)

--- output ---
1 true
0 false

 

map 의 item 을 삭제하려면 delete 내장 함수 를 사용한다. delete 함수에 첫번째 아규먼트에는 map 을, 두번째 아규먼트에는 key 를 넣으면 된다. 해당 key 가 map 에 없다고 해서 에러를 만들지는 않는다.

m := map[string]int{
    "one": 1,
    "two": 2,
}

delete(m, "one")
fmt.Println(m)
delete(m, "three")

--- output ---
map[two:2]

 

map 의 key 는 중복될 수 없다. key, value 를 추가하는데 기존의 map 에 key 가 존재한다면, key 에 해당 하는 value 값이 업데이트 된다.

Go 에는 Set 타입이 없는데 참고로 key 가 중복될 수 없다는 이 특징을 잘 활용하면 Set 을 구현할 수 있다.

m := map[string]int{
    "one": 1,
    "two": 2,
}
m["two"] = 3
fmt.Println(m)

--- output ---
map[one:1 two:3]

 

 

반응형
Posted by seungkyua@gmail.com
,
반응형

 

 

기본 내장 타입에 추가하여 Array, Slice, Map 타입에 대해서 살펴본다.

Go 에서는 Array 는 거의 사용하지 않는다. 그러므로 이번에는 Slice 와 Map 타입에 대해서만 살펴 본다.

참고로 Go 에서는 nil 이라는 값이 있다. 이는 값이 없다라는 뜻인데, 기본 타입에는 이 nil 과의 비교문 (==)을 쓸 수 없다. (이전에 기본 타입은 전부 zero 값이 nil 이 아니다.)

**nil 과 비교문이 가능한 타입은 앞으로 설명할 slice, map, pointer 이며, 이 3개의 zero 값이 nil 이다.**

 

Slice

slice 는 값을 순차적으로 가질 수 있는 데이터 구조이다. 파이썬에서는 list 와 비슷하다고 생각하면 된다. 단지 다른 것은 하나의 slice 에는 사전에 지정된 타입만 저장될 수 있다.

다음은 int 타입을 slice 저장 한 후에 출력한 결과이다. [] 는 slice 를 만들겠다는 의미이고, 바로 이어서 type 이 오며, 초기값을 지정하기 위해서 { } 를 사용한다.

var s = []int{1, 2, 3, 4, 5}
fmt.Println(s)

--- output ---
[1 2 3 4 5]

 

slice 는 초기화 값을 지정하지 않으면 nil 을 나타낸다. 즉, 변수 선언만 하면 항상 nil 값을 갖게 된다. lencap 을 사용하면 slice 가 가지가 있는 길이와 용량을 알 수 있다.

Slice 는 연속적인 값을 가지고 있는 메모리 주소인 포인터, 길이, 용량 으로 구성되어 있다. (이 부분은 매우 중요하다.)

길이는 실제 들어있는 값이고 용량은 해당 값을 가질 수 있는 크기라고 할 수 있다.

아래 s1 은 nil 값이기 때문에 len 과 cap 이 모두 0 이다.

var s1 []int
fmt.Println(s1 == nil)
fmt.Println(len(s1), cap(s1))

--- output ---
true
0 0

 

슬라이스는 배열처럼 사용하여 s1[0] 첫번째 값을 가져올 수 있다.

하지만 선언만 하여 nil 인 상태에서는 해당 index 를 사용하게 되면 런타임 에러가 난다.

var s1 []int
fmt.Println(s1[0])

--- output ---
panic: runtime error: index out of range [0] with length 0

 

s1 slice 가 nil 인 상태에서도 append 내장 함수를 사용하면 값을 추가할 수 있다(그렇기 때문에 특별한 경우를 제외하고는 nil 값인 변수 선언만 하여 사용하면 된다). append 함수를 쓰면 nil 이라 하더라도 slice 에 값을 추가 할 수 있다. 물론 값이 추가된 결과 return 값은 변수에 할당해야 한다. (변수가 달라도 됨)

var s1 []int
s1 = append(s1, 1)
fmt.Println(s1)

--- output ---
[1]

 

slice 에 slice 를 합쳐서 추가할 수 있다. python 의 extend 와 비슷하다고 보면된다. 두번째 slice 에는 여러 아규먼트가 들어간다는 것을 표현해 주기 위해서 ... 을 붙혀야 한다.

var s1 []int
s1 = append(s1, 1)

s2 := []int{2, 3, 4, 5}
s1 = append(s1, s2...)  // append(s1, 2, 3, 4, 5) 와 동일함
fmt.Println(s1)

 

slice 는 값이 추가되어 길이가 커져서 용량에 도달하면 용량이 자동으로 확장된다.

var s []int
fmt.Println(s, len(s), cap(s))
s = append(s, 1)
fmt.Println(s, len(s), cap(s))
s = append(s, 2)
fmt.Println(s, len(s), cap(s))
s = append(s, 3)
fmt.Println(s, len(s), cap(s))
s = append(s, 4)
fmt.Println(s, len(s), cap(s))
s3 := append(s, 5)
fmt.Println("s:", s, len(s), cap(s))
fmt.Println("s3", s3, len(s3), cap(s3))

-- output ---
[] 0 0
[1] 1 1
[1 2] 2 2
[1 2 3] 3 4
[1 2 3 4] 4 4
s: [1 2 3 4] 4 4
s3 [1 2 3 4 5] 5 8

 

 

make 내장 함수

slice 와 map 에서 make 내장 함수를 사용하면 nil 이 아닌 empty slice 를 만들 수 있다. make 함수의 첫번째 아큐먼트는 slice type, 두번째는 길이, 세번째는 용량이다. 세번째 용량은 생략이 가능하다.

아래 make 로 생성한 s 는 nil 과의 비교가 false 임을 알 수 있다.

s := make([]int, 0, 5)
fmt.Println(s == nil)
fmt.Println(s, len(s), cap(s))

--- output ---
false
[] 0 5

 

make 로 길이를 지정하면 그 길이 만큼은 zero 값이 채워진 slice 가 만들어 진다. 그러니 필요없이 길이를 1 이상으로 지정하면 초기 메모리만 차지할 수 있다. 아래는 make 로 만든 s1 slice 에 0번째와 1번째 index 까지 0 값이 채워진 것을 볼 수 있다.

s1 := make([]int, 2)
fmt.Println(s1)

s1 = append(s1, 1)
fmt.Println(s1)

--- output ---
[0 0]
[0 0 1]

 

길이가 0 인 slice 는 아래와 같이 만들 수 도 있다. nil 이 아니면서 길이가 0 인 slice 는 JSON 으로 변환할 때 사용된다.

var s = []int{}   // make([]int, 0) 와 동일하다.

 

 

Slice 자르기

Slice 를 자르는 것은 : 을 이용해서 자를 수 있다. 하지만 자른다고 해도 최종적으로 보는 Reference 는 동일하다. 아래를 보면 이를 이해할 수 있다.

첫번째 단락에서 slice 를 자르면, b 는 0 index 이상, 3 index 미만 까지의 slice 를 갖고, c 는 2 index 이상 끝까지의 slice 값을 갖는다. 중요한 것은 c 처럼 중간에서 마지막까지 자른 slice 는 용량이 그 길이만큼으로 줄어든다는 것이다. (앞의 자른 부분이 없어졌기 때문이다)

두번째 단락에서 c[0] 에 30 을 대입하면 slice 자른 것은 같은 reference 를 바라보기 때문에 a 와 b 에 모두 영향을 준다.

세번째 단락도 마찬가지 이다.

a := []int{1, 2, 3, 4, 5}
b := a[:3]
c := a[2:]
fmt.Println("cap(a):", cap(a), "cap(b):", cap(b), "cap(c):", cap(c))
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("================")

c[0] = 30
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("================")

b = append(b, 40)
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)

--- output ---
cap(a): 5 cap(b): 5 cap(c): 3
a: [1 2 3 4 5]
b: [1 2 3]
c: [3 4 5]
================
a: [1 2 30 4 5]
b: [1 2 30]
c: [30 4 5]
================
a: [1 2 30 40 5]
b: [1 2 30 40]
c: [30 40 5]

 

Slice 를 자른 것이 append 를 사용해서 추가할 때 같은 레퍼런스를 처다보지 않게 하려면, 용량을 길이와 동일하게 하면 된다. 그러면 append 했을 경우 용량이 초과되어 새로운 slice 가 생겨서 return 되므로 서로 영향이 가지 않는다.

아래 처럼 a 를 잘라서 b 를 만들 때 :3:3 과 같이 마지막에 용량 3을 추가하여 b slice 의 길이와 용량을 똑같이 맞췄다. 그렇게 되면 c[0] 처럼 값을 바꾸는 것은 영향을 받지만, append(b, 40) 과 같이 append 한 것은 새로운 slice 가 만들어져서 return 되므로 a 와 c 에 영향을 주지 않는다.

a := []int{1, 2, 3, 4, 5}
b := a[:3:3]
c := a[2:]
fmt.Println("cap(a):", cap(a), "cap(b):", cap(b), "cap(c):", cap(c))
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)

fmt.Println("===== (c[0] = 30) =====")
c[0] = 30
fmt.Println("cap(a):", cap(a), "cap(b):", cap(b), "cap(c):", cap(c))
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)

fmt.Println("===== append(b, 40) ======")
b = append(b, 40)
b[0] = 10
fmt.Println("cap(a):", cap(a), "cap(b):", cap(b), "cap(c):", cap(c))
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)


--- output ---
cap(a): 5 cap(b): 3 cap(c): 3
a: [1 2 3 4 5]
b: [1 2 3]
c: [3 4 5]
===== (c[0] = 30) =====
cap(a): 5 cap(b): 3 cap(c): 3
a: [1 2 30 4 5]
b: [1 2 30]
c: [30 4 5]
===== append(b, 40) ======
cap(a): 5 cap(b): 6 cap(c): 3
a: [1 2 30 4 5]
b: [10 2 30 40]
c: [30 4 5]
c: [30 4 5]
===== append(b, 40) ======
a: [1 2 30 4 5]
b: [1 2 30 40]
c: [30 4 5]

 

반응형
Posted by seungkyua@gmail.com
,
반응형

 

 

C 와 Java 를 안다면 Go 를 공부하면 헷갈리는 부분들이 다소 있다. 일반적으로 기본 내장 타입은 비슷해서 이해하기 쉽지만 pointer 와 interface 등을 공부하다 보면 헷갈리기 마련이다.

일단 먼저 기본 타입에 대해서 설명한다.

 

Built-in Type

기본 내장 타입에는 boolean, integer, rune, float, byte, string 이 있다. 물론 complex type 도 있지만 이는 생략하겠다. 기본 내장 타입은 zero 값을 갖고 있다.

이 중에서 숫자 타입은 integer, rune, float 이다.

 

boolean

boolean 의 zero 값은 false 이다.

var isDev bool
var isTrue = true

fmt.Println(isDev)
fmt.Println(isTrue)

--- output ---
false
true

변수 선언은 var 변수명 타입 으로 선언할 수 있다. 이렇게 설명하면 zero 값으로 false 가 자동 대입된다.

다른 방법은 var 변수명 = 값 과 같이 변수를 선언하면서 값을 대입하는 것이다. 그럼 값을 보고 알아서 타입을 결정해 준다.

 

Integer (숫자 타입 중 하나)

Integer type 은 int8, int16, int32, int64, uint8, uint16, uint32, uint64 이다. 그리고 특별한 integer 타입으로 byterune 타입이 있다.

byteuint8 의 별칭으로 unit8 보다는 byte 를 주로 쓰게 된다.

runeint32 의 별칭이다.

보통 Integer Type 은 int 로 많이 사용하는데, 32bit cpu 이면 int32, 64bit cpu 이면 int64 와 동일하다.

int 의 zero 값은 0 이다.

var x int
var y int = 1
var z = 2
fmt.Println(x)
fmt.Println(y)
fmt.Println(z)

--- output ---
0
1
2

 

Float (숫자 타입 중 하나)

Float type 은 float32, float64 이 있다.

float type 의 zero 값은 int 와 동일하게 0 이다.

var p float64
var q = 0.0
fmt.Println(p)
fmt.Println(q)

--- output ---
0
0

숫자 타입 간에는 편하게 해당 타입으로 변환이 가능하다.

var x int = 10
var p float64 = 20.2
fmt.Println(x + int(p))
fmt.Println(float64(x) + p)

--- output ---
30
30.2

 

String

string 의 zero 값은 empty string 이다. Java 에서는 null string 이 있지만 Go 에서는 null 이 아니다.

string 과 자주 사용되는 타입은 runebyte 이다.

앞에서 숫자 타입은 서로 타입 변환이 해당 타입으로 감싸주면 된다고 설명했는데 string, rune, byte 도 서로 타입 변환이 가능하다.

Go 최신 버전에서는 숫자 타입을 단순히 string 으로 감싸는 것은 체크 시에 에러가 발생한다.

var r rune = 'r'
var s string = string(r)
var b byte = 'b'
var s2 string = string(b)
fmt.Println(r)
fmt.Println(b)
fmt.Println(s)
fmt.Println(s2)

--- output ---
114
98
r
b

runebyteint 타입의 별칭이라 숫자 값이 출력된 것을 이해하자.

나중에 설명하겠지만 [] 를 사용하여 slice (배열과 비슷한) 타입을 활용하면 string[]byte 혹은 []rune 으로 변환할 수 있다.

var s string = "Hello, 😀"
var bs []byte = []byte(s)
var rs []rune = []rune(s)
fmt.Println(bs)
fmt.Println(rs)

--- output ---
[72 101 108 108 111 44 32 240 159 152 128]
[72 101 108 108 111 44 32 128512]

😀 이모지 값은 utf-8 으로 표시해야 되기 때문에 byte 는 [240 159 152 128] 와 같이 4 바이트로 표시되고 rune 은 [128512] 로 표시된다. byte 는 int8 이고, rune 은 int32 임을 기억하자.

이렇게 보면 string 은 rune 으로 변환하는 것이 편리하다.

마지막으로 Short Variable Delcaration Operator(:=) 를 사용하여 vartype 키워드 없이 변수를 선언하고 바로 할당할 수 있다.

var a, b int = 1, 2
var c, d = 3, "a"

i := 10
x, s := 20.5, "Hello World"

--- output ---
1 2 3 a
10 20.5 Hello World

 

 

반응형
Posted by seungkyua@gmail.com
,
반응형

 

 

Kubernetes source code 를 디버깅하면서 분석하기 위해 golang 환경 설정과 사용을 계속해서 설명하고자 한다.

 

오늘은 첫번째로 무료 IDE 인 (thx MS) VSCode 를 설치하고 세팅하는 법을 알아보자.

golang 설치

mac 에서 golang 은 home brew 로 간단히 설치할 수 있다. 혹시 라도 이전에 낮은 버전이 설치되어 있는지 확인한다.

$ brew list | grep go
$ go@1.12

$ go version
go version go1.12.9 darwin/amd64

새로운 버전을 install 한다.

$ brew install go@1.16

현재 1.12 버전을 unlink 한다.

$ brew unlink go

새로운 버전을 link 한다.

$ brew link --force go@1.16

새로운 버전을 check 하면 업그레이드 되어 있는 것을 알 수 있다.

$ go version
go version go1.16.14 darwin/amd64

Go Workspace

go 는 install 명령어로 다른 go tool 들을 설치할 수 있다. 설치 위치는 workspace 라고 부르는 곳인데 특별히 지정하지 않으면 디폴트로 $HOME/go 디렉토리에 설치 된다.

 

go install 로 설치된 툴들은 소스는 $HOME/go/src 에, 바이너리 실행파일은 $HOME/go/bin 에 설치된다.

 

workspace 는 변경할 수 있는데 아래와 같이 .zshrc (zsh 일 경우) 이나 .bash_profile (bash 일 경우) 파일에 환경 변수를 추가하면 된다. 설정하기 전에 해당 workspace 디렉토리는 미리 만들어 줘야 한다.

## 디렉토리 만들기
$ mkdir -p $HOME/Documents/go_workspace

## .zshrc 파일에 환경변수 세팅
export GOPATH=$HOME/Documents/go_workspace
export PATH=$PATH:$GOPATH/bin

Go Tool 설치

코드를 체크해 주는 golint 툴이 있다. 이를 설치해 보자

$ go install golang.org/x/lint/golint@latest

$ which golint
/Users/ahnsk/Documents/go_workspace/bin/lint

특정 디렉토리에서 lint 체크를 하고 싶으면 아래 명령어를 쓰면 된다. 코드를 만든 프로젝트 디렉토리에서 수행하면 모든 파일을 lint 한다.

$ golint ./...

그리고 go 를 설치하면 일부 tool 들은 자동으로 설치되는데 그 중 vet 이라는 유용한 tool 있다. vet 은 error 를 잡아주는 tool 이다.

$ go vet ./...

VS Code 설치

VS Code 는 아래 url 에 접속해서 OS 별로 설치하면 된다. 설치 화면은 생략한다.

 

https://code.visualstudio.com/Download

 

VS Code 를 띄우고 나서 아무 화면에서나 Cmd+Shift+P 키를 입력하면 Command Palette 가 화면 위에 생성된다. 거기에서 shell command 로 검색하여 install ‘code’ command in PATH 를 선택한다.

 

 

이렇게 설치한 후 터미널을 새롭게 띄워서 특정 디렉토리에서 code 명령어를 이력하면 VS Code 가 실행된다.

 

아래와 같이 go-sample 디렉토리에서 실행하면 VS Code 가 뜨는 것을 볼 수 있다.

$ cd go-sample
$ code

VS Code Extension 설치

VS Code 는 다양한 Extension 을 설치할 수 있다. 그 중에 go 를 써야 하니 Go extension 을 설치해 보자.

 

VS Code 화면에 보면 맨 좌측에 세로로 아이콘들이 여러개 있다. 그 중 위에서 5번째 혹은 6번째에 있는 extension 아이콘을 선택하고 go 를 검색해서 go Team at Google 에서 만든 Go 를 설치해 준다.

 

아래 그림에서 맨 처음 줄의 Go 의 install 파란 버튼을 클릭해서 설치한다.

 

 

이제 기본 설치는 끝났다.

 

hello world 를 출력하는 간단한 소스를 만들어서 실행해 보자.

 

VS Code 에서 새로운 파일을 생성해서 main.go 파일을 만들어서 저장한다.

package main

import "fmt"

func main() {
	fmt.Println("Hello world!!!")
}

VS Code 상단 메뉴에 Terminal >> New Terminal 을 클릭하면 터미널을 VS Code 에서 사용할 수 있다.

$ go run main.go
Hello world!!!

VS Code 화면을 보면 다음과 같다.

 

 

이제 세팅은 모두 끝났다. 디버깅 세팅하는 방법이 있는데 go 를 설명하다가 어느 정도 지나면 디버깅 세팅도 추가로 올릴 예정이다.

반응형
Posted by seungkyua@gmail.com
,
반응형
.
├── config
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-39.pyc
│   │   ├── settings.cpython-39.pyc
│   │   ├── urls.cpython-39.pyc
│   │   ├── views.cpython-39.pyc
│   │   └── wsgi.cpython-39.pyc
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   ├── views.py
│   └── wsgi.py
├── db.sqlite3
├── firstapp
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-39.pyc
│   │   ├── urls.cpython-39.pyc
│   │   └── views.cpython-39.pyc
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
└── manage.py

위와 같이 django 디렉토리가 있을 때 package, module import 하는 방법이 헷갈려서 정리하는 차원이다.

 

바로 하위 디렉토리 config 와 firstapp 은 패키지 이다.

그리고 config 아래의 urls.py 와 같은 python 파일은 모듈이다.

 

import 방법은 다음과 같다.

 

 

import {패키지}.{모듈}

import {패키지}.{모듈}.{함수}

 

from {패지키} import {모듈}

from {패키지}.{모듈} import {함수}

 

 

 

 

 

반응형
Posted by seungkyua@gmail.com
,