最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Go語言如何實(shí)現(xiàn)類似Python中的with上下文管理器

 更新時間:2023年07月03日 10:01:45   作者:江湖十年  
熟悉?Python?的同學(xué)應(yīng)該知道?Python?中的上下文管理器非常好用,那么在?Go?中是否也能實(shí)現(xiàn)上下文管理器呢,下面小編就來和大家仔細(xì)講講吧

熟悉 Python 的同學(xué)應(yīng)該知道 Python 中的上下文管理器非常好用,在對數(shù)據(jù)庫進(jìn)行讀寫、訪問文件等操作時,上下文管理器能夠確保資源在使用后得到釋放。在 Go 中是否也能實(shí)現(xiàn)上下文管理器呢?這便是本文所要探討的話題。

Python 上下文管理器

以操作文件為例,為了保證操作文件完成后資源能被正確關(guān)閉,在 Python 中我們可以編寫出如下代碼:

try:
    f = open('foo.txt', 'r')
    print(f.readlines())
finally:
    f.close()

不過這種寫法顯然不夠 Pythonic,Python 在語法層面提供了 with 語句實(shí)現(xiàn)上下文管理,用法如下:

with open('foo.txt', 'r') as f:
    print(f.readlines())

這段使用 with 語句實(shí)現(xiàn)的代碼,才更符合 Python 哲學(xué)。

如果你對 Python with 語法不熟悉,可以參閱我的文章《Python 上下文管理器實(shí)現(xiàn)》。

Go 中資源釋放問題

我們知道,在 Go 語言中訪問數(shù)據(jù)庫、文件等資源時,可以使用 defer 語句完成資源釋放操作。

如下定義一個 ReadFile 函數(shù)用來讀取文件:

func ReadFile(paths []string) error {
	for _, path := range paths {
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()
		content, err := io.ReadAll(file)
		if err != nil {
			return err
		}
		fmt.Printf("%s content: %s\n", file.Name(), content)
	}
	return nil
}

這個函數(shù)使用循環(huán)遍歷傳進(jìn)來的文件路徑列表,依次打開文件并輸出文件內(nèi)容。

為了保證即使在遇到錯誤時,資源也能夠被釋放,我們往往會使用 defer file.Close() 來關(guān)閉文件。

不過,這段代碼其實(shí)是存在問題的,我們知道 defer 的調(diào)用實(shí)際上并不會立即執(zhí)行,而是等到函數(shù)退出時才會執(zhí)行。

所以,代碼中的 defer 調(diào)用并不會在本輪循環(huán)中處理完當(dāng)前文件時被執(zhí)行,而是直到所有循環(huán)執(zhí)行完成,函數(shù)退出時才會執(zhí)行。

我們可以對以上示例稍作修改,來驗(yàn)證下這個問題:

func ReadFile(paths []string) error {
	for _, path := range paths {
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer func() {
			file.Close()
			fmt.Printf("close %s\n", file.Name())
		}()
		content, err := io.ReadAll(file)
		if err != nil {
			return err
		}
		fmt.Printf("%s content: %s\n", file.Name(), content)
	}
	return nil
}

我們將原來的 defer 語句改成:

defer func() {
    file.Close()
    fmt.Printf("close %s\n", file.Name())
}()

以此來顯示 defer 調(diào)用時機(jī)。

針對以上示例,我們使用如下代碼來調(diào)用:

func main() {
	err := ReadFile([]string{"foo.txt", "bar.txt"})
	fmt.Printf("ReadFile err: %v\n", err)
}

注意:foo.txtbar.txt 兩個文件我已經(jīng)提前準(zhǔn)備好了,foo.txt 文件內(nèi)容為 foo,bar.txt 文件內(nèi)容為 bar。

執(zhí)行以上示例,得到如下輸出:

$ go run main.go
foo.txt content: foo
bar.txt content: bar
close bar.txt
close foo.txt
ReadFile err: <nil>

根據(jù)輸出內(nèi)容可以驗(yàn)證,defer 語句的調(diào)用,的確在 for 循環(huán)退出以后才開始執(zhí)行。

如果打開資源過多,而沒有及時關(guān)閉,勢必會造成資源的浪費(fèi),甚至因此而意外終止程序。

所以切記,不要在循環(huán)中使用 defer

我們可以使用匿名函數(shù)來解決這個問題:

func ReadFile(paths []string) error {
	for _, path := range paths {
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		err = func() error {
			defer func() {
				file.Close()
				fmt.Printf("close %s\n", file.Name())
			}()
			content, err := io.ReadAll(file)
			if err != nil {
				return err
			}
			fmt.Printf("%s content: %s\n", file.Name(), content)
			return nil
		}()
		if err != nil {
			return err
		}
	}
	return nil
}

現(xiàn)在,將 defer 語句放入到一個立即執(zhí)行的匿名函數(shù)中,就可以解決問題了。

執(zhí)行以上示例,得到如下輸出:

$ go run main.go
foo.txt content: foo
close foo.txt
bar.txt content: bar
close bar.txt
ReadFile err: <nil>

