Поиск по сайту:

Импорт пакетов в Go


Введение

Возможность заимствовать и совместно использовать код в разных проектах является основополагающей для любого широко используемого языка программирования и всего сообщества открытого исходного кода. Заимствование кода позволяет программистам тратить большую часть своего времени на написание кода, отвечающего их потребностям, и тем не менее часто часть их нового кода оказывается полезной для других. Затем они могут решить организовать эти многократно используемые части в единое целое и поделиться им со своей командой или более широким сообществом программистов.

В Go базовая единица повторно используемого кода называется пакетом. Даже самая простая программа Go представляет собой отдельный пакет и, вероятно, использует как минимум еще один пакет. В этом руководстве вы напишете две крошечные программы: одну, которая использует пакет стандартной библиотеки для генерации случайных чисел, и другую, которая использует популярный сторонний пакет для генерации UUID. Затем вы можете написать более длинную программу, которая сравнивает два похожих пакета стандартных библиотек, импортируя и используя оба пакета, даже если они имеют одинаковое базовое имя. Наконец, вы будете использовать инструмент goimports, чтобы увидеть, как форматировать импорт.

Примечание. В Go также есть единица повторно используемого кода более высокого уровня: модуль. Модули — это версионные наборы пакетов. Вы познакомитесь с модулями в следующей статье «Как использовать модули Go».

Предпосылки

Прежде чем приступить к этому руководству, вам нужно только установить Go. Прочитайте правильный учебник для вашей операционной системы:

  • Как установить Go в Ubuntu 20.04
  • Как установить Go и настроить локальную среду программирования в macOS
  • Как установить Go и настроить локальную среду программирования в Windows 10

Шаг 1 — Использование пакетов стандартной библиотеки

Как и большинство языков, Go поставляется со встроенной библиотекой повторно используемого кода, который вы можете использовать для обычных задач. Вам не нужно писать собственный код для форматирования и печати строк или, например, отправлять HTTP-запрос. В стандартной библиотеке Go есть пакеты для обеих этих и многих других задач.

В программе «Как написать свою первую программу на Go» использовались пакеты fmt и strings из стандартной библиотеки. Давайте напишем еще одну программу, которая использует пакет math/rand для генерации случайных чисел.

Откройте новый файл с именем random.go в nano или в вашем любимом текстовом редакторе:

  1. nano random.go

Давайте создадим программу, которая выводит пять случайных целых чисел от нуля до девяти. Вставьте следующее в свой редактор:

package main

import "math/rand"

func main() {
	for i := 0; i < 5; i++ {
		println(rand.Intn(10))
	}
}

Эта программа импортирует пакет math/rand и использует его, ссылаясь на его базовое имя, rand. Это имя появляется в объявлении package вверху каждого исходного файла Go в пакете.

Каждая итерация цикла for вызывает rand.Intn(10) для генерации случайного целого числа от нуля до девяти (10 не включительно), а затем выводит целое число на консоль.

Обратите внимание, что вызов println() не ссылается на имя пакета. Это встроенная функция, которую не нужно импортировать. Обычно здесь используется функция fmt.Println() из пакета fmt, но эта программа использует println() для представления встроенных функций.

Сохраните программу. Если вы используете nano, нажмите CTRL+X, затем Y и ENTER, чтобы подтвердить изменения. Затем запустите программу:

go run random.go

Вы должны увидеть пять целых чисел от нуля до девяти:

Output
1 7 7 9 1

Похоже, что генератор случайных чисел работает, но обратите внимание, что если вы запускаете программу снова и снова, она каждый раз печатает одни и те же числа, а не новые случайные числа, как вы ожидаете. Это потому, что мы не вызывали функцию rand.Seed() для инициализации генератора чисел уникальным значением. Если вы этого не сделаете, пакет будет вести себя так, как если бы был вызван rand.Seed(1), и каждый раз будет генерировать одни и те же \случайные числа.

Поэтому вам нужно заполнять генератор чисел уникальным значением каждый раз при запуске программы. Программисты часто используют текущую метку времени в наносекундах. Для этого вам понадобится пакет time. Снова откройте random.go в своем редакторе и вставьте следующее:

package main

