Python에는 beautifulsoup 패키지가 존재해 Go에서도 없을까 찾아보던 도중 유사한 패키지를 찾았습니다. 해당 패키지를 사용하여 웹 크롤러를 만들거나 html에서 필요한 정보를 검색할 때, 편리하게 코딩할 수 있습니다.

설치

$ go get github.com/PuerkitoBio/goquery

사용법

아래는 현재 최신버전인 1.5 버전으로 설명합니다. 1.4이하 버전에서는 NewDocument()가 존재했지만 1.4버전에서 더이상 사용하지 않으므로 언급하지 않습니다.

package main

import (
  "fmt"
  "log"
  "net/http"

  "github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
  // Request the HTML page.
  res, err := http.Get("http://metalsucks.net")
  if err != nil {
    log.Fatal(err)
  }
  defer res.Body.Close()
  if res.StatusCode != 200 {
    log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
  }

  // Load the HTML document
  doc, err := goquery.NewDocumentFromReader(res.Body)
  if err != nil {
    log.Fatal(err)
  }

  // Find the review items
  doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
    // For each item found, get the band and title
    band := s.Find("a").Text()
    title := s.Find("i").Text()
    fmt.Printf("Review %d: %s - %s\n", i, band, title)
  })
}

func main() {
  ExampleScrape()
}


위는 타겟 사이트를 호출하여 원하는 데이터를 찾아가는 예시입니다. 아래에서는 test html을 생성 후 기능을 하나씩 설명한 예제입니다.

test html 생성 후 원하는 태그 찾기

검색은 html을 찾는 방식과 동일합니다.

태그 .클래스명 #아이디명

예시)

div 안에 있는 모든 p태그 찾기

  • div p

class=name 명 찾기

  • .name

id=err 명 찾기

  • #err

<div class=name> 찾기

  • div.name

<div class=name> <p>test</p> </div> 에서 p태그 내의 내용찾기

  • div.name p


아래는 a 태그를 찾는 예시입니다.

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		// 코드
		// fmt.Println(s.Text())
		
	})
}

func main() {
	ExampleScrape()
}

태그 사이에 있는 내용 출력

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("h1").Each(func(i int, s *goquery.Selection) {
		fmt.Println(s.Text())

	})
}

func main() {
	ExampleScrape()
}

// This is a heading

태그 내의 속성값 출력

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		value, isExist := s.Attr("href")
		fmt.Println(value, isExist)

	})
}

func main() {
	ExampleScrape()
}


// http://brownbears.tistory.com true

복잡한 검색

아래는 ex_class라는 클래스명을 가진 div태그 내 p태그들의 내용을 찾는 쿼리입니다.

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("div.ex_class p").Each(func(i int, s *goquery.Selection) {
		fmt.Println(s.Text())

	})
}

func main() {
	ExampleScrape()
}


만약 id명이 ex_id를 가진 div태그 내의 모든 p태그들에 대한 내용을 가지고 오고 싶을 경우, div.ex_class p 가 아닌 div#ex_id p 로 검색하면 됩니다.


더 많은 정보는 https://github.com/PuerkitoBio/goquery 에서 확인할 수 있습니다.

파이썬 3.4부터 내장함수로 pathlib가 추가되었습니다. pathlib가 추가되기 이전에는 os 모듈을 사용했습니다. pathlib는 파일위치 찾기, 파일 입출력과 같은 동작을 하는데 os모듈과 어떻게 다른지 아래에서 설명합니다.

pathlib 설명

pathlib 모듈의 기본 아이디어는 파일시스템 경로를 단순한 문자열이 아니라 객체로 다루자는 것입니다. 가령 파일의 존재성 여부를 판단하는 것은 아래와 같이 작성할 수 있습니다.

import os
from pathlib import Path

file_path = './path/to/file'

if os.path.exists(file_path):
    pass

p = Path(file_path)

if p.exists():
    pass


파일시스템을 문자열이 아닌 객체로 다루게 되면서 얻게 된 큰 이익 중 하나는 연산자를 새롭게 정의할 수 있게 되었다는 점입니다.

import os
from pathlib import Path

dir_name = 'dir'
sub_dir_name = 'sub_dir_name'
file_name = 'file_name'

# 기존 (os.path)
file = os.path.join(dir_name, sub_dir_name, file_name)

# pathlib
dir = Path(dir_name)
file = dir / sub_dir_name / file_name

