importdeklarationer I Go

michajlowicki

michajlowicki

följ

jan 15, 2018 · 6 min läs

program i go består av paket. Vanligtvis paket beror på andra paket antingen de inbyggda i standardbiblioteket eller 3: e part. Paketet måste importeras först för att använda sina exporterade identifierare. Det är gjort med konstruktion som heter importdeklaration:

package mainimport (
"fmt"
"math"
)func main() {
fmt.Println(math.Exp2(10)) // 1024
}

ovan har vi en importdeklaration med två ImportSpecs. Varje ImportSpec definierar import av ett enda paket.

paket som heter main används för att skapa körbar binär. Programkörning startar i paketet main genom att anropa dess funktion som också kallas main.

men… det finns andra, mindre kända alternativ som är användbara i olika scenarier:

import (
"math"
m "math"
. "math"
_ "math"
)

var och en av dessa fyra importspecifikationer beter sig annorlunda och i det här inlägget analyserar vi dessa skillnader.

Importpaket kan endast referera till exporterade identifierare från importerat paket. Exporterade identifierare är de som startas med Unicode versaler – https://golang.org/ref/spec#Exported_identifiers.

Anatomi av importdeklaration

ImportDeclaration = "import" ImportSpec
ImportSpec = ImportPath
  • identifierare är en giltig identifierare som kommer att användas i kvalificerade identifierare
  • ImportPath är sträng bokstavlig (rå eller tolkad)

Låt oss se några exempel:

import . "fmt"
import _ "io"
import log "github.com/sirupsen/logrus"
import m "math"

faktorerad importdeklaration

importera två eller flera paket kan skrivas på två sätt. Antingen kan vi skriva flera importdeklarationer:

import "io"
import "bufio"

eller vi kan använda factored importdeklaration (med flera ImportSpec inom en enda importdeklaration):

import (
"io"
"bufio"
)

2: a alternativet är särskilt användbart om paketet har många import och sedan upprepa import sökord många gånger minskar läsbarheten. Det sparar också några tangenttryckningar om du inte använder verktyg som https://github.com/bradfitz/goimports som fixar importen automatiskt.

(kort) importväg

sträng bokstav som används i importspecifikationen (varje importdeklaration innehåller en eller flera importspecifikationer) anger vilket paket som ska importeras. Denna sträng kallas importväg. Enligt språkspecifikation beror det på implementeringen hur import path (string) tolkas men i verkligheten är det path relative paketets leverantörskatalog eller `go env GOPATH`/src (mer om GOPATH).

inbyggda paket importeras med korta importvägar som "math" eller "fmt".

Anatomi av .gå fil

struktur av varje .go-filen är densamma. Först är paket klausul eventuellt föregås med kommentarer vanligtvis beskriver syftet med paketet. Sedan noll eller fler importdeklarationer. 3: e avsnittet innehåller noll eller fler toppnivådeklarationer (källkod):

// description...
package main // package clause// zero or more import declarations
import (
"fmt"
"strings"
)import "strconv"// top-level declarationsfunc main() {
fmt.Println(strings.Repeat(strconv.FormatInt(15, 16), 5))
}

Enforced organization tillåter inte att införa onödig röra som förenklar tolkning och i princip navigering genom kodbasen (importdeklarationer kan inte placeras före paketklausulen eller interfolieras med toppnivådeklarationer så det är alltid lätt att hitta).

omfattning av import

omfattningen av import är filblocket. Det betyder att det kan nås från hela filen men inte inom hela paketet:

// github.com/mlowicki/a/main.go
package mainimport "fmt"func main() {
fmt.Println(a)
}// github.com/mlowicki/a/foo.go
package mainvar a int = 1func hi() {
fmt.Println("Hi!")
}

sådant program kan inte kompileras:

> go build
# github.com/mlowicki/a
./foo.go:6:2: undefined: fmt

mer om omfattningar i ett av tidigare inlägg:

anpassat Paketnamn

enligt konvention är den sista komponenten i importvägen också namnet på importerat paket. Naturligtvis hindrar ingenting oss från att inte följa den konventionen:

# github.com/mlowicki/main.go
package mainimport (
"fmt"
"github.com/mlowicki/b"
)func main() {
fmt.Println(c.B)
}# github.com/mlowicki/b/b.go
package cvar B = "b"

utgången är helt enkelt b. Även om det är möjligt är det vanligtvis bättre att följa konventionen — olika verktyg beror på det.

om anpassat Paketnamn inte anges i importspecifikationen används name from package-satsen för att referera till exporterade identifierare från importerat paket:

package mainimport "fmt"func main() {
fmt.Println("Hi!")
}