import (
	"math/rand"
	"time"
)

func main() {
	now := time.Now()
	rand.Seed(now.UnixNano())
println("Numbers seeded using current date/time:", now.UnixNano())
	for i := 0; i < 5; i++ {
		println(rand.Intn(10))
	}
}

При импорте более одного пакета вы можете использовать круглые скобки для создания блока импорта. Используя блок, вы можете избежать повторения ключевого слова import в каждой строке, что сделает ваш код чище.

Сначала вы получаете текущее системное время с помощью функции time.Now(), которая возвращает структуру Time. Затем вы передаете время функции rand.Seed(). Эта функция принимает 64-битное целое число (int64), поэтому вам нужно использовать метод Time.UnixNano() в структуре now для проходят во времени в наносекундах. Наконец, вы печатаете время, затраченное на заполнение генератора случайных чисел.

Теперь сохраните и снова запустите программу:

  1. go run random.go

Вы должны увидеть вывод, похожий на этот:

Output
Numbers seeded using current date/time: 1674489465616954000 2 6 3 1 0

Если вы запустите программу несколько раз, теперь вы должны каждый раз видеть разные целые числа, а также уникальное целое число, используемое для заполнения генератора случайных чисел.

Давайте еще раз отредактируем программу, чтобы распечатать начальное время в более удобном для пользователя формате. Отредактируйте строку, содержащую первый вызов println(), чтобы она выглядела следующим образом:

	println("Numbers seeded using current date/time:", now.Format(time.StampNano))

Теперь вы вызываете метод Time.Format() и передаете один из многих форматов, определенных в пакете time. Константа time.StampNano (const) представляет собой строку, и ее передача в Time.Format() позволяет распечатать месяц, день, и время, вплоть до наносекунды. Сохраните и запустите программу еще раз:

go run random.go
Output
Numbers seeded using current date/time: Jan 23 10:01:50.721413000 7 6 3 7 3

Это лучше, чем видеть огромное целое число, представляющее количество наносекунд, прошедших с 1 января 1970 года.

Что, если вашей программе нужны не случайные целые числа, а UUID, которые многие программисты используют в качестве глобальных уникальных идентификаторов фрагментов данных в своем развертывании? В стандартной библиотеке Go нет пакета для их создания, но у сообщества есть. Давайте теперь посмотрим, как загружать и использовать сторонние пакеты.

Шаг 2 — Использование сторонних пакетов

Одним из самых популярных пакетов для генерации UUID является pkg.go.dev и другие. Однако при ссылке на него в операторах кода вы будете использовать только базовое имя.

Перед загрузкой пакета вам необходимо инициализировать модуль, именно так Go управляет зависимостями программы и их версиями. Чтобы инициализировать модуль, используйте go mod init и передайте полное имя вашего собственного пакета. Если вы хотите разместить свой модуль на GitHub под своим именем пользователя «sammy», вы можете инициализировать свой модуль следующим образом:

  1. go mod init github.com/sammy/random

Это создает файл с именем go.mod. Давайте посмотрим на этот файл:

  1. cat go.mod
Output
module github.com/sammy/random go 1.19

Этот файл должен находиться в корне любого репозитория Go, который будет распространяться как модуль Go. Он должен как минимум определить имя модуля и требуемую версию Go. Ваша собственная версия Go может отличаться от показанной выше.

Вы не будете распространять свой модуль в этом руководстве, но этот шаг необходим для загрузки и использования сторонних пакетов.

Теперь используйте команду go get для загрузки стороннего модуля UUID:

  1. go get github.com/google/uuid

Это загружает последнюю версию:

Output
go: downloading github.com/google/uuid v1.3.0 go: added github.com/google/uuid v1.3.0

Пакет помещается в ваш локальный каталог $GOPATH/pkg/mod/. Если в вашей оболочке явно не задан $GOPATH, его значением по умолчанию является $HOME/go. Если ваш локальный пользователь — sammy и вы используете macOS, например, этот пакет будет загружен в /Users/sammy/go/pkg/mod. Вы можете запустить go env GOMODCACHE, чтобы увидеть, куда Go поместит загруженные модули.