pathlib은 슬래시(/)가 경로 구분 문자로 사용하여 직관적으로 이해할 수 있습니다.

사용법

파일 열기

import pathlib


path = pathlib.Path('test.txt')
file = path.open('r')

파일 읽고 쓰기

파일을 읽거나 쓰는 행동을 1번만 하게 된다면 아래와 같이 할 수 있습니다.

import pathlib

# 읽기
path = pathlib.Path('test.txt')
file = path.read_text()


# 쓰기
path = pathlib.Path('test1.txt')
path.write_text('파일쓰기')

경로분석

import pathlib


path = pathlib.Path('/user/path/to/file')

print(path)
print(path.parent)
print(list(path.parents))
print(path.parts)


# /user/path/to/file
# /user/path/to
# [PosixPath('/user/path/to'), PosixPath('/user/path'), PosixPath('/user'), PosixPath('/')]
# ('/', 'user', 'path', 'to', 'file')

실행한 스크립트 폴더경로 얻기

import pathlib

path = pathlib.Path.cwd()

현재 위치한 파일/폴더 얻기

glob 패턴을 사용하여 파일/폴더를 얻을 수 있습니다.

import pathlib


path = pathlib.Path('.')
files = path.glob('*')

print(list(files))

현재 위치에서 존재하는 모든 파일/폴더 얻기

위의 glob에서 패턴만 변경하여 현재 위치에서 모든 폴더(폴더 내 파일 포함)와 파일을 얻을 수 있습니다.

import pathlib


path = pathlib.Path('.')
files = path.glob('**/*')

print(list(files))


기존에 사용하던 os 모듈을 사용하여 경로찾기나 파일입출력을 pathlib로 대체할 수 있습니다.


웹 크롤러를 만들거나 html에서 필요한 정보를 검색할 때, BeautifulSoup 라이브러리를 사용하여 편리하게 코딩할 수 있습니다.

설치

$ pip3 install beautifulsoup4

beautifulsoup에는 기본적으로 파이썬 표준라이브러리인 html 파서를 지원하지만, lxml이라는 모듈이 더 빠르게 동작하므로 lxml 모듈도 설치해 줍니다.

$ pip3 install lxml


아래는 beautifulsoup에서 사용할 수 있는 파서의 장단점을 보여주는 테이블 입니다.

Parser선언방법장점단점
파이썬 html.parserBeautifulSoup(markup, 'html.parser')

설치할 필요 없음

적당한 속도


lxml HTML parserBeautifulSoup(markup, 'lxml')매우 빠름lxml 추가 설치 필요
lxml XML parser

BeautifulSoup(markup, 'lxml-xml')

BeautifulSoup(markup, 'xml')

매우 빠름

유일하게 지원되는 xml parser

lxml 추가 설치 필요
html5libBeautifulSoup(markup, 'html5lib')

웹 브라우저와 같은 방식으로 페이지를 파싱

유효한 HTML5 생성

html5lib 추가 설치 필요

매우 느림

사용법

기본 선언 및 테스트 HTML은 아래와 같습니다.

from bs4 import BeautifulSoup

html = """
<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
"""


bs = BeautifulSoup(html, 'lxml')

find(name, attrs, recursive, string, **kwargs)

조건에 맞는 태그를 가져옵니다. 만약 조건에 맞는 태그가 1개 이상이면 가장 첫 번째 태그를 가져옵니다.

result = bs.find('p')
print(result)


# <p>a</p>

find_all(name, attrs, recursive, string, limit, **kwargs)

조건에 맞는 모든 태그들을 가져옵니다.

result = bs.find_all('p')
print(result)

# [<p>a</p>, <p>b</p>, <p>c</p>, <p>d</p>, <p>e</p>, <p>f</p>, <p>g</p>, <p>h</p>, <p>i</p>, <p>This is a paragraph.</p>, <p>This is another paragraph.</p>]

class 명으로 찾기

result = bs.find('div', class_='ex_class')
print(result)


# <div class="ex_class">
# <p>d</p>
# <p>e</p>
# <p>f</p>
# </div>

id 명으로 찾기

result = bs.find('div', id='ex_id')
print(result)


# <div id="ex_id">
# <p>g</p>
# <p>h</p>
# <p>i</p>
# </div>

해당 태그명 출력

result = bs.find('div', id='ex_id')
print(result.name)

# div

해당 id명 출력