det är möjligt att skicka anpassat Paketnamn för import:

# github.com/mlowicki/b/b.go
package bvar B = "b"package mainimport (
"fmt"
c "github.com/mlowicki/b"
)func main() {
fmt.Println(c.B)
}

och resultatet är detsamma som tidigare. Denna form av import kan vara användbar om vi har paket som har samma gränssnitt (exporterade identifierare) som andra paket. Ett sådant exempel är https://github.com/sirupsen/logrus som har ett API som är kompatibelt med log:

import log "github.com/sirupsen/logrus"

Om vi bara använder API som finns i det inbyggda loggpaketet och ersätter sådan import med import "log" kräver inga ändringar i källkoden. Det är också lite kortare (men ändå meningsfullt) så kan spara några tangenttryckningar.

alla exporterade identifierare till importblock

med import som:

import m "math"
import "fmt"

det är möjligt att antingen referera exporterad identifierare med Paketnamn som skickas i importspecifikationen (m.Exp) eller med namnet från paketklausulen för importerat paket (fmt.Println). Det finns ett annat alternativ som tillåter åtkomst till exporterad identifierare utan kvalificerad identifierare:

package mainimport (
"fmt"
. "math"
)func main() {
fmt.Println(Exp2(6)) // 64
}

När kan det vara användbart? I tester. Låt oss anta att vi har Paket a som importeras av paket b. Nu vill vi lägga till tester i paket a. Om tester också kommer att finnas i paket a och tester också kommer att importera paket b (för då behöver något implementerat där) kommer vi att sluta med cirkulärt beroende vilket är förbjudet. Ett sätt att kringgå det är att sätta test i separat paket som a_tests. Då måste vi importera paket a och referera till varje exporterad identifierare med kvalificerad identifierare. För att göra vårt liv enklare kan vi importera paket a med dot :

import . "a"

och sedan referera exporterade identifierare från Paket A utan Paketnamn (precis som när tester fanns i samma paket men icke-exporterade identifierare är inte tillgängliga).

det är omöjligt att importera två paket med dot som Paketnamn om de har minst en exporterad identifierare gemensamt:

# github.com/mlowicki/c
package cvar V = "c"# github.com/mlowkci/b
package bvar V = "b"# github.com/mlowicki/a
package mainimport (
"fmt"
. "github.com/mlowicki/b"
. "github.com/mlowicki/c"
)func main() {
fmt.Println(V)
}> go run main.go
# command-line-arguments
./main.go:6:2: V redeclared during import "github.com/mlowicki/c"
previous declaration during import "github.com/mlowicki/b"
./main.go:6:2: imported and not used: "github.com/mlowicki/c"

Importera med blank identifierare

Golangs kompilator skriker om paketet importeras och inte används (källkod):

package mainimport "fmt"func main() {}

Importera med punkt där alla exporterade identifierare läggs direkt till importera filblock misslyckas också när du bygger källkod. Den enda varianten som fungerar är den med blank identifierare. Det krävs att veta vilka init-funktioner som är för att förstå varför behöver vi importera med blank identifierare. Under hela introduktionen till init-funktioner har behandlats i en av tidigare berättelser:

Jag uppmuntrar att läsa den från topp till botten men i huvudsak importera som:

import _ "math"

behöver inte använda paketmatematik vid import av fil men init-funktion(er) från importerat paket kommer att utföras ändå (paket och IT-beroenden kommer att initieras). Det är användbart om vi bara är intresserade av bootstrapping-arbete som utförs av importerat paket men vi refererar inte till några exporterade identifierare från det.

kompilering misslyckas om paketet importeras utan blank identifierare och inte används alls.

cirkulär import

Go — specifikationen förbjuder uttryckligen cirkulär import-när paketet importerar sig indirekt. Det mest uppenbara fallet är när Paket A importerar paket b och paket b i sin tur importerar paket a:

# github.com/mlowicki/a/main.go
package aimport "github.com/mlowicki/b"var A = b.B# github.com/mlowicki/b/main.go
package bimport "github.com/mlowicki/a"var B = a.A

ett försök att bygga något av dessa två paket slutar med ett fel:

> go build
can't load package: import cycle not allowed
package github.com/mlowicki/a
imports github.com/mlowicki/b
imports github.com/mlowicki/a

Naturligtvis kan det vara mer komplicerat scenario som en/p>

paketet kan inte importera sig varken:

package mainimport (
"fmt"
"github.com/mlowicki/a"
)var A = "a"func main() {
fmt.Println(a.A)
}

Compiling such package also gives an error: can’t load package: import cycle not allowed.

Lämna ett svar

Din e-postadress kommer inte publiceras.