Давайте просмотрим собственный файл go.mod вашей новой зависимости:

  1. cat /Users/sammy/go/pkg/mod/github.com/google/uuid@v1.3.0/go.mod
Output
module github.com/google/uuid

Похоже, этот модуль не имеет сторонних зависимостей; он использует только пакеты из стандартной библиотеки Go.

Обратите внимание, что версия модуля включена в имя его каталога. Это позволяет вам разрабатывать и тестировать несколько версий одного и того же пакета либо в одной программе, либо в разных программах, которые вы пишете.

Теперь снова взгляните на свой собственный файл go.mod:

  1. cat go.mod
Output
module github.com/sammy/random go 1.19 require github.com/google/uuid v1.3.0 // indirect

Команда go get обнаружила файл go.mod в вашем текущем каталоге и обновила его, чтобы отразить новую зависимость вашей программы, включая ее версию. Теперь вы можете использовать этот пакет в своем package. Откройте новый файл с именем uuid.go в текстовом редакторе и вставьте в него следующее:

package main

import "github.com/google/uuid"

func main() {
	for i := 0; i < 5; i++ {
		println(uuid.New().String())
	}
}

Эта программа похожа на random.go, но использует github.com/google/uuid для печати пяти UUID вместо использования math/rand. вывести пять целых чисел.

Сохраните новый файл и запустите его:

  1. go run uuid.go

Ваш вывод должен быть похож на этот:

Output
243811a3-ddc6-4e26-9649-060622bba2b0 b8129aa1-3803-4dae-bd9f-6ba8817f44b2 3ae27c71-caa8-4eaa-b8e6-e629b7c1cb49 37e06706-004d-4504-ad37-03c68252bb0f a2da6904-a6ab-4cc2-849b-d9d25a86e373

Пакет github.com/google/uuid генерирует эти UUID с помощью стандартного библиотечного пакета crypto/rand, который похож на пакет math/rand, но отличается от него. , который вы использовали в random.go. Что делать, если вам нужно использовать оба пакета? Оба они имеют базовое имя rand, так как же вы можете ссылаться на каждый отдельный пакет в своем коде? Давайте посмотрим на это дальше.

Шаг 3 — Импорт пакетов с одинаковыми именами

В документации для math/rand говорится, что на самом деле он генерирует псевдослучайные числа и «не подходит для работы, связанной с безопасностью». Для такой работы вы должны использовать crypto/rand. Но что, если качество случайности целых чисел не имеет значения для вашей программы? Может быть, вам действительно нужны только произвольные числа.

Вы можете написать программу для сравнения производительности этих двух пакетов rand, но вы не можете ссылаться на оба пакета по имени rand в такой программе. Чтобы обойти это, Go позволяет вам выбрать альтернативное локальное имя для пакета при его импорте.

Вот как можно импортировать два пакета с одинаковым базовым именем:

import (
    “math/rand”
    crand “crypto/rand”
)

Вы можете выбрать любой псевдоним (если он не совпадает с другими именами пакетов) и поместить его слева от полного имени пакета. В данном случае это псевдоним crand. Обратите внимание, что псевдоним не заключен в кавычки. В остальной части исходного файла, содержащего этот блок импорта, вы можете получить доступ к пакету crypto/rand, используя выбранное вами имя crand.

Вы также можете импортировать пакеты в свое собственное пространство имен (используя . в качестве псевдонима) или как пустой идентификатор (используя _ в качестве псевдонима). Подробнее об этом читайте в документации Go.

Чтобы проиллюстрировать, как вы можете захотеть использовать пакеты с одинаковыми именами, давайте создадим и запустим более длинную программу, которая генерирует случайные целые числа, используя оба пакета, и измеряет время, затрачиваемое в каждом случае. Эта часть не является обязательной; если вам это не интересно, перейдите к шагу 4.

Сравнение math/rand и crypto/rand (необязательно)

Получение аргумента командной строки

Начните с открытия третьего нового файла в вашем рабочем каталоге с именем compare.go и вставьте его в следующую программу:

package main

import "os"
import "strconv"