result = bs.find('div', id='ex_id')
print(result['id'])

# ex_id

해당 class명 출력

result = bs.find('div', class_='ex_class')
print(result['class'])


# ex_class

태그 사이에 있는 내용 출력

result = bs.find('div', class_='ex_class')
print(result.p.text)

# d

result = bs.find('div', class_='ex_class')


# 검색된 div 태그 내의 모든 p태그를 조회
for tag in result.find_all('p'):
    print(tag.text)

# d
# e
# f

태그 내의 속성값 출력

result = bs.find('a', class_='a')
print(result.get('href'))
# http://brownbears.tistory.com


위에서 설명한 방법만으로 충분히 HTML을 파서할 수 있습니다. 더 자세한 내용은 https://www.crummy.com/software/BeautifulSoup/bs4/doc/ 에서 확인할 수 있습니다.

파이썬 스크립트를 개발할 때, 호출 당시 인자값을 줘서 동작을 다르게 하고 싶은 경우가 있습니다. 이때, 파이썬 내장함수인 argparse 모듈을 사용하여 원하는 기능을 개발할 수 있습니다.

아래 설명은 파이썬 3.7 버전 기준으로 작성했습니다.

사용법

간단하게 인자값을 받아 처리하는 로직은 아래와 같습니다.

import argparse

# 인자값을 받을 수 있는 인스턴스 생성
parser = argparse.ArgumentParser(description='사용법 테스트입니다.')

# 입력받을 인자값 등록
parser.add_argument('--target', required=True, help='어느 것을 요구하냐')
parser.add_argument('--env', required=False, default='dev', help='실행환경은 뭐냐')

# 입력받은 인자값을 args에 저장 (type: namespace)
args = parser.parse_args()

# 입력받은 인자값 출력
print(args.target)
print(args.env)


위와 같이 코드를 작성한 다음, 터미널에서 해당 파일을 인자값 없이 실행시키면 아래와 같이 노출이 됩니다.

$ python3 argparse_test.py
usage: argparse_test.py [-h] --target TARGET [--env ENV]
argparse_test.py: error: the following arguments are required: --target

$ python3 argparse_test.py -h
usage: argparse_test.py [-h] --target TARGET [--env ENV]

사용법 테스트입니다.

optional arguments:
  -h, --help       show this help message and exit
  --target TARGET  어느 것을 요구하냐
  --env ENV        실행환경은 뭐냐


다음은 인자값을 target, env에 인자값을 주고 실행시킨 결과입니다.

$ python3 argparse_test.py --target=테스트 --env=local
테스트
local

$ python3 argparse_test.py --target=테스트
테스트
dev

$ python3 argparse_test.py --env=qa
usage: argparse_test.py [-h] --target TARGET [--env ENV]
argparse_test.py: error: the following arguments are required: --target

인자값 설명

위에서 설명한 기능으로도 원하는 동작을 충분히 커버할 수 있습니다. 아래는 객체와 메서드에 어떤 인자가 있는지 간략하게 설명하겠습니다.

ArgumentParser()

해당 객체에는 아래와 같이 입력받고 있습니다.

  • prog: 프로그램의 이름 (기본값: sys.argv[0])
    • 기본값으로 실행한 스크립트파일명을 노출. 작성 시 스크립트 파일 대신 입력한 값이 노출
  • usage: 프로그램 사용법을 설명하는 문자열 (기본값: 파서에 추가된 인자로부터 만들어지는 값)
    • 사용방법을 노출.  기본값으로 실행한 파일 + 입력한 인자값들을 노출
  • description: 인자 도움말 전에 표시할 텍스트 (기본값: none)
    • 스크립트에 -h 옵션을 주어 실행 시, usage 아래에 노출
  • epilog: 인자 도움말 후에 표시할 텍스트 (기본값: none)
  • parents: ArgumentParser 객체들의 리스트이고, 이 들의 인자들도 포함
  • formatter_class: 도움말 출력을 사용자 정의하기 위한 클래스
  • prefix_chars: 선택 인자 앞에 붙는 문자 집합 (기본값: '-').
  • fromfile_prefix_chars: 추가 인자를 읽어야 하는 파일 앞에 붙는 문자 집합 (기본값: None).
  • argument_default: 인자의 전역 기본값 (기본값: None)
  • conflict_handler: 충돌하는 선택 사항을 해결하기 위한 전략 (일반적으로 불필요함)
  • add_help: 파서에 -h/--help 옵션을 추가 (기본값: True)
  • allow_abbrev: 약어가 모호하지 않으면 긴 옵션을 축약할 수 있도록 함. (기본값: True)

