Go 프로그래밍 입문: 파이썬 개발자를 위한 안내서

파이썬 개발자로 활동하면서 Go 언어를 프로그래밍 도구에 추가하고 싶다면, 이 글이 도움이 될 것입니다. 이 글에서는 Go 프로그래밍의 기초를 배우는 방법을 알려드립니다.
처음부터 시작하기보다는 파이썬 개발자로서 이미 알고 있는 지식을 바탕으로 Go의 문법과 개념을 익히는 데 도움을 드리겠습니다.
Go 시작하기: 설정 및 첫 프로젝트
Go 코드를 작성하기 전에, 개발 환경을 설정해 봅시다. Go를 설치하고 프로젝트를 만드는 과정은 파이썬의 다양한 환경 관리 도구에 비해 상대적으로 간단합니다.
Go를 설치하려면 다음 단계를 따르세요:
- golang.org/dl/ 사이트 방문
- 운영체제에 맞는 설치 프로그램 다운로드 (Windows, macOS, 또는 Linux)
- 설치 프로그램을 실행하고 설치 단계를 따름
- 터미널에서
go version
명령어로 설치 확인
참고: 이 튜토리얼의 코드 스니펫만 실행하려면 Go 플레이그라운드를 사용해도 됩니다.
간단한 프로젝트를 만들어 시작해 봅시다. Go는 파이썬처럼 가상 환경이 필요하지 않습니다. 대신 하나의 설치로 프로젝트 수준에서 종속성을 관리합니다.
프로젝트 디렉토리 생성:
$ mkdir hello-go
$ cd hello-go
Go 모듈 초기화:
$ go mod init hello
이렇게 하면 go.mod 파일이 생성됩니다. 이는 파이썬의 requirements.txt나 pyproject.toml과 비슷하지만 더 간단합니다. 커맨드 인수(hello)가 모듈 이름이 됩니다.
main.go 파일 생성:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go world!")
}
프로그램 실행:
$ go run main.go
터미널에 다음 출력이 표시됩니다:
Hello, Go world!
참고: Go에서 모든 프로그램은 패키지로 구성되며 일반적으로 main 패키지에서 실행을 시작합니다. 개념에 더 집중하기 위해 관련 코드만 보여드리겠습니다. 하지만 이 코드를 다음과 같은 일반적인 .go 파일에 넣을 수 있습니다:
package main
import "fmt"
func main() {
//YOUR CODE GOES HERE
}
1. 기본 문법과 변수
Go 프로그램의 기본 구조부터 시작해 봅시다:
모든 Go 파일은 패키지 선언으로 시작합니다. main
패키지는 특별한 패키지로, 실행 가능한 프로그램의 진입점입니다. import
문은 fmt
와 같이 서식 지정과 출력을 위한 패키지 등 필요한 패키지를 가져옵니다.
이제 변수 선언을 살펴봅시다:
// 타입을 명시적으로 선언하는 변수
var name string = "Gopher"
이 예제에서는 string
타입의 변수를 명시적으로 선언하고 있습니다. 파이썬과 달리 Go에서는 변수 타입을 지정해야 합니다.
하지만 타입을 추론할 수 있는 다음 구문도 사용할 수 있습니다:
// := 연산자를 사용한 타입 추론
age := 5
:=
연산자는 변수를 선언하고 초기화하며, Go가 값에서 타입을 추론하도록 합니다.
상수는 const
키워드를 사용하여 비슷하게 작동합니다:
// 상수 선언
const pi = 3.14159
variables.go 파일에서는 다음과 같이 작성할 수 있습니다:
package main
import "fmt"
func main() {
// 타입을 명시적으로 선언하는 변수
var name string = "Gopher"
// := 연산자를 사용한 타입 추론
age := 5
// 상수 선언
const pi = 3.14159
fmt.Println(name)
fmt.Println(age)
fmt.Println(pi)
}
변수를 사용하지 않고 선언할 수 없으므로, 여기서는 변수 값을 출력합니다.
Go는 정적 타입 언어로, 파이썬의 동적 타이핑과 상당히 다릅니다. 이는 모든 타입이 컴파일 시간에 확인되어 프로그램 실행 전에 오류를 잡는 데 도움이 됩니다.
2. 제어 구조
Go의 if 문을 살펴봅시다:
// if 문
score := 85
if score >= 90 {
fmt.Println("A grade")
} else if score >= 80 {
fmt.Println("B grade")
} else {
fmt.Println("Lower grade")
}
출력:
B grade
조건 주위에 괄호가 없지만 중괄호는 필수입니다. 조건은 불리언 표현식이어야 합니다.
이제 Go의 표준 for 루프를 살펴봅시다:
for n := 0; n < 4; n++ {
fmt.Println("Count:", n)
}
이 루프는 n
을 0으로 초기화하고, n < 4
인 동안 계속하며, 각 반복 후 n
을 증가시킵니다.
Count: 0
Count: 1
Count: 2
Count: 3
Go에는 별도의 while 루프가 없습니다. 대신 조건만 있는 for 문을 사용합니다:
// while 루프처럼 사용하는 for
sum := 1
for sum < 10 {
sum += sum
fmt.Println("Sum is now:", sum)
}
컬렉션의 요소를 반복하려면 range
키워드를 사용합니다:
// range로 반복하기
fruits := []string{"apple", "banana", "cherry"}
for index, fruit := range fruits {
fmt.Println(index, fruit)
}
출력:
0 apple
1 banana
2 cherry
range
함수는 인덱스와 값을 모두 제공합니다(파이썬의 enumerate()
와 비슷함). 값만 필요한 경우 빈 식별자 _
를 사용할 수 있습니다:
// 인덱스를 무시하기 위해 _를 사용
for _, fruit := range fruits {
fmt.Println(fruit)
}
3. 함수
Go에서는 다음과 같이 func
키워드로 함수를 선언할 수 있습니다:
func add(x int, y int) int {
return x + y
}
함수는 func main()
외부에 정의하고 선언된 함수를 main()
함수 내에서 호출해야 합니다.
매개변수 이름 뒤에 매개변수 타입이 오는 것을 주목하세요. 매개변수 목록 뒤에 반환 타입을 지정합니다. 매개변수가 같은 타입을 공유할 때는 단축형을 사용할 수 있습니다:
func subtract(x, y int) int {
return x - y
}
Go의 특징적인 기능 중 하나는 여러 값을 반환할 수 있다는 것입니다:
// 여러 값을 반환하는 함수
func divide(x, y float64) (float64, string) {
if y == 0 {
return 0, "division by zero"
}
return x / y, ""
}
이제 이 함수를 호출할 수 있습니다:
package main
import "fmt"
...
func main() {
result, message := divide(10, 2)
if message != "" {
fmt.Println("Error:", message)
} else {
fmt.Println("Result:", result)
}
}
출력:
Result: 5
또한 Go는 명명된 반환 값을 허용합니다:
func calculateStats(values []int) (min, max, sum int) {
if len(values) == 0 {
return 0, 0, 0
}
min = values[0]
max = values[0]
sum = 0
for _, v := range values {
if v < min {
min = v
}
if v > max {
max = v
}
sum += v
}
return // 네이키드 리턴
}
적절한 인수로 함수를 호출할 수 있습니다:
package main
import "fmt"
...
func main() {
values := []int{5, 8, 2, 10, 3}
min, max, sum := calculateStats(values)
fmt.Println("Min:", min)
fmt.Println("Max:", max)
fmt.Println("Sum:", sum)
}
출력:
Min: 2
Max: 10
Sum: 28
4. 오류 처리
Go의 오류 처리 방식은 파이썬의 예외와 다릅니다. try/except 블록 대신 Go 함수는 명시적으로 확인해야 하는 오류를 반환합니다:
// errors 패키지 가져오기
import "errors"
// 오류를 반환하는 함수
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
오류를 반환할 수 있는 함수를 호출할 때는 오류가 nil이 아닌지 확인합니다:
package main
import (
"errors"
"fmt"
)
// 오류를 반환하는 함수
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
// 오류 처리
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error occurred:", err)
} else {
fmt.Println("Result:", result)
}
}
예상대로 오류가 발생합니다:
Error occurred: division by zero
명시적인 오류 확인에 중점을 두는 것은 Go 코드를 견고하고 읽기 쉽게 만듭니다. 무언가 잘못되었을 때 어떻게 해야 하는지 생각하도록 강제합니다.
5. 데이터 구조
배열
Go의 배열은 고정 크기를 가집니다:
// 배열 선언 및 초기화
var colors [3]string
colors[0] = "Red"
colors[1] = "Green"
colors[2] = "Blue"
Go에서는 다음과 같이 배열을 선언하고 초기화할 수도 있습니다:
// 배열 리터럴
numbers := [5]int{1, 2, 3, 4, 5}
슬라이스
슬라이스는 파이썬 리스트와 유사한 동적 배열입니다:
// 슬라이스 생성
fruits := []string{"Apple", "Banana", "Cherry"}
append를 사용하여 슬라이스에 요소를 추가할 수 있습니다:
// 슬라이스에 요소 추가
fruits = append(fruits, "Date")
append는 새 슬라이스를 반환한다는 점에 유의하세요 - 기존 슬라이스를 직접 수정하지 않습니다.
다른 슬라이스나 배열에서 슬라이스를 생성할 수도 있습니다.
맵
맵은 파이썬 딕셔너리와 동등합니다:
// 맵 생성
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
값 접근, 추가, 업데이트는 예상대로 작동합니다:
// 맵 작업
fmt.Println(ages["Alice"]) // 25
ages["Charlie"] = 22 // 새 항목 추가
ages["Alice"] = 26 // 기존 항목 업데이트
키가 존재하는지 확인하는 것은 일반적인 작업입니다:
// 키가 존재하는지 확인
age, exists := ages["Dave"]
if exists {
fmt.Println("Dave's age:", age)
} else {
fmt.Println("Dave not in map")
}
출력:
Dave not in map
키-값 쌍을 제거하려면 다음을 사용합니다:
// 항목 삭제
delete(ages, "Bob")
6. 고루틴과 채널을 이용한 동시성
Go 런타임은 고루틴이라는 경량 스레드를 관리합니다:
// 고루틴 시작
go func() {
fmt.Println("Running in a goroutine")
}()
go 키워드는 새 고루틴을 시작합니다. 여기서는 익명 함수를 사용하고 있지만, 이름이 있는 함수도 고루틴에서 실행할 수 있습니다.
채널은 고루틴 간 통신에 사용됩니다:
// 채널 생성
ch := make(chan string)
채널에 데이터 전송:
// 채널로 전송
go func() {
ch <- "Hello from goroutine"
}()
채널에서 데이터 수신:
message := <-ch
fmt.Println(message)
이 접근 방식의 유용성을 보여주는 보다 완전한 예제입니다:
ch := make(chan string)
// 값을 생성하는 고루틴
go func() {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("Message %d", i)
}
close(ch) // 완료 시 채널 닫기
}()
// 값을 소비하는 메인 고루틴
for msg := range ch {
fmt.Println("Received:", msg)
}
range 루프는 채널이 닫힐 때까지 계속되므로 채널로 전송된 모든 값을 처리하기 쉽습니다.
Received: Message 0
Received: Message 1
Received: Message 2
Received: Message 3
Received: Message 4
마무리
Go는 강력한 타이핑, 네이티브 코드로의 컴파일, 우수한 동시성 지원을 통해 파이썬의 매력적인 대안을 제공합니다. 파이썬 개발자로서 Go의 단순성과 가독성에 중점을 두는 것이 상대적으로 배우기 쉽지만, 명시적인 타입 선언과 다른 오류 처리 접근 방식에 적응해야 할 수도 있습니다.
주요 내용:
- Go는 정적 타입이며, 컴파일되고, 단순성에 중점을 둡니다
- Go의 오류 처리는 반환된 오류 값으로 명시적입니다
- Go의 고루틴과 채널을 사용한 동시성 모델은 강력하고 직관적입니다
- 구문은 파이썬에 비해 최소화되었지만 고유한 방식으로 표현력이 있습니다
이 소개가 Go 프로그래밍을 시작하는 데 도움이 되길 바랍니다. 즐거운 코딩 되세요!