func main() {
	// User must pass in number of integers to generate
	if len(os.Args) < 2 {
		println("Usage:\n")
		println("  go run compare.go <numberOfInts>")
		return
	}
	n, err := strconv.Atoi(os.Args[1])
	if err != nil { // Maybe they didn't pass an integer
		panic(err) 
	}

	println("User asked for " + strconv.Itoa(n) + " integers.")
}

Этот код подготавливает вас к генерации заданного пользователем количества псевдослучайных целых чисел с использованием обоих пакетов rand позже. Он использует пакеты стандартных библиотек os и strconv для преобразования одного аргумента командной строки в целое число. Если аргумент не передан, он печатает оператор использования и завершает работу.

Запустите программу с одним аргументом 10, чтобы убедиться, что она работает:

  1. go run compare.go 10
[seconary_label Output]
User asked for 10 integers.

Все идет нормально. Теперь давайте сгенерируем случайные целые числа с помощью пакета math/rand, как вы это делали раньше, но на этот раз вы вычислите время, необходимое для этого.

Этап 1 — Измерение математических/рандовых показателей

Удалите последний оператор println() и замените его следующим:

	// Phase 1 — Using math/rand
	// Initialize the byte slice
	b1 := make([]byte, n)
	// Get the time
	start := time.Now()
	// Initialize the random number generator
	rand.Seed(start.UnixNano())
	// Generate the pseudo-random numbers
	for i := 0; i < n; i++ {
		b1[i] = byte(rand.Intn(256)) // Where the magic happens!
	}
	// Compute the elapsed time
	elapsed := time.Now().Sub(start)
	// In case n is very large, only print a few numbers
	for i := 0; i < 5; i++ {
		println(b1[i])
	}
	fmt.Printf("Time to generate %v pseudo-random numbers with math/rand: %v\n", n, elapsed)

Во-первых, вы используете встроенную функцию make() для создания пустой части байтов ([]byte) для хранения сгенерированных целых чисел (в виде байтов). Его длина — это количество целых чисел, которые запросил пользователь.

Затем вы получаете текущее время и заполняете его генератором случайных чисел, как вы делали это в random.go на шаге 1.

После этого вы генерируете n псевдослучайных целых чисел от 0 до 255, конвертируете каждое в байт и помещаете его в свой байтовый срез. Почему целые числа от 0 до 255? Поскольку код, использующий crypto/rand, который вы собираетесь написать, генерирует байты, а не целые числа любого размера, и мы должны выполнить равное сравнение пакетов. Байт, состоящий из 8 бит, может быть представлен целым числом без знака от 0 до 255. (На самом деле тип byte в Go является псевдонимом для типа uint8. .)

Наконец, вы печатаете только первые пять байтов, если пользователь запросил очень большое количество целых чисел. Приятно видеть несколько целых чисел, просто чтобы убедиться, что генератор чисел работает.

Перед запуском программы не забудьте добавить новые пакеты, которые вы используете, в свой блок import:

import (
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"time"
)

После добавления выделенных пакетов запустите программу:

  1. go run compare.go 10
Output
189 203 209 238 235 Time to generate 10 pseudo-random numbers with math/rand: 33.417µs

Потребовалось 33,417 микросекунд, чтобы сгенерировать десять целых чисел от 0 до 255 и сохранить их в байтовом срезе. Давайте посмотрим, как это соотносится с производительностью crypto/rand.

Фаза 2 — Измерение производительности крипто/ранд

Перед добавлением кода, использующего crypto/rand, добавьте пакет в свой блок import, как показано ранее:

import (
	"fmt"
	"math/rand"
	crand "crypto/rand"
	"os"
	"strconv"
	"time"
)

Затем добавьте следующий код в конец функции main():

	// Phase 2 — Using crypto/rand
	// Initialize the byte slice
	b2 := make([]byte, n)
	// Get the time (Note: no need to seed the random number generator)
	start = time.Now()
	// Generate the pseudo-random numbers
	_, err = crand.Read(b2) // Where the magic happens!
	// Compute the elapsed time
	elapsed = time.Now().Sub(start)
	// exit if error
	if err != nil {
		panic(err)
	}
	// In case n is very large, only print a few numbers
	for i := 0; i < 5; i++ {
		println(b2[i])
	}
	fmt.Printf("Time to generate %v pseudo-random numbers with crypto/rand: %v\n", n, elapsed)

