Language/Golang

Golang (Go언어) 패키지(Package)

HeeWorld 2024. 11. 9. 00:17

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

 

Golang 마스코트 Gopher(고퍼)

 

패키지(Package)

- Go 언어에서 코드를 묶는 가장 큰 단위

- 다른 언어에서는 네임스페이스(namespace)라는 키워드를 사용해서 코드 영역을 분리하기도 하지만, 

   Go언어에서는 네임스페이스를 지원하지 않고 패키지를 사용함.

- 프로그램은 main 패키지(필수 요소) 하나와 여러 외부 패키지(선택 요소)로 구성됨.

 

main 패키지

- 특별한 패키지 프로그램 시작점을 포함한 패키지로 프로그램 시작점이란 main() 함수를 의미

 

그 외 패키지

- 한 프로그램은 main 패키지 외에 다수의 다른 패키지를 포함할 수 있음

- 표준 입출력은 "fmt" 패키지를, 암호화 기능은 "crypto" 패키지를, 네트워크 기능은 "net" 패키지를 임포트(import)해 사용

 

유용한 패키지 찾기

- 아래 사이트를 통해 표준 패키지에서 같은 기능을 제공하는지 확인

    → https://golang.org/pkg/ 

- 만약 표준 패키지에서 제공하지 않는 기능이라면 공개된 외부 패키지에서 원하는 기능을 제공하는지 찾아보기

   → https://github.com/avelino/awesome-go

 

 

패키지 사용하기

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println(rand.Int())
}

// 결과

1403345515016663023

 