可以發(fā)現(xiàn),現(xiàn)在 defer 語句不再是等到 for 循環(huán)退出才會執(zhí)行,而是在匿名函數(shù)退出時即可執(zhí)行。

這樣,就達(dá)到了在本輪循環(huán)中盡早釋放不再使用的文件資源的目的。

此外,為了代碼的可讀性,我們可以將匿名函數(shù)提取出來,單獨(dú)封裝一個函數(shù):

func ReadFile(paths []string) error {
	for _, path := range paths {
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		err = processFile(file)
		if err != nil {
			return err
		}
	}
	return nil
}
func processFile(file *os.File) error {
	defer func() {
		file.Close()
		fmt.Printf("close %s\n", file.Name())
	}()
	content, err := io.ReadAll(file)
	if err != nil {
		return err
	}
	fmt.Printf("%s content: %s\n", file.Name(), content)
	return nil
}

processFile 函數(shù)專門用來處理打開的文件,ReadFile 函數(shù)可讀性也得到了提高。

執(zhí)行以上示例,得到如下輸出:

go run main.go
foo.txt content: foo
close foo.txt
bar.txt content: bar
close bar.txt
ReadFile err: <nil>

這個輸出符合預(yù)期。

以上我們介紹了兩種方式,能夠解決 defer 語句延遲調(diào)用的問題。

在 Go 中實(shí)現(xiàn)上下文管理器

最近為了寫《Go 語言中 database/sql 是如何設(shè)計(jì)的》一文,我閱讀了下 database/sql 的源碼。在這個過程中,*sql.DB.queryDC 方法中一小段代碼激起了我的興趣:

func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []any) (*Rows, error) {
	...
	if ok {
		var nvdargs []driver.NamedValue
		var rowsi driver.Rows
		var err error
		withLock(dc, func() {
			nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
			if err != nil {
				return
			}
			rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs)
		})
		...
	}
	...
}

*sql.DB.queryDC 方法中有一個 withLock 函數(shù)的調(diào)用,withLock 函數(shù)定義如下:

func withLock(lk sync.Locker, fn func()) {
	lk.Lock()
	defer lk.Unlock()
	fn()
}

當(dāng)看到 withLock 函數(shù)定義時,我瞬間就想到了 Python 中的 with 上下文管理器。

withLock 接收一個 sync.Locker 接口,定義如下:

type Locker interface {
	Lock()
	Unlock()
}

它只有兩個方法,加鎖和釋放鎖。

withLock 能夠用于所有實(shí)現(xiàn) sync.Locker 接口的對象,在執(zhí)行 fn() 前加鎖,執(zhí)行之后釋放鎖。

這與 Python 的上下文管理器功能如出一轍,就是這么一個只有三行的小函數(shù),實(shí)現(xiàn)卻相當(dāng)精妙,真可謂短小精悍。

于是,參考 withLock 函數(shù)實(shí)現(xiàn),解決 for 循環(huán)中defer 語句延遲調(diào)用的問題,就有了第三種解法。

我們可以模仿 withLock 實(shí)現(xiàn)一個 WithClose 函數(shù):

func WithClose(closer io.Closer, fn func()) {
	defer func() {
		closer.Close()
		fmt.Printf("close %s\n", closer.(*os.File).Name())
	}()
	fn()
}

WithClose 接收一個 io.Closer 接口,定義如下:

type Closer interface {
	Close() error
}

我們可以在執(zhí)行 fn() 函數(shù)之前,使用 defer 語句來調(diào)用 io.CloserClose 方法釋放資源。

現(xiàn)在,我們可以在 ReadFile 函數(shù)中使用這個小函數(shù)了:

func ReadFile(paths []string) error {
	for _, path := range paths {
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		WithClose(file, func() {
			var content []byte
			content, err = io.ReadAll(file)
			if err != nil {
				return
			}
			fmt.Printf("%s content: %s\n", file.Name(), content)
		})
		if err != nil {
			return err
		}
	}
	return nil
}

這個用法同 *sql.DB.queryDC 中調(diào)用 withLock 函數(shù)一樣,并且因?yàn)殚]包的存在,我們可以拿到 WithClose 內(nèi)部執(zhí)行的 fn() 函數(shù)所產(chǎn)生的錯誤對象。

執(zhí)行以上示例,得到如下輸出:

$ go run main.go
foo.txt content: foo
close foo.txt
bar.txt content: bar
close bar.txt
ReadFile err: <nil>

這個輸出依然符合預(yù)期。

我們可以測試下遇到錯誤的情況,修改 main 函數(shù),調(diào)用 ReadFile 時最后傳入一個不存在的文件 baz.txt

func main() {
	err := ReadFile([]string{"foo.txt", "bar.txt", "baz.txt"})
	fmt.Printf("ReadFile err: %v\n", err)
}

執(zhí)行以上示例,得到如下輸出:

$ go run main.go
foo.txt content: foo
close foo.txt
bar.txt content: bar
close bar.txt
ReadFile err: open baz.txt: no such file or directory

遇到錯誤能夠被正常捕獲。

現(xiàn)在,我們就在 Go 中實(shí)現(xiàn)類了似 Python 中的 with 上下文管理器,為解決 for 循環(huán)中defer 語句延遲調(diào)用的問題提供了新思路。