add_argument()

해당 메서드는 아래와 같이 입력받고 있습니다.

  • name or flags: 옵션 문자열의 이름이나 리스트, 예를 들어 foo 또는 -f, --foo.
  • action: 명령행에서 이 인자가 발견될 때 수행 할 액션의 기본형.
  • nargs: 소비되어야 하는 명령행 인자의 수.
  • const: 일부 action 및 nargs 를 선택할 때 필요한 상숫값.
  • default: 인자가 명령행에 없는 경우 생성되는 값.
  • type: 명령행 인자가 변환되어야 할 형.
  • choices: 인자로 허용되는 값의 컨테이너.
  • required: 명령행 옵션을 생략 할 수 있는지 아닌지 (선택적일 때만).
  • help: 인자가 하는 일에 대한 간단한 설명.
  • metavar: 사용 메시지에 사용되는 인자의 이름.
  • dest: parse_args() 가 반환하는 객체에 추가될 어트리뷰트의 이름.


해당 인자들과 추가적인 기능에 대한 자세한 설명은 https://docs.python.org/ko/3.7/library/argparse.html 에서 확인할 수 있습니다.

Go언어에서 날짜계산이나 포맷변경은 극악입니다. 실제로 검색해보면 욕 한바가지 써놓은 것을 쉽게 볼 수 있습니다. 

현재날짜 가져오기

기본 현재날짜, utc, unix 별로 가져오는 로직입니다.

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	nowUTC := time.Now().UTC()
	nowUNIX := time.Now().Unix()
	fmt.Println(now)
	fmt.Println(nowUTC)
	fmt.Println(nowUNIX)
}


// 결과


// 2018-12-19 20:42:08.219845 +0900 KST m=+0.000394187
// 2018-12-19 11:42:08.219846 +0000 UTC
// 1545219728

날짜포맷 변경하기

여기서 욕이 제일 많이 볼 수 있고 많이 나옵니다. GO언어는 다른 언어와 달리 날짜 포맷형식이 이상합니다.

아래는 time 패키지에서 제공해주는 constant입니다.

const (
        ANSIC       = "Mon Jan _2 15:04:05 2006"
        UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
        RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
        RFC822      = "02 Jan 06 15:04 MST"
        RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
        RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
        RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
        RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
        RFC3339     = "2006-01-02T15:04:05Z07:00"
        RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
        Kitchen     = "3:04PM"
        // Handy time stamps.
        Stamp      = "Jan _2 15:04:05"
        StampMilli = "Jan _2 15:04:05.000"
        StampMicro = "Jan _2 15:04:05.000000"
        StampNano  = "Jan _2 15:04:05.000000000"
)


아래는 time 패키지에서 제공하는 포맷형식을 사용하는 방법과 흔히 사용되는 yyyy-mm-dd hh:mm:ss 형식으로 포맷한 방법입니다.

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	custom := now.Format("2006-01-02 15:04:05")
	ansic := now.Format(time.ANSIC)
	fmt.Println(custom)
	fmt.Println(ansic)
}


// 결과
// 2018-12-19 20:41:21
// Wed Dec 19 20:41:21 2018


위 코드에서 2006-01-02 15:04:05 는 날짜를 나타내는 것이 아닌 yyyy-mm-dd hh:mm:ss 를 의미합니다. 해당 날짜는 아무 무작위로 한 것처럼 보이지만 뜻이 숨어 있습니다.

Mon Jan 2 15:04:05 -0700 MST 2006
0   1   2  3  4  5              6

위와 같이 각 순서라고 생각하시면 되지만 자주 사용되는 yyyy-mm-dd 를 사용안하고 복잡한 2006-01-02 와 같은 형식을 선택한 것은 의문입니다.

현재시간에서 시, 분 차감하기

import (
  "time"
)


func main() {
	now := time.Now()

	convMinutes, _ := time.ParseDuration("10m")
	convHours, _ := time.ParseDuration("1h")
	diffMinutes := now.Add(-convMinutes).Format("2006-01-02 15:04:05")
	diffHours := now.Add(-convHours).Format("2006-01-02 15:04:05")
	
	fmt.Println(now)
	fmt.Println(diffMinutes)
	fmt.Println(diffHours)
}