math/rand (https://pkg.go.dev/math/rand@go1.23.3)

- math 안에 있는 rand라는 패키지를 사용

- rand 패키지의 Int()함수를 호출하면 랜덤한 int 타입 정수를 반환(유사 랜덤 값을 반환)

 

 

겹치는 패키지명

- 패키지 명이 겹치면 별칭(aliasing)을 줘서 구별

import (
   "text/template"    // template 패키지
   "html/template"    // 같은 이름의 template 패키지
)

 

- "text/template"과 "html/template" 패키지가 있을 때 이를 import하면 맨 마지막 명이 패키지가 됨 = template

 

✓ 패키지 명이 겹치는 경우 별칭을 사용하는데, 별칭은 패키지 명 앞에 사용하면 됨

import (
    "text/template"    // template 패키지
    htemplate "html/template"   // 별칭 htemplate
)

template.New("foo").Parse(`{{define "T"}}Hello`)
htemplate.New("foo").parse(`{{define "T"}}Hello`)

 

"text/template"

- template 패키지 함수를 호출

 

htemplate "html/template" 
- htemplate을 사용하여 패키지 함수 호출

 

 

사용하지 않는 패키지 포함하기

- 패키지를 가지고 오면 반드시 사용을 해야하는데, import하고 나서 사용하지 않으면 에러가 발생

- 패키지를 직접 사용하지 않지만 부가효과를 얻고자 import하는 경우 밑줄(_/빈칸지시자)을 패키지명 앞에 붙이면 됨

import (
    "databases/sql"
    _ "github.com/mattn/go-sqlite3"
)

 

 

Go 모듈

- Go 모듈은 Go 패키지들을 모아놓은 Go 프로젝트 단위이며, Go 1.16 버전부터 모듈 사용이 기본이 됨

- Go 코드는 모두 GOPATH/src 폴더 아래 있어야 했지만, 모듈이 기본이 되면서 모둔 Go 코드는 Go 모듈 아래 있어야 함

 

★ Go build를 하려면 반드시 Go 모듈 루트 폴터에 go.mod 파일이 있어야 함!! ★

 

- go.mod 파일은 모듈 이름과 Go 버전, 필요한 외부 패키지 등이 명시되어 있음

- Go 모듈은 go mod init 명령을 통해 만들 수 있음

go mod init [패키지명]

 

 

Go 모듈을 만들고 외부 패키지 활용하기

- VSCode를 사용하여 실습!

 

1. goproject/usepkg 폴더 생성

 

2. usepkg/custompkg 폴더 생성

 

3. custompkg.go 작성

- func만 한 개 가지고 있는 패키지

package custompkg //실행 시작점을 포함하고 있지 않음(main이 아님), 보조 패키지

import "fmt"

func PrintCustom() {
	fmt.Println("This is custom package!")
}

 

4. usepkg/program 폴더 생성

 

5. usepkg.go 작성

package main //main 프로그램, 실행 시작점

import (
	"Golang/usepkg/custompkg"
	"fmt"

	"github.com/guptarohit/asciigraph"
	"github.com/tuckersGo/musthaveGo/ch14/expkg"
)

func main() {
	custompkg.PrintCustom()
	expkg.PrintSample

	data := []float64{3, 4, 5, 6, 9, 7, 5, 8, 5, 10, 2, 7, 2, 5, 6}
	graph := asciigraph.Plot(data)
	fmt.Println(graph)
}

 

6. go mod init goproject/usepkg

- 강의에서는 goproject라는 디렉터리를 사용하고 있으나, 나는 Golang이라는 디렉터리를 만들었었음 (각자 만든 디렉터리 경로 사용)

모듈 생성

 

- 모듈을 생성하고 나면, go.mod라는 파일이 생기는데 그 파일을 열면 모듈명과 Go 버전이 적혀있음 (아래 참고)

module Golang/usepkg

go 1.23.2

프로젝트 폴더 구조

→ custompkg와 program은 usepkg 모듈에 포함되어 있으며 반대로 usepkg는 custompkg와 program 패키지를 가지고 있음

 

✓ 같은 경로의 패키지에서는 import 등을 하지 않고 아래와 같이 사용할 수 있음 = 같은 패키지이기 때문

    - 경로가 달라지면 패키지도 달라짐

package custompkg

func print2() {
   PrintCustom()
}

 

7. go mod tidy

- 해당 명령어 수행 시, 해당 모듈이 사용하고 있는 외부 패키지를 전부 다운로드함

외부 패키지 다운로드

- 명령어 수행 이후 go.sum이라는 파일이 생성되는데 이는 다운받은 패키지들의 정보를 가지고 있음 

패키지 정보

- 버전과 hash code(파일 완결성 체크하는 checksum) 등의 정보 확인 가능

 

✓ 다운받은 패키지는 "go env"를 사용하면 go 설정들 중에 GOPATH를 확인할 수 있는데 해당 경로 아래 다운됨

    - 한 번 다운로드 받은 패키지는 다른 프로그램에서도 공유해서 함께 사용할 수 있음

패키지 경로

8. program build

- program 디렉터리에서 "go build" 명령어를 사용하여 실행파일 생성

- 실행파일 실행하면 아래와 같이 출력되는 것을 확인할 수 있음

실행파일 결과

 

- 처음에 custompkg.PrintCustom()을 호출하는데, 해당 패키지는 "goproject/usepkg/custompkg" 경로에 존재

   → PrintCustom을 호출하니 "This is custom package!"가 출력됨

 

- 두 번째로 expkg.PrintSample()은 github.com/tuckersGo/~ 의 패키지 안에 속한 PrintSample 함수를 호출한 것

   → PrintSample을 호출하니 "This is Github expkg Sample"이 출력됨

 

- 마지막으로, data 값을 넣고 aciigraph.Plot을 호출하는데, github.com/guptarohit/~ 의 패키지 Plot을 호출하고 그 결과를 출력함

   → data에 넣은 값을 가지고 그래프를 출력해줌 

 

 

패키지 외부 공개

- 패키지 내 타입, 전역변수, 상수, 함수, 메서드는 패키지 외부로 공개 됨

- 구조체 이름의 첫 글자가 대문자이고 필드명 역시 첫 글자가 대문자인 구조체의 필드도 패키지 외부로 공개됨

 

1. 위에서 만들었던 custompkg.go에 새로운 func을 작성

package custompkg //실행 시작점을 포함하고 있지 않음, 보조 패키지

import "fmt"

func PrintCustom() {
	fmt.Println("This is custom package!")
}

func printCustom2() {
	fmt.println("This is custom package22222!")   //새로운 내용
}

 

2. usepkg.go에서 해당 func을 추가하고 다시 build하면 에러가 발생함

   → custompkg.printcustom2는 외부로 공개되지 않아서 사용할 수 없음 (cannot refer to unexported name ~) 

 

✓ 외부로 공개된 것인지 아닌지는 맨 앞글자가 소문자면 공개되지 않고, 대문자로 사용시 공개 됨

 

 

패키지 초기화

- 패키지가 프로그램에 포함되어 초기화 될 때, 패키지내 init() 함수가 있으면 한 번만 호출

- init()를 통해 패키지 내 전역 변수를 초기화 함

 

1. usepkg 아래 exinit 디렉터리 생성

 

2. exinit.go 생성

package exinit

import "fmt"

var (
	a = c + b
	b = f()
	c = f()
	d = 3
)

func init() {
	d++
	fmt.Println("exinit.init function", d)
}

func f() int {
	d++
	fmt.Println("f() d:", d)
	return d
}

func PrintD() {
	fmt.Println("d:", d)
}

 

3. usepkg.go 파일 내용 수정

package main //main 프로그램, 실행 시작점

import (
	"fmt"
	"Golang/usepkg/custompkg"
	"Golang/usepkg/exinit"
)

func main() {
	custompkg.PrintCustom()
	exinit.PrintD()
}

 

4. 다시 go build한 후 실행파일 실행 

실행 과정 및 결과

 

<출력 결과 실행 순서(초기화)>

1. 어떤 패키지를 import하면 패키지를 초기화 함(한 번만)

 

2. exinit.go에 있는 변수들이 다 초기화되고, a = c + b는 a는 c값과 b값을 더한 값이 되고, c값이 먼저 정해지는데 c는 f() 함수를 실행해야 함

  f() 함수가 실행되어 d++로 d값이 4가 되고, 4를 리턴함, 그래서 c값은 4가 됨

  b()값은 다시 f()함수를 호출하고, f()가 실행되어 d++로 d값이 5가 되어, 5를 리턴함, 그래서 b는 5가 됨

  a의 값은 c+b였으니 5+4=9, a의 값은 9가 됨

 

3. 그 다음에 변수가 초기화 되고, 만약 패키지 안에 init 함수가 있으면 실행하게 됨

   출력 결과를 보면 exinit.init을 실행하고, d++를 실행하여 d가 6이 됨

 

4. init 함수가 실행이 끝나면 패키지 초기화가 끝난 것 (패키지 안에 있는 모든 변수 값은 초기화하였고, init도 실행하였기에 패키지 초기화 끝남)

 

5. 패키지 초기화가 끝나서 main 함수가 실행됨

    그래서 custompkg.PrintCustom()과 exinit.PrintD()를 출력함.

 

만약 custompkg 패키지 안에서도 exinit 패키지를 import해서 PrintCustom()에 exinit.PrintD()라는 함수를 실행했다고 하면,

main 패키지에서는 "goproject/usepkg/custompkg"를 import하고, custompkg에서는 다시 "goproject/usepkg/exinit"를

import 했고, main 패키지에서 또 "goproject/usepkg/exinit"를 import 함.

 

다시 build해서 실행하면, 결과가 동일하게 출력됨 (d값은 2번 더 출력됨)

왜냐하면, init는 import될 때마다 호출되는게 아니고 한 번만 호출됨(패키지 초기화는 한 번만 진행)

맨 처음 import될 때 한 번만 호출됨!!!

 

서로가 서로를 import해도 에러가 발생됨, 서로 cycle이 들어가면 허용되지 않음

즉, 서로가 서로를 import할 수 없음!!!