Импорт пакетов в 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
или в вашем любимом текстовом редакторе:
- 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
Вы должны увидеть пять целых чисел от нуля до девяти:
Output1
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
для проходят во времени в наносекундах. Наконец, вы печатаете время, затраченное на заполнение генератора случайных чисел.
Теперь сохраните и снова запустите программу:
- go run random.go
Вы должны увидеть вывод, похожий на этот:
OutputNumbers 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
OutputNumbers 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», вы можете инициализировать свой модуль следующим образом:
- go mod init github.com/sammy/random
Это создает файл с именем go.mod
. Давайте посмотрим на этот файл:
- cat go.mod
Outputmodule github.com/sammy/random
go 1.19
Этот файл должен находиться в корне любого репозитория Go, который будет распространяться как модуль Go. Он должен как минимум определить имя модуля и требуемую версию Go. Ваша собственная версия Go может отличаться от показанной выше.
Вы не будете распространять свой модуль в этом руководстве, но этот шаг необходим для загрузки и использования сторонних пакетов.
Теперь используйте команду go get
для загрузки стороннего модуля UUID:
- go get github.com/google/uuid
Это загружает последнюю версию:
Outputgo: 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
вашей новой зависимости:
- cat /Users/sammy/go/pkg/mod/github.com/google/uuid@v1.3.0/go.mod
Outputmodule github.com/google/uuid
Похоже, этот модуль не имеет сторонних зависимостей; он использует только пакеты из стандартной библиотеки Go.
Обратите внимание, что версия модуля включена в имя его каталога. Это позволяет вам разрабатывать и тестировать несколько версий одного и того же пакета либо в одной программе, либо в разных программах, которые вы пишете.
Теперь снова взгляните на свой собственный файл go.mod
:
- cat go.mod
Outputmodule 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
. вывести пять целых чисел.
Сохраните новый файл и запустите его:
- go run uuid.go
Ваш вывод должен быть похож на этот:
Output243811a3-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
, чтобы убедиться, что она работает:
- 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"
)
После добавления выделенных пакетов запустите программу:
- go run compare.go 10
Output189
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-битных целых чисел, используя каждый пакет:
- go run compare.go 10
Output145
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
:
- go install golang.org/x/tools/cmd/goimports@latest
Это помещает двоичный файл goimports
в ваш каталог $GOPATH/bin
. Если вы следовали руководству Как установить Go и настроить локальную среду программирования в macOS (или соответствующему руководству для вашей операционной системы), этот каталог уже должен быть в вашем PATH
. Попробуйте запустить инструмент:
- goimports --help
Если вы не видите инструкции по использованию инструмента, $GOPATH/bin
отсутствует в вашем PATH
. Прочтите руководство по настройке среды Go для вашей операционной системы, чтобы настроить это.
Как только goimports
появится в вашем PATH
, удалите весь блок import
из random.go
. Затем запустите goimports
с параметром -d
, чтобы показать разницу между тем, что он хочет добавить:
- goimports -d random.go
Outputsdiff -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
:
- goimports -d uuid.go
Outputdiff -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
и:
- Добавьте
импорт
дляmath/rand
, который код не использует. - Замените встроенную функцию
println()
наfmt.Println()
, но не добавляйтеimport \fmt
. ли>
package main
import "math/rand"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(uuid.New().String())
}
}
Сохраните файл и снова запустите для него goimports
:
- goimports -d uuid.go
Outputdiff -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
:
- goimports -w uuid.go
Вы должны настроить свой текстовый редактор или IDE для вызова goimports
при сохранении файла *.go
, чтобы ваши исходные файлы всегда были правильно отформатированы. Как упоминалось ранее, goimports
заменяет gofmt
, поэтому, если ваш текстовый редактор уже использует gofmt
, настройте его на использование goimports
вместо этого.
Еще одна вещь, которую делает goimports
, — это принудительно устанавливает определенный порядок сортировки для вашего импорта. Попытка поддерживать порядок импорта вручную может быть утомительной и подверженной ошибкам, поэтому позвольте goimports
сделать это за вас.
Если команда Go изменит официальный формат исходных файлов Go, они обновят goimports
, чтобы отразить эти изменения, поэтому вам следует периодически обновлять goimports
, чтобы ваш код всегда соответствовал последние стандарты.
Заключение
В этом руководстве вы создали и запустили две программы длиной менее десяти строк, используя популярные пакеты, помогающие генерировать случайные числа и UUID. Экосистема Go настолько богата хорошо написанными пакетами, что написание новой программы на Go должно быть удовольствием, и вы обнаружите, что пишете полезные программы, соответствующие вашим конкретным потребностям, с меньшими усилиями, чем вы могли бы подумать.
Ознакомьтесь со следующим руководством из этой серии «Как использовать модули Go», чтобы понять, как группировать пакеты вместе и распространять их среди других как единое целое.