Menu

Golang Printf와 Sprintf: fmt 포맷 동사 정리

Go의 fmt 패키지가 값을 출력하고 형식화하는 방식: Println, Printf, Sprintf, Errorf의 차이, 포맷 동사 전체 표(%v, %+v, %d, %s, %q, %f, %T, %w 등), 너비, 정밀도, 채우기를 다룹니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

fmt 패키지에는 출력 함수 계열이 세 가지 있고, 계열마다 같은 세 가지 변형이 있습니다:

함수출력 대상형식
Print, Println, Printf표준 출력기본, 공백과 줄바꿈을 붙인 기본, 형식 문자열
Sprint, Sprintln, Sprintf반환되는 string같은 세 가지 스타일
Fprint, Fprintln, Fprintf모든 io.Writer(파일, 버퍼, HTTP 응답)같은 세 가지 스타일
Errorf반환되는 error형식 문자열, 감싸기용 %w 추가

Println은 피연산자 사이에 공백을, 끝에 줄바꿈을 붙입니다. Printf는 아무것도 붙이지 않으므로 \n을 직접 써야 합니다. Print는 둘 다 문자열이 아닌 피연산자 사이에만 공백을 넣는데, 이것이 꽤 헷갈리기 때문에 대부분의 코드는 Println이나 Printf를 씁니다.

포맷 동사

일반

동사출력출력 예시
%v기본 형식의 값{Ana 31 [admin]}
%+v필드 이름을 포함한 구조체{Name:Ana Age:31 Tags:[admin]}
%#v값의 Go 문법 표현main.User{Name:"Ana", Age:31, Tags:[]string{"admin"}}
%T타입main.User
%%퍼센트 기호 그대로%

디버깅할 때 쓸 것은 %+v입니다. 구조체 포인터는 주소가 아니라 &{...}로 출력됩니다. 맵은 키를 정렬해서 출력하므로, 맵 순회 순서가 무작위여도 출력은 안정적입니다.

정수

동사의미fmt.Sprintf(verb, 255)
%d10진수255
%b2진수11111111
%o8진수377
%O0o 접두사가 붙은 8진수0o377
%x / %X16진수, 소문자 또는 대문자ff / FF
%#x0x 접두사가 붙은 16진수0xff
%c그 코드 포인트의 문자ÿ
%q따옴표로 감싼 문자 리터럴'ÿ'
%U유니코드 형식U+00FF

실수

동사의미fmt.Sprintf(verb, 1234.5678)
%f10진수, 기본 소수점 6자리1234.567800
%.2f10진수, 소수점 2자리1234.57
%e과학적 표기법1.234568e+03
%g%e%f 중 짧은 쪽, 뒤쪽 0 없음1234.5678
%v%g와 같음1234.5678

%.2f는 실수의 정확한 이진 값을 반올림하므로 fmt.Sprintf("%.2f", 2.675)2.67입니다. 2.675에 가장 가까운 float64가 그보다 조금 작기 때문입니다. 돈을 실수로 형식화하지 마세요. 센트를 정수로 보관하세요.

문자열과 바이트

동사의미fmt.Sprintf(verb, "go\n")
%s문자열 그대로go와 줄바꿈
%q큰따옴표로 감싸고 이스케이프를 보여 줌"go\n"
%x각 바이트의 16진수676f0a
% x공백을 넣은 16진수67 6f 0a

[]byte%s를 쓰면 텍스트로 출력되고, %v를 쓰면 숫자([104 105])로 출력됩니다.

그 밖의 타입

동사타입출력
%tbooltrue 또는 false
%p포인터, 슬라이스, 맵, 채널, 함수0xc000012345 같은 주소
%werror (Errorf에서만)오류 메시지, 그리고 감싸기

너비, 정밀도, 채우기

%와 동사 사이에 플래그, 너비, 정밀도를 넣을 수 있습니다:

형태효과
%5d너비 5, 오른쪽 정렬(왼쪽을 공백으로 채움)
%-5d너비 5, 왼쪽 정렬
%05d0으로 채움
%.2f소수점 아래 2자리
%8.2f너비 8, 소수점 2자리
%.3s문자열에서 최대 3글자
%+d부호를 항상 표시
%*d다음 인자에서 너비를 가져옴

2.5%.0f를 쓰면 2가 출력된다는 점에 주의하세요. 여기서 Go는 오사오입(가장 가까운 짝수로 반올림)을 합니다. 문자열의 너비는 화면 칸이 아니라 룬 개수를 세므로, 한글, 한자, 이모지가 섞이면 표가 여전히 어긋날 수 있습니다. 길이가 제각각인 텍스트로 열을 맞추려면 text/tabwriter가 측정을 대신해 줍니다.

인자 인덱스

%[n]은 위치로 인자를 골라서 재사용할 수 있게 합니다:

fmt.Printf("%[2]s %[1]s\n", "world", "hello") // hello world
fmt.Printf("%d %[1]x %[1]b\n", 10)             // 10 a 1010

Errorf와 %w

fmt.Errorf는 형식 문자열로 error를 만듭니다. %w를 쓰면 다른 오류도 감싸므로, 호출자가 여전히 원래 오류를 감지할 수 있습니다:

호출자가 원인을 확인해야 할 수 있다면 %w를, 일부러 숨길 때는 %v를 쓰세요. Go 1.20부터 Errorf 호출 하나에 %w 동사를 여러 개 넣을 수 있습니다. 감싸기는 오류 처리 페이지에서 깊이 다룹니다.

String()으로 직접 형식 정하기

String() string 메서드가 있는 타입은 %v, %s, Println이 자신을 보여 주는 방식을 직접 정합니다:

%dString()을 거치지 않고 기반 숫자를 출력합니다. 오류 타입에서 그에 해당하는 메서드는 Error() string이며, String()보다 우선합니다.

동사가 틀렸을 때

fmt는 잘못된 형식에 절대 패닉을 일으키지 않습니다. 문제를 그 자리에 출력합니다:

fmt.Printf("%d\n", "oops")
fmt.Printf("%d %d\n", 1)
fmt.Printf("%d\n", 1, 2)
%!d(string=oops)
1 %!d(MISSING)
1
%!(EXTRA int=2)

아무것도 죽이지 않기 때문에 이런 출력은 운영 환경까지 가기 쉽습니다. go vet은 빌드할 때 셋 모두 잡아 줍니다:

./main.go:8:2: fmt.Printf format %d has arg "oops" of wrong type string
./main.go:9:2: fmt.Printf format %d reads arg #2, but call has 1 arg
./main.go:10:2: fmt.Printf call needs 1 arg but has 2 args

성능 참고

fmt는 모든 인자를 any로 받아 런타임에 타입을 살펴보므로(구조체, 슬라이스, 맵은 리플렉션으로 처리), 로깅과 출력에는 괜찮지만 빡빡한 반복문에서는 비용이 눈에 띕니다. 숫자 하나를 변환할 때는 strconv.Itoastrconv.FormatFloatSprintf보다 빠릅니다. 반복문에서 긴 문자열을 만들 때는 Sprintf 결과를 이어 붙이지 말고 fmt.Fprintf(&b, ...)strings.Builder에 쓰세요.

자주 묻는 질문

Go에서 Println, Printf, Sprintf의 차이는 무엇인가요?

fmt.Println은 인자를 공백으로 구분해서 출력하고 끝에 줄바꿈을 붙입니다. fmt.Printf는 형식 문자열에 따라 출력하며 줄바꿈을 붙이지 않습니다. fmt.SprintfPrintf와 같은 방식으로 형식화하지만 출력하지 않고 결과를 문자열로 반환합니다. fmt.Errorf도 같은 일을 하고 error를 반환합니다.

Go에서 구조체를 필드 이름과 함께 출력하려면 어떻게 하나요?

%+v를 씁니다: fmt.Printf("%+v\n", user){Name:Ana Age:31}을 출력합니다. %v는 값만 {Ana 31}로 출력하고, %#v는 타입을 포함한 Go 문법 main.User{Name:"Ana", Age:31}로 출력합니다.

Go에서 실수를 소수점 둘째 자리까지 형식화하려면 어떻게 하나요?

%.2f를 씁니다: fmt.Sprintf("%.2f", 3.14159)"3.14"를 반환합니다. 열을 맞추려면 너비를 추가하고(%8.2f), 왼쪽 정렬하려면 빼기 기호를 붙입니다(%-8.2f). strconv.FormatFloat(f, 'f', 2, 64)는 형식 문자열 없이 같은 결과를 줍니다.

fmt.Errorf에서 %w는 무엇을 하나요?

%w는 오류를 %v처럼 형식화하면서 감싸기도 하므로, 새 오류가 원래 오류를 담게 됩니다. 그러면 errors.Iserrors.As가 감싼 오류를 찾을 수 있습니다: err := fmt.Errorf("load config: %w", os.ErrNotExist)이면 errors.Is(err, os.ErrNotExist)가 참입니다. %wfmt.Errorf에서만 동작합니다.

출력에 %!d(string=...)가 나오는 이유는 무엇인가요?

동사가 인자의 타입과 맞지 않기 때문입니다. 예를 들어 %d에 문자열을 넘긴 경우입니다. fmt는 패닉 대신 문제를 그 자리에 출력합니다: %!d(string=oops). 인자가 모자라면 %!d(MISSING), 남으면 %!(EXTRA int=2)가 출력됩니다. go vet은 프로그램을 실행하기 전에 셋 모두 잡아냅니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기