Go 語言 包的匿名導(dǎo)入

2023-03-14 16:59 更新

原文鏈接:https://gopl-zh.github.io/ch10/ch10-05.html


10.5. 包的匿名導(dǎo)入

如果只是導(dǎo)入一個(gè)包而并不使用導(dǎo)入的包將會導(dǎo)致一個(gè)編譯錯(cuò)誤。但是有時(shí)候我們只是想利用導(dǎo)入包而產(chǎn)生的副作用:它會計(jì)算包級變量的初始化表達(dá)式和執(zhí)行導(dǎo)入包的init初始化函數(shù)(§2.6.2)。這時(shí)候我們需要抑制“unused import”編譯錯(cuò)誤,我們可以用下劃線_來重命名導(dǎo)入的包。像往常一樣,下劃線_為空白標(biāo)識符,并不能被訪問。

import _ "image/png" // register PNG decoder

這個(gè)被稱為包的匿名導(dǎo)入。它通常是用來實(shí)現(xiàn)一個(gè)編譯時(shí)機(jī)制,然后通過在main主程序入口選擇性地導(dǎo)入附加的包。首先,讓我們看看如何使用該特性,然后再看看它是如何工作的。

標(biāo)準(zhǔn)庫的image圖像包包含了一個(gè)Decode函數(shù),用于從io.Reader接口讀取數(shù)據(jù)并解碼圖像,它調(diào)用底層注冊的圖像解碼器來完成任務(wù),然后返回image.Image類型的圖像。使用image.Decode很容易編寫一個(gè)圖像格式的轉(zhuǎn)換工具,讀取一種格式的圖像,然后編碼為另一種圖像格式:

gopl.io/ch10/jpeg

// The jpeg command reads a PNG image from the standard input
// and writes it as a JPEG image to the standard output.
package main

import (
    "fmt"
    "image"
    "image/jpeg"
    _ "image/png" // register PNG decoder
    "io"
    "os"
)

func main() {
    if err := toJPEG(os.Stdin, os.Stdout); err != nil {
        fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
        os.Exit(1)
    }
}

func toJPEG(in io.Reader, out io.Writer) error {
    img, kind, err := image.Decode(in)
    if err != nil {
        return err
    }
    fmt.Fprintln(os.Stderr, "Input format =", kind)
    return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
}

如果我們將gopl.io/ch3/mandelbrot(§3.3)的輸出導(dǎo)入到這個(gè)程序的標(biāo)準(zhǔn)輸入,它將解碼輸入的PNG格式圖像,然后轉(zhuǎn)換為JPEG格式的圖像輸出(圖3.3)。

$ go build gopl.io/ch3/mandelbrot
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
Input format = png

要注意image/png包的匿名導(dǎo)入語句。如果沒有這一行語句,程序依然可以編譯和運(yùn)行,但是它將不能正確識別和解碼PNG格式的圖像:

$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
jpeg: image: unknown format

下面的代碼演示了它的工作機(jī)制。標(biāo)準(zhǔn)庫還提供了GIF、PNG和JPEG等格式圖像的解碼器,用戶也可以提供自己的解碼器,但是為了保持程序體積較小,很多解碼器并沒有被全部包含,除非是明確需要支持的格式。image.Decode函數(shù)在解碼時(shí)會依次查詢支持的格式列表。每個(gè)格式驅(qū)動列表的每個(gè)入口指定了四件事情:格式的名稱;一個(gè)用于描述這種圖像數(shù)據(jù)開頭部分模式的字符串,用于解碼器檢測識別;一個(gè)Decode函數(shù)用于完成解碼圖像工作;一個(gè)DecodeConfig函數(shù)用于解碼圖像的大小和顏色空間的信息。每個(gè)驅(qū)動入口是通過調(diào)用image.RegisterFormat函數(shù)注冊,一般是在每個(gè)格式包的init初始化函數(shù)中調(diào)用,例如image/png包是這樣注冊的:

package png // image/png

func Decode(r io.Reader) (image.Image, error)
func DecodeConfig(r io.Reader) (image.Config, error)

func init() {
    const pngHeader = "\x89PNG\r\n\x1a\n"
    image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

最終的效果是,主程序只需要匿名導(dǎo)入特定圖像驅(qū)動包就可以用image.Decode解碼對應(yīng)格式的圖像了。

數(shù)據(jù)庫包database/sql也是采用了類似的技術(shù),讓用戶可以根據(jù)自己需要選擇導(dǎo)入必要的數(shù)據(jù)庫驅(qū)動。例如:

import (
    "database/sql"
    _ "github.com/lib/pq"              // enable support for Postgres
    _ "github.com/go-sql-driver/mysql" // enable support for MySQL
)

db, err = sql.Open("postgres", dbname) // OK
db, err = sql.Open("mysql", dbname)    // OK
db, err = sql.Open("sqlite3", dbname)  // returns error: unknown driver "sqlite3"

練習(xí) 10.1: 擴(kuò)展jpeg程序,以支持任意圖像格式之間的相互轉(zhuǎn)換,使用image.Decode檢測支持的格式類型,然后通過flag命令行標(biāo)志參數(shù)選擇輸出的格式。

練習(xí) 10.2: 設(shè)計(jì)一個(gè)通用的壓縮文件讀取框架,用來讀取ZIP(archive/zip)和POSIX tar(archive/tar)格式壓縮的文檔。使用類似上面的注冊技術(shù)來擴(kuò)展支持不同的壓縮格式,然后根據(jù)需要通過匿名導(dǎo)入選擇導(dǎo)入要支持的壓縮格式的驅(qū)動包。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號