Этот код максимально точно отражает код фазы 1. Вы генерируете байтовый фрагмент размером n, получаете текущее время, генерируете n байтов, вычисляете прошедшее время и, наконец, печатаете пять целых чисел и прошедшее время. При использовании пакета crypto/rand нет необходимости явно запускать генератор случайных чисел.

Примечание: crypto/rand также включает функцию Int(), но в нашем примере здесь используется Read(), потому что это то, что используется только фрагмент примера кода в документации пакета. Не стесняйтесь исследовать пакет crypto/rand самостоятельно.

Вся ваша программа compare.go должна выглядеть так:

package main

import (
	"fmt"
	"math/rand"
	crand "crypto/rand"
	"os"
	"strconv"
	"time"
)

func main() {
	// User must pass in number of integers to generate
	if len(os.Args) < 2 {
		println("Usage:\n")
		println("  go run compare.go <numberOfInts>")
		return
	}
	n, err := strconv.Atoi(os.Args[1])
	if err != nil { // Maybe they didn't pass an integer
		panic(err)
	}

	// Phase 1 — Using math/rand
	// Initialize the byte slice
	b1 := make([]byte, n)
	// Get the time
	start := time.Now()
	// Initialize the random number generator
	rand.Seed(start.UnixNano())
	// Generate the pseudo-random numbers
	for i := 0; i < n; i++ {
		b1[i] = byte(rand.Intn(256)) // Where the magic happens!
	}
	// Compute the elapsed time
	elapsed := time.Now().Sub(start)
	// In case n is very large, only print a few numbers
	for i := 0; i < 5; i++ {
		println(b1[i])
	}
	fmt.Printf("Time to generate %v pseudo-random numbers with math/rand: %v\n", n, elapsed)

	// Phase 2 — Using crypto/rand
	// Initialize the byte slice
	b2 := make([]byte, n)
	// Get the time (Note: no need to seed the random number generator)
	start = time.Now()
	// Generate the pseudo-random numbers
	_, err = crand.Read(b2) // Where the magic happens!
	// Compute the elapsed time
	elapsed = time.Now().Sub(start)
	// exit if error
	if err != nil {
		panic(err)
	}
	// In case n is very large, only print a few numbers
	for i := 0; i < 5; i++ {
		println(b2[i])
	}
	fmt.Printf("Time to generate %v pseudo-random numbers with crypto/rand: %v\n", n, elapsed)
}

Запустите программу с параметром 10, чтобы сгенерировать десять 8-битных целых чисел, используя каждый пакет:

  1. go run compare.go 10
Output
145 65 231 211 250 Time to generate 10 pseudo-random numbers with math/rand: 32.5µs 101 188 250 45 208 Time to generate 10 pseudo-random numbers with crypto/rand: 42.667µs

В этом примере пакет math/rand был немного быстрее, чем crypto/rand. Попробуйте запустить compare.go несколько раз с параметром 10. Затем попробуйте сгенерировать тысячу или миллион целых чисел. Какой пакет стабильно быстрее?

Этот пример программы предназначен для того, чтобы показать, как вы можете использовать два пакета с одинаковым именем и схожим назначением в одной программе. Это не означает рекомендацию одного из этих пакетов по сравнению с другим. Если вы хотите расширить compare.go, вы можете использовать пакет math/stats для сравнения случайности байтов, генерируемых каждым пакетом. Какую бы программу вы ни писали, вы должны оценить различные пакеты и выбрать лучший для ваших нужд.

Наконец, давайте посмотрим, как форматировать объявления import с помощью инструмента goimports.

Шаг 4 — Использование Goimports

Иногда, когда вы находитесь в процессе программирования, вы забываете импортировать пакеты, которые используете, или удаляете те, которые не используете. Инструмент командной строки goimports не только форматирует объявление (объявления) import, но и остальной код, что делает его более функциональной заменой gofmt — он также добавляет все отсутствующие import для пакетов, на которые ссылается ваш код, и удаляет import для неиспользуемых пакетов.

Инструмент не входит в комплект поставки Go по умолчанию, поэтому установите его сейчас с помощью go install:

  1. go install golang.org/x/tools/cmd/goimports@latest