// 결과


// 2018-12-19 20:52:35.100418 +0900 KST m=+0.000385834
// 2018-12-19 20:42:35
// 2018-12-19 19:52:35

ParseeDuration() 함수를 호출하여 계산하고자 하는 시간을 Duration 타입으로 변경해야 합니다. 위의 1h, 10m 과 같이 시, 분, 초를 명시해야 값이 정확하게 전달됩니다. Go에서 제공하는 시간단위는 "ns", "us" (or "µs"), "ms", "s", "m", "h" 입니다.

현재시간을 가져온 다음 Add() 함수를 통해 위에서 구한 값을 빼줍니다. 만약 -부호가 없으면 현재시간에서 더한 시간이 반환됩니다.

현재날짜에서 년,월,일 차감하기

import (
  "time"
)

func main() {
	now := time.Now()

	convDays := 1
	convMonths := 1
	convYears := 1

	diffDays := now.AddDate(0, 0, -convDays).Format("2006-01-02 15:04:05")
	diffMonths := now.AddDate(0, -convMonths, 0).Format("2006-01-02 15:04:05")
	diffYears := now.AddDate(-convYears, 0, 0).Format("2006-01-02 15:04:05")


	fmt.Println(now)
	fmt.Println(diffDays)
	fmt.Println(diffMonths)
	fmt.Println(diffYears)
}


// 결과
// 2018-12-19 21:00:23.506858 +0900 KST m=+0.000362007
// 2018-12-18 21:00:23
// 2018-11-19 21:00:23
// 2017-12-19 21:00:23

위의 시간계산과 유사한 형태입니다. 날짜는 AddDate() 함수를 호출하며 AddDate(year, month, day) 와 같이 값을 넣어 주면 됩니다. 

struct 성질을 까먹고 한참 삽질하다가 정리하던 도중 중요한 부분을 기억했습니다...........

GO에서 struct를 선언하는 방식은 여러가지가 있습니다. struct 성질을 먼저 복기한 후, struct를 리스트로 반환하는 코드를 설명하겠습니다.

1. 빈 struct 객체 먼저 생성 후 데이터 채우기

Article이란 빈 객체를 생성 후 데이터를 삽입하는 코드입니다.

type Article struct {
    Title      string
}


func main() {
	article := Article{}
	article.Title = "test"
}

2. GO 내장함수 new() 사용

GO 내장함수인 new() 메소드를 사용하여 생성합니다.

type Article struct {
    Title      string
}


func main() {
	article := new(Article)
	article.Title = "test"
}


두 방식이 동일해 보이지만 반환되는 값에 아주 큰 차이가 있습니다. 1번의 경우 value가 넘어오지만 2번은 객체의 pointer가 넘어오게 됩니다. 이 부분을 간과하고 struct 를 리스트로 하려다 혼돈에 빠졌었습니다. 

만약 struct 객체를 생성한 다음, 다른 함수에 넘겨주는 일이 없다면 1번을 사용해도 되지만 해당 struct를 다른 함수에 넘겨 사용하는 일이 있으면 2번처럼 선언하여 사용하는 것이 좋습니다.

struct 리스트로 반환

struct를 리스트로 만드는 것은 아주 간단합니다. 해당 코드에 맞게 struct를 잘 선언해서 사용하면 됩니다.

type Article struct {
    Title      string
}

// list: value, list data: value
func InitArticleList1() []Article {
    articleList := []Article{}
    return articleList
}
// list: pointer, list data: value
func InitArticleList2() *[]Article {
    articleList := []Article{}
    return &articleList
}

// list: pointer, list data: pointer
func InitArticleList3() *[]*Article {
    articleList := []*Article{}
    return &articleList
}

func appendList1(articleList *[]Article) {
    article := Article{"test"}
    *articleList=append(*articleList, article)
}

func appendList2(articleList *[]*Article) {
    article := new(Article)
		article.Title = "test"
    *articleList=append(*articleList, article)
}

func main() {

    articleList1 := InitArticleList1()
    appendList1(&articleList1)
		articleList2 := InitArticleList2()
    appendList1(articleList2)
    articleList3 := InitArticleList3()
    appendList2(articleList3)

    fmt.Println(articleList1)
    fmt.Println(articleList2)
	fmt.Println(articleList3)
}


// 결과
// [{test}]
// &[{test}]
// &[0xc42000e1f0]


+ Random Posts