總結(jié)

本文靈感來自于 database/sql 源碼中的一小段代碼,為大家講解了如何在 Go 中實(shí)現(xiàn)類似 Python 中的 with 上下文管理器。

切記,不要在循環(huán)中使用 defer。為了解決這個問題,我們可以使用匿名函數(shù)、函數(shù)封裝以及 WithClose 三種方案。

希望此文能對你有所幫助。

P.S.

database/sql 源碼中的這一小段代碼,找回了我在開始用 Go 作為主力語言后,很久沒有在編程語言語法層面上體會過快感。相較于我最近寫的幾篇長篇大論型文章,本文顯得微不足道,但我還是很樂于為這一小段代碼寫一篇文章分享出來,畢竟這久違的感覺又回來了。

從把 Go 作為主力編程語言開始,寫代碼的思路都是“平鋪直敘”,很少思考怎么寫出更加優(yōu)雅且有趣的代碼。盡管我也分享過幾篇 Go 編程模式的文章,但相較于用 Python 作為主力編程語言時,還是少了很多“花哨”的小技巧在里面,更多的是遵循套路的樣板代碼。

盡管 Go 語言的哲學(xué)更適合工程化,但 Go 代碼寫多了,有時不免會略感乏味,懷念 Python 的靈活。我無意于討論哪種編程語言的好壞,只是,愿在編程的道路上,你我都能找到屬于自己的樂趣所在。

以上就是詳解Go語言如何實(shí)現(xiàn)類似Python中的with上下文管理器的詳細(xì)內(nèi)容,更多關(guān)于Go語言上下文管理器的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang配置管理庫?Viper的教程詳解

    Golang配置管理庫?Viper的教程詳解

    這篇文章主要介紹了Golang?配置管理庫?Viper,使用?viper?能夠很好的去管理你的配置文件信息,比如數(shù)據(jù)庫的賬號密碼,服務(wù)器監(jiān)聽的端口,你可以通過更改配置文件去更改這些內(nèi)容,而不用定位到那一段代碼上去,提高了開發(fā)效率,需要的朋友可以參考下
    2022-05-05
  • Golang 限流器的使用和實(shí)現(xiàn)示例

    Golang 限流器的使用和實(shí)現(xiàn)示例

    這篇文章主要介紹了Golang 限流器的使用和實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • golang讀寫分離sync.Map的使用

    golang讀寫分離sync.Map的使用

    sync.Map是Go語言的并發(fā)安全映射,通過讀寫分離優(yōu)化讀性能,本文主要介紹了golang讀寫分離sync.Map的使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-05-05
  • 淺談go-restful框架的使用和實(shí)現(xiàn)

    淺談go-restful框架的使用和實(shí)現(xiàn)

    這篇文章主要介紹了淺談go-restful框架的使用和實(shí)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • golang 交叉編譯C++ dll配置文件的實(shí)現(xiàn)

    golang 交叉編譯C++ dll配置文件的實(shí)現(xiàn)

    本文探討了在64位環(huán)境下調(diào)用32位C++ DLL的的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-07-07
  • golang之判斷元素是否在數(shù)組內(nèi)問題

    golang之判斷元素是否在數(shù)組內(nèi)問題

    這篇文章主要介紹了golang之判斷元素是否在數(shù)組內(nèi)問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2020-12-12
  • 一文秒懂Go 編寫命令行工具的代碼

    一文秒懂Go 編寫命令行工具的代碼

    這篇文章主要介紹了一文秒懂Go 編寫命令行工具的代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Go JSON中序列化大整數(shù)精度丟失的問題分析

    Go JSON中序列化大整數(shù)精度丟失的問題分析

    當(dāng)存儲或傳輸 大整數(shù)(int64) 時,往往會出現(xiàn)精度丟失的問題,本文通過一個示例來詳細(xì)分析原因,并給出解決方案,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2026-01-01
  • go 異常處理panic和recover的簡單實(shí)踐

    go 異常處理panic和recover的簡單實(shí)踐

    在Go語言中,異常處理主要通過panic和recover這兩個內(nèi)建函數(shù)來實(shí)現(xiàn),本文主要介紹了go異常處理panic和recover的簡單實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-04-04
  • Go語言題解LeetCode下一個更大元素示例詳解

    Go語言題解LeetCode下一個更大元素示例詳解

    這篇文章主要為大家介紹了Go語言題解LeetCode下一個更大元素示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12

最新評論

巴楚县| 江川县| 宁蒗| 华容县| 昌吉市| 华亭县| 绿春县| 涞源县| 秦皇岛市| 青海省| 五大连池市| 兰溪市| 扬州市| 云林县| 涿州市| 西安市| 当雄县| 洛南县| 横峰县| 上犹县| 封丘县| 安丘市| 澳门| 昌邑市| 辽源市| 东城区| 城口县| 左权县| 永兴县| 壶关县| 茶陵县| 巴马| 南丰县| 七台河市| 梁河县| 正镶白旗| 古丈县| 台中市| 岳西县| 南部县| 台东县|