Это помещает двоичный файл goimports в ваш каталог $GOPATH/bin. Если вы следовали руководству Как установить Go и настроить локальную среду программирования в macOS (или соответствующему руководству для вашей операционной системы), этот каталог уже должен быть в вашем PATH. Попробуйте запустить инструмент:

  1. goimports --help

Если вы не видите инструкции по использованию инструмента, $GOPATH/bin отсутствует в вашем PATH. Прочтите руководство по настройке среды Go для вашей операционной системы, чтобы настроить это.

Как только goimports появится в вашем PATH, удалите весь блок import из random.go. Затем запустите goimports с параметром -d, чтобы показать разницу между тем, что он хочет добавить:

  1. goimports -d random.go
Outputs
diff -u random.go.orig random.go --- random.go.orig 2023-01-25 16:29:38 +++ random.go 2023-01-25 16:29:38 @@ -1,5 +1,10 @@ package main +import ( + "math/rand" + "time" +) + func main() { now := time.Now() rand.Seed(now.UnixNano())

Впечатляет, но goimports также может распознавать и добавлять сторонние пакеты, если они установлены локально через go get. Удалите import из uuid.go и запустите на нем goimports:

  1. goimports -d uuid.go
Output
diff -u uuid.go.orig uuid.go --- uuid.go.orig 2023-01-25 16:32:56 +++ uuid.go 2023-01-25 16:32:56 @@ -1,8 +1,9 @@ package main +import "github.com/google/uuid" + func main() { for i := 0; i < 5; i++ { println(uuid.New().String()) } }

Теперь отредактируйте uuid.go и:

  1. Добавьте импорт для math/rand, который код не использует.
  2. Замените встроенную функцию println() на fmt.Println(), но не добавляйте import \fmt. ли>

package main

import "math/rand"

func main() {
	for i := 0; i < 5; i++ {
		fmt.Println(uuid.New().String())
	}
}

Сохраните файл и снова запустите для него goimports:

  1. goimports -d uuid.go
Output
diff -u uuid.go.orig uuid.go --- uuid.go.orig 2023-01-25 16:36:28 +++ uuid.go 2023-01-25 16:36:28 @@ -1,10 +1,13 @@ package main -import "math/rand" +import ( + "fmt" + "github.com/google/uuid" +) + func main() { for i := 0; i < 5; i++ { fmt.Println(uuid.New().String()) } }

Инструмент не только добавляет недостающие импорты, но и удаляет ненужные. Обратите также внимание, что он помещает импорт в блок в круглых скобках, а не использует ключевое слово import в каждой строке.

Чтобы записать изменения в uuid.go (или любой другой файл), а не выводить их на терминал, используйте goimports с параметром -w:

  1. goimports -w uuid.go

Вы должны настроить свой текстовый редактор или IDE для вызова goimports при сохранении файла *.go, чтобы ваши исходные файлы всегда были правильно отформатированы. Как упоминалось ранее, goimports заменяет gofmt, поэтому, если ваш текстовый редактор уже использует gofmt, настройте его на использование goimports вместо этого.

Еще одна вещь, которую делает goimports, — это принудительно устанавливает определенный порядок сортировки для вашего импорта. Попытка поддерживать порядок импорта вручную может быть утомительной и подверженной ошибкам, поэтому позвольте goimports сделать это за вас.

Если команда Go изменит официальный формат исходных файлов Go, они обновят goimports, чтобы отразить эти изменения, поэтому вам следует периодически обновлять goimports, чтобы ваш код всегда соответствовал последние стандарты.

Заключение

В этом руководстве вы создали и запустили две программы длиной менее десяти строк, используя популярные пакеты, помогающие генерировать случайные числа и UUID. Экосистема Go настолько богата хорошо написанными пакетами, что написание новой программы на Go должно быть удовольствием, и вы обнаружите, что пишете полезные программы, соответствующие вашим конкретным потребностям, с меньшими усилиями, чем вы могли бы подумать.

Ознакомьтесь со следующим руководством из этой серии «Как использовать модули Go», чтобы понять, как группировать пакеты вместе и распространять их среди других как единое целое.