Erstellen und Importieren von Go-Paketen

GolangBeginner
Jetzt üben

Einführung

Im vorherigen Abschnitt haben Sie ein einfaches Go-Programm erstellt, das die folgenden Codezeilen enthielt:

package main
import "fmt"

Wie verstehen wir diese beiden Codezeilen? Wie können wir die Anweisungen package und import effektiv nutzen?

In diesem Lab (Praktikum) werden Sie lernen, wie Sie Pakete in Go erstellen und importieren. Dies ermöglicht es Ihnen, Ihren Code in wiederverwendbare Module zu organisieren, wodurch Ihre Go-Projekte wartbarer und skalierbarer werden.

Wissenspunkte:

  • Definition und Deklaration eines Pakets
  • Verständnis von exportierten (öffentlichen) und nicht-exportierten (privaten) Bezeichnern
  • Verschiedene Formen des Paketimports: Einzelimport, gruppierter Import, Punkt-Import, Alias-Import und anonymen Import

Deklarieren und Definieren von Paketen

In diesem Schritt deklarieren Sie ein wiederverwendbares Paket und unterscheiden exportierte Bezeichner von paketinternen Bezeichnern.

Ein Paket in Go ähnelt Modulen in Python oder Bibliotheken in C. Es ist eine Sammlung von Quellcode-Dateien, die dazu dienen, Code zu organisieren und wiederzuverwenden. Jede Go-Datei muss am Anfang der Datei ein Paket deklarieren.

Hinweis: Ein ausführbares Go-Programm wird aus einem Paket main mit einer Funktion main gebaut. Wiederverwendbare Pakete benötigen diesen Einstiegspunkt nicht. Ein Modul kann mehrere Befehlsverzeichnisse enthalten, die jeweils ein eigenes Paket main haben.

Wichtige Punkte:

Exportierte (öffentliche) Bezeichner: Bezeichner (Variablen, Funktionen, Typen usw.), die mit einem Großbuchstaben beginnen, sind von anderen Paketen zugänglich. Denken Sie an diese als die öffentliche Schnittstelle Ihres Pakets.

Nicht-exportierte (private) Bezeichner: Bezeichner, die mit einem Kleinbuchstaben beginnen, sind nur innerhalb desselben Pakets zugänglich. Diese gelten als interne Implementierungsdetails des Pakets.

Paketkohesion: Alle Dateien in demselben Ordner müssen zum gleichen Paket gehören. Dies stellt sicher, dass verwandter Code zusammengehalten wird.

Paketnamenskonventionen: Paketnamen sollten klein geschrieben, kurz und beschreibend sein und Unterstriche oder Großbuchstaben vermeiden.

Lassen Sie uns unser eigenes benutzerdefiniertes Paket erstellen:

Erstellen Sie einen Ordner namens propagandist und eine Datei propagandist.go darin:

mkdir ~/project/propagandist
touch ~/project/propagandist/propagandist.go

Schreiben Sie den folgenden Code in propagandist.go:

package propagandist

var Shout = "I Love LabEx" // Public variable
var secret = "I love the dress" // Private variable

func Hit() string {
    return "Don't hit me, please!"
}
  • Shout ist öffentlich, da es mit einem Großbuchstaben beginnt. Das bedeutet, dass Sie darauf von anderen Paketen zugreifen können, die propagandist importieren.
  • secret ist privat, da es mit einem Kleinbuchstaben beginnt. Es kann nur innerhalb des propagandist-Pakets verwendet werden.
  • Hit ist eine öffentliche Funktion, auf die von anderen Paketen zugegriffen werden kann.

Initialisieren Sie ein Go-Modul für das Paket:

cd ~/project/propagandist
go mod init propagandist

Dieser Befehl initialisiert ein neues Go-Modul im propagandist-Verzeichnis, das hilft, die Abhängigkeiten für das Paket zu verwalten.

Einzelner Import

In diesem Schritt importieren und verwenden Sie Ihr lokales Paket mit separaten import-Deklarationen.

Um das propagandist-Paket zu verwenden, erstellen wir ein neues Go-Programm. Dieser Schritt zeigt, wie Sie ein einzelnes Paket in Ihrem Go-Code importieren und nutzen können.

Erstellen Sie eine neue Go-Datei namens pacExercise.go im Projektordner:

touch ~/project/pacExercise.go

Initialisieren Sie ein Go-Modul für das Programm:

cd ~/project
go mod init pacExercise

Aktualisieren Sie die go.mod-Datei, um die Abhängigkeit vom lokalen Paket hinzuzufügen. Führen Sie den folgenden Befehl im Terminal aus:

echo "replace propagandist => ./propagandist" >> go.mod

Wichtig: Dieser Befehl fügt eine replace-Direktive in Ihre go.mod-Datei ein. Dies ist wichtig, da es Go mitteilt, dass das propagandist-Paket aus dem lokalen Verzeichnis ./propagandist bezogen werden soll, anstatt es von einem Remote-Repository herunterzuladen. Sie sollten diesen Befehl in Ihrem Terminal ausführen, wodurch die Zeile replace propagandist => ./propagandist an Ihre go.mod-Datei angehängt wird. Schreiben Sie diese Zeile nicht manuell direkt in die Datei.

Schreiben Sie den folgenden Code in pacExercise.go, um das propagandist-Paket zu importieren und zu verwenden:

package main

import "fmt"
import "propagandist"

func main() {
    fmt.Println(propagandist.Shout) // Access the public variable
}
  • Dieser Code importiert das fmt-Paket zum Ausgeben von Text und das propagandist-Paket.
  • Anschließend greift er auf die öffentliche Variable Shout aus dem propagandist-Paket über propagandist.Shout zu.

Führen Sie das Programm aus:

go mod tidy
go run pacExercise.go

Der Befehl go mod tidy stellt sicher, dass Ihre go.mod-Datei mit allen neuen Abhängigkeiten aktualisiert wird. Der Befehl go run pacExercise.go kompiliert und führt das Programm aus.

Erwartete Ausgabe:

I Love LabEx

Gruppierte Importe

In diesem Schritt gruppieren Sie Paketimporte, ohne das Programmverhalten zu ändern.

Wenn Sie mehrere Pakete importieren, können Sie gruppierte Importe verwenden, um die Lesbarkeit und Organisation Ihres Codes zu verbessern. Dies ist eine stilistische Wahl und ändert nicht die Funktionalität Ihres Codes.

Ändern Sie pacExercise.go, um gruppierte Importe zu verwenden:

 package main

 import (
     "fmt"
     "propagandist"
 )

 func main() {
     fmt.Println(propagandist.Shout)
 }
 ```

Im obigen Codeausschnitt werden die Pakete `fmt` und `propagandist` innerhalb eines einzelnen `import`-Blocks in Klammern importiert. Dies erleichtert das Lesen und Verwalten mehrerer Paketimporte. Anders als die separaten import-Deklarationen im vorherigen Schritt fasst dies beide Paketpfade in einer Deklaration zusammen. Das Programmverhalten bleibt gleich.

Führen Sie das Programm aus, um zu bestätigen, dass es weiterhin funktioniert:

```bash
 go run pacExercise.go
 ```

Das Programm sollte ohne Fehler ausgeführt werden und das gleiche Ergebnis wie zuvor ausgeben.


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="punkt-import"></div>

## Punkt-Import

In diesem Schritt untersuchen Sie Punkt-Importe und die damit verbundenen Vor- und Nachteile im Namensraum.

Mit einem **Punkt-Import** können Sie das Paketnamen-Präfix weglassen, wenn Sie Funktionen oder Variablen aus diesem Paket aufrufen. Davon wird häufig zugunsten expliziter Paketnamen abgeraten, da es zu Namensraumkonflikten führen und die Lesbarkeit verringern kann. Dennoch ist es gut, zu wissen, was es ist.

Ändern Sie `pacExercise.go`, um einen Punkt-Import für `fmt` zu verwenden:

```go
 package main

 import . "fmt"
 import "propagandist"

 func main() {
     Println(propagandist.Shout) // No `fmt.` prefix needed
 }
 ```

- Hier bedeutet `import . "fmt"`, dass Sie Funktionen und Variablen aus dem `fmt`-Paket direkt ohne das `fmt.`-Präfix verwenden können.
- Beispielsweise verwenden Sie `Println` anstelle von `fmt.Println`.

Führen Sie das Programm aus:

```bash
 go run pacExercise.go
 ```

Erwartete Ausgabe:

```bash
 I Love LabEx
 ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="alias-import"></div>

## Alias-Import

In diesem Schritt geben Sie einem importierten Paket einen lokalen Alias.

Sie können ein importiertes Paket mit einem Alias versehen, um die Klarheit zu erhöhen oder Konflikte zu vermeiden, wenn zwei Pakete ähnliche Namen haben. Dies hilft, Ihren Code lesbarer zu gestalten und Namensraum-Kollisionen zu verwalten.

Ändern Sie `pacExercise.go`, um `fmt` mit dem Alias `io` zu versehen:

```go
 package main

 import io "fmt"
 import "propagandist"

 func main() {
     io.Println(propagandist.Shout) // Use the alias `io` instead of `fmt`
 }
 ```

- `import io "fmt"` erstellt einen Alias `io` für das `fmt`-Paket.
- Jetzt verwenden Sie `io.Println` anstelle von `fmt.Println`.

Führen Sie das Programm aus:

```bash
 go run pacExercise.go
 ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="anonymer-import"></div>

## Anonymer Import

In diesem Schritt importieren Sie ein Paket ausschließlich wegen der Nebeneffekte seiner Initialisierung.

Anonyme Importe werden verwendet, um ein Paket wegen seiner Nebeneffekte zu importieren, wie z. B. das Ausführen seiner `init()`-Funktion, ohne dass es erforderlich ist, direkt auf seine exportierten Bezeichner zu verweisen. Dies ist nützlich für Pakete, die Treiber registrieren oder andere Initialisierungsaufgaben ausführen.

Ändern Sie `pacExercise.go`, um einen anonymen Import für `time` einzufügen:

```go
 package main

 import (
     "fmt"
     "propagandist"
     _ "time" // Anonymous import
 )

 func main() {
     fmt.Println(propagandist.Shout)
 }
 ```

- `import _ "time"` ist ein anonymer Import. Der Unterstrich `_` wird als leerer Bezeichner verwendet, um dem Compiler mitzuteilen, dass Sie das Paket wegen seiner Nebeneffekte importieren und in Ihrem Code nicht direkt auf etwas aus diesem Paket verweisen werden.
- Die `init()`-Funktion des `time`-Pakets wird ausgeführt, wenn dieses Programm läuft. Das `time`-Paket hat hier keine sichtbaren besonderen Nebeneffekte. Viele Pakete nutzen dies jedoch, um Datenbanktreiber oder Konfigurationseinstellungen zu registrieren.

Führen Sie das Programm aus:

```bash
 go run pacExercise.go
 ```

Erwartete Ausgabe:

```bash
 I Love LabEx
 ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="die-funktion-init()-verstehen"></div>

## Die Funktion init() verstehen

In diesem Schritt verfolgen Sie die Initialisierung von Paketvariablen und die Ausführung von init()-Funktionen, bevor main() ausgeführt wird.

Die Funktion `init()` ist eine besondere Funktion in Go, die eine wichtige Rolle bei der Paketinitialisierung spielt. Go führt sie beim Importieren eines Pakets automatisch aus, bevor Code in der Funktion `main` ausgeführt wird. Dieser Abschnitt erläutert die Einzelheiten von `init()`-Funktionen und ihre Rolle im Initialisierungsprozess von Go.

**Wichtige Eigenschaften von `init()`-Funktionen:**

**Definition und Zweck:**

- Eine `init()`-Funktion hat weder Parameter noch Rückgabewerte: `func init() {}`
- Sie dient Initialisierungsaufgaben wie dem Einrichten eines Anfangszustands, dem Registrieren von Treibern oder dem Prüfen von Voraussetzungen.

**Ausführungsreihenfolge:**

- Go garantiert, dass jedes Paket nur einmal initialisiert wird, auch wenn es mehrfach importiert wird.
- Die Initialisierung folgt einer festgelegten Reihenfolge:
- Zuerst werden Variablen auf Paketebene initialisiert.
- Anschließend werden die `init()`-Funktionen ausgeführt.
- Zuletzt wird die Funktion `main()` ausgeführt (nur im Paket main).

**Mehrere `init()`-Funktionen:**

- Eine einzelne Go-Datei kann mehrere `init()`-Funktionen enthalten.
- Mehrere Dateien desselben Pakets können jeweils eigene `init()`-Funktionen haben.
- Alle diese `init()`-Funktionen werden in der Reihenfolge ihrer Deklarationen im Quellcode ausgeführt. Dabei gilt die Reihenfolge, in der die Dateien dem Compiler übergeben werden. Das Go-Werkzeug übergibt die Paketdateien in lexikografischer Reihenfolge ihrer Dateinamen. Verlassen Sie sich nicht auf die Dateireihenfolge, um Abhängigkeiten auszudrücken.

**Abhängigkeitskette:**

- Wenn Pakete andere Pakete importieren, stellt Go sicher, dass die `init()`-Funktionen der Abhängigkeiten zuerst ausgeführt werden.
- Dadurch entsteht ein Initialisierungsablauf von unten nach oben: Die tiefsten Abhängigkeiten werden zuerst initialisiert.

Erstellen wir ein praktisches Beispiel, das die Funktionsweise von `init()` zeigt:

Ergänzen Sie zuerst unser Paket `propagandist` um eine `init()`-Funktion. Aktualisieren Sie `propagandist.go`:

```go
package propagandist

import "fmt"

var Shout = "I Love LabEx" // Public variable
var secret = "I love the dress" // Private variable
var initialized bool

func init() {
    fmt.Println("Initializing propagandist package...")
    initialized = true
}

func Hit() string {
    return "Don't hit me, please!"
}

func IsInitialized() bool {
    return initialized
}

Erstellen Sie nun eine weitere Datei im Paket propagandist, um mehrere init()-Funktionen zu demonstrieren:

touch ~/project/propagandist/second.go

Fügen Sie der Datei folgenden Inhalt hinzu:

package propagandist

import "fmt"

func init() {
    fmt.Println("Second init function in propagandist package...")
}

Erstellen Sie ein neues helper-Paket, um die Initialisierungsreihenfolge zu zeigen:

mkdir -p ~/project/helper
touch ~/project/helper/helper.go

Fügen Sie der Datei folgenden Inhalt hinzu:

package helper

import "fmt"

var Message = "Helper package is ready"

func init() {
    fmt.Println("Initializing helper package...")
}

func GetMessage() string {
    return Message
}

Fügen Sie die Moduldatei für das Paket helper hinzu:

cd ~/project/helper
go mod init helper

Aktualisieren Sie pacExercise.go, um beide Pakete zu verwenden und die Initialisierungsreihenfolge zu zeigen:

package main

import (
    "fmt"
    "helper"
    "propagandist"
)

func init() {
    fmt.Println("Initializing main package...")
}

func main() {
    fmt.Println("Main function is running")
    fmt.Println(propagandist.Shout)
    fmt.Println(helper.Message)
    fmt.Printf("Propagandist initialized: %v\n", propagandist.IsInitialized())
}

Aktualisieren Sie die Datei go.mod im Hauptprojekt, um das lokale Paket helper einzubinden:

cd ~/project
echo "replace helper => ./helper" >> go.mod
go mod tidy

Führen Sie das Programm aus und beobachten Sie die Initialisierungsreihenfolge:

go run pacExercise.go

Erwartete Ausgabe mit den vorgegebenen Dateinamen und dem Go-Werkzeug:

Initializing helper package...
Initializing propagandist package...
Second init function in propagandist package...
Initializing main package...
Main function is running
I Love LabEx
Helper package is ready
Propagandist initialized: true

Diese Ausgabe verdeutlicht die wesentlichen Aspekte des Initialisierungsprozesses von Go:

Abhängige Pakete werden vor den Paketen initialisiert, die sie importieren.

Innerhalb eines Pakets werden alle init()-Funktionen in Deklarationsreihenfolge ausgeführt, entsprechend der Reihenfolge, in der die Quelldateien dem Compiler übergeben werden.

Die Funktion main() wird erst ausgeführt, nachdem alle Paketinitialisierungen abgeschlossen sind.

Variablen auf Paketebene werden vor der Ausführung von init()-Funktionen initialisiert.

Diese Initialisierungsfolge ist beim Entwerfen von Go-Paketen wichtig, insbesondere beim Verwalten von Abhängigkeiten oder bei Einrichtungsaufgaben, die in einer bestimmten Reihenfolge erfolgen müssen.

Zusammenfassung

In diesem Lab haben Sie gelernt:

  1. Wie man benutzerdefinierte Pakete in Go erstellt und definiert, um wiederverwendbaren Code zu kapseln.

  2. Der Unterschied zwischen öffentlichen (exportierten) und privaten (nicht exportierten) Bezeichnern und wie sie die Zugänglichkeit beeinflussen.

  3. Verschiedene Methoden zum Importieren von Paketen, jede mit ihrem Anwendungsfall:

    • Einzelner Import: Importieren eines Pakets nach dem anderen.
    • Gruppierter Import: Importieren mehrerer Pakete in einem einzigen Block für eine bessere Organisation.
    • Punkt-Import: Importieren eines Pakets und direktes Verwenden seiner Bezeichner ohne das Paketnamen-Präfix. (Mit Vorsicht verwenden)
    • Alias-Import: Umbenennen importierter Pakete für eine bessere Lesbarkeit oder zur Vermeidung von Namenskonflikten.
    • Anonymer Import: Importieren eines Pakets ausschließlich wegen seiner Nebeneffekte, wie z. B. Initialisierung.
  4. Die Rolle der init()-Funktion in Paketen und wie anonyme Importe deren Ausführung auslösen können.

  5. Die Einzelheiten des Initialisierungsprozesses von Go, darunter:

    • Wie Variablen auf Paketebene vor den init()-Funktionen initialisiert werden.
    • Die garantierte Ausführungsreihenfolge von init()-Funktionen über abhängige Pakete hinweg.
    • Wie mehrere init()-Funktionen innerhalb eines Pakets funktionieren.
    • Den vollständigen Initialisierungsablauf von den abhängigen Paketen bis zur Hauptfunktion.

Durch die Absolvierung dieses Labs sind Sie nun in der Lage, Go-Projekte effektiv mit Paketen zu strukturieren und zu verwalten. Sie können wiederverwendbare Module erstellen, den Zugang zu Bezeichnern steuern, Ihren Code besser organisieren und den Initialisierungsprozess verstehen, was zu wartbareren und skalierbaren Go-Anwendungen führt.