Python 程式設計師的 Golang 學習指南(III): 入門篇

Cloudinsight發表於2016-10-14

Authors: startover


基礎語法

型別和關鍵字

  • 型別
// 基礎型別
布林型別: bool
整型: int8,uint8,int16,uint16,int32,uint32,int64,uint64,int,rune,byte,complex128, complex64,其中,byte 是 int8 的別名
浮點型別: float32 、 float64
複數型別: complex64 、 complex128
字串: string
字元型別: rune(int32的別名)
錯誤型別: error

// 複合型別
指標(pointer)
陣列(array)
切片(slice)
字典(map)
通道(chan)
結構體(struct)
介面(interface)
  • 關鍵字
break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

變數

Go 同其他語言不同的地方在於變數的型別在變數名的後面,不是 int a,而是 a int。至於為什麼這麼定義,Go 的官方部落格有給出解釋,有興趣的可以參考下。

變數定義語法如下:

var a int
a = 2

// 或者
a := 2

// 同時定義多個變數
var (
    a int
    b bool
)

// 同時給多個變數賦值
a, b := 2, true

操作符

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=

控制結構

Go 語言支援如下的幾種流程控制語句:

  • 條件語句,對應的關鍵字為 if、else 和 else if;
  • 選擇語句,對應的關鍵字為 switch、case 和 select;
  • 迴圈語句,對應的關鍵字為 for 和 range;
  • 跳轉語句,對應的關鍵字為 goto。

值得一提的是,Go 語言並不支援 do 或者 while 關鍵字,而是對 for 關鍵字做了增強,以實現類似的效果,如下:

for {
    // 實現無限迴圈,慎用!
}

常用內建函式

  • len:計算(字串,陣列或者切片,map)長度
  • cap:計算(陣列或者切片,map)容量
  • close:關閉通道
  • append:追加內容到切片
  • copy:拷貝陣列/切片內容到另一個陣列/切片
  • delete:用於刪除 map 的元素

array, slice 和 map

// array
a := [3]int{ 1, 2, 3 } // 等價於 a := [...]int{ 1, 2, 3 }

// slice
s := make([]int , 3) // 建立一個長度為 3 的 slice
s := append(s, 1) // 向 slice 追加元素
s := append(s, 2)

// map
m := make(map[string]int) // 使用前必須先初始化
m["golang"] = 7

關於 array, slice 和 map 的更多慣用法,有一篇文章介紹的挺詳細,有興趣的可以看看。

函式

Go 語言的函式有如下特性:

  • 不定引數

由於 Go 語言不支援函式過載(具體原因見 Go Language FAQ),但我們可以通過不定引數實現類似的效果。

func myfunc(args ...int) {
    // TODO
}

// 可通過如下方式呼叫
myfunc(2)
myfunc(1, 3, 5)
  • 多返回值

與 C、C++ 和 Java 等開發語言的一個極大不同在於,Go 語言的函式或者成員的方法可以有多 個返回值,這個特效能夠使我們寫出比其他語言更優雅、更簡潔的程式碼。

func (file *File) Read(b []byte) (n int, err error)

// 我們可以通過下劃線(_)來忽略某個返回值
n, _ := f.Read(buf)
  • 匿名函式

匿名函式是指不需要定義函式名的一種函式實現方式,它並不是一個新概念,最早可以回溯 到 1958 年的 Lisp 語言。但是由於各種原因,C 和 C++ 一直都沒有對匿名函式給以支援,其他的各 種語言,比如 JavaScript、C# 和 Objective-C 等語言都提供了匿名函式特性,當然也包含 Go 語言。

匿名函式由一個不帶函式名的函式宣告和函式體組成,如下:

func(a, b int) bool {
    return a < b
}

匿名函式可以直接賦值給一個變數或者直接執行:

f := func(a, b int) bool {
    return a < b
}

func(a, b int) bool {
    return a < b
}(3, 4) // 花括號後直接跟引數列表表示函式呼叫
  • 閉包

閉包是可以包含自由 (未繫結到特定物件) 變數的程式碼塊,這些變數不在這個程式碼塊內或者 任何全域性上下文中定義,而是在定義程式碼塊的環境中定義。要執行的程式碼塊 (由於自由變數包含 在程式碼塊中,所以這些自由變數以及它們引用的物件沒有被釋放) 為自由變數提供繫結的計算環 境 (作用域)。

Go 的匿名函式就是一個閉包。我們來看一個例子:

package main

import "fmt"

func main() {
    j := 5
    a := func() func() {
        i := 10
        return func() {
            fmt.Printf("i, j: %d, %d\n", i, j)
        }
    }()
    a()
    j *= 2
    a()
}

程式輸出如下:

i, j: 10, 5
i, j: 10, 10

錯誤處理

Go 語言追求簡潔優雅,所以,Go 語言不支援傳統的 try...catch...finally 這種異常,因為 Go 語言的設計者們認為,將異常與控制結構混在一起會很容易使得程式碼變得混亂。因為開發者很容易濫用異常,甚至一個小小的錯誤都丟擲一個異常。在 Go 語言中,使用多值返回來返回錯誤。不要用異常代替錯誤,更不要用來控制流程。在極個別的情況下,也就是說,遇到真正的異常的情況下(比如除數為 0 了),才使用 Go 中引入的 Exception 處理:defer, panic, recover。

用法如下:

package main

import "fmt"

func main() {
    defer func() {
        fmt.Println("recovered:", recover())
    }()
    panic("not good")
}

關於 Go 語言的錯誤處理機制和傳統的 try...catch...finally 異常機制孰優孰劣,屬於仁者見仁,智者見智,這裡不做贅速。有興趣的同學可以去看看知乎上的討論:Go 語言的錯誤處理機制是一個優秀的設計嗎?

物件導向 -> 一切皆型別

Python 推崇 “一切皆物件”,而在 Go 語言中,型別才是一等公民。

我們可以這樣定義一個結構體:

type Name struct {
    First  string
    Middle string
    Last   string
}

同樣也可以定義基礎型別:

type SimpleName string

還能給任意型別定義方法:

func (s SimpleName) String() string { return string(s) }
// 或者
func (s string) NoWay()

Golang VS Python

最後我們通過幾個例子來比較一下 Golang 與 Python 的一些基本用法,如下:

生成器(Generator)

  • Python 版本
def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
        yield a

for x in fib(10):
    print x

print 'done'
  • Golang 版本
package main

import "fmt"

func fib(n int) chan int {
    c := make(chan int)
    go func() {
        a, b := 0, 1
        for i := 0; i < n; i++ {
            a, b = b, a+b
            c <- a
        }
        close(c)
    }()
    return c
}

func main() {
    for x := range fib(10) {
        fmt.Println(x)
    }
}

裝飾器(Decorator)

  • Python 版本
from urlparse import urlparse, parse_qs
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

def auth_required(myfunc):
    def checkuser(self):
        user = parse_qs(urlparse(self.path).query).get('user')
        if user:
            self.user = user[0]
            myfunc(self)
        else:
            self.wfile.write('unknown user')
    return checkuser

class myHandler(BaseHTTPRequestHandler):
    @auth_required
    def do_GET(self):
        self.wfile.write('Hello, %s!' % self.user)

if __name__ == '__main__':
    try:
        server = HTTPServer(('localhost', 8080), myHandler)
        server.serve_forever()
    except KeyboardInterrupt:
        server.socket.close()
  • Golang 版本
package main

import (
    "fmt"
    "net/http"
)

var hiHandler = authRequired(
    func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hi, %v", r.FormValue("user"))
    },
)

func authRequired(f http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.FormValue("user") == "" {
            http.Error(w, "unknown user", http.StatusForbidden)
            return
        }
        f(w, r)
    }
}

func main() {
    http.HandleFunc("/hi", hiHandler)
    http.ListenAndServe(":8080", nil)
}

猴子補丁(Monkey patching)

  • Python 版本
import urllib

def say_hi(usr):
    if auth(usr):
        print 'Hi, %s' % usr
    else:
        print 'unknown user %s' % usr

def auth(usr):
    try:
        auth_url = 'localhost'
        r = urllib.urlopen(auth_url + '/' + usr)
        return r.getcode() == 200
    except:
        return False

def sayhitest():
    # Test authenticated user
    globals()['auth'] = lambda x: True
    say_hi('John')

    # Test unauthenticated user
    globals()['auth'] = lambda x: False
    say_hi('John')

if __name__ == '__main__':
    sayhitest()
  • Golang 版本
package main

import (
    "fmt"
    "net/http"
)

func sayHi(user string) {
    if !auth(user) {
        fmt.Printf("unknown user %v\n", user)
        return
    }
    fmt.Printf("Hi, %v\n", user)
}

var auth = func(user string) bool {
    authURL := "localhost"
    res, err := http.Get(authURL + "/" + user)
    return err == nil && res.StatusCode == http.StatusOK
}

func testSayHi() {
    auth = func(string) bool { return true }
    sayHi("John")

    auth = func(string) bool { return false }
    sayHi("John")
}

func main() {
    testSayHi()
}

相關連結:
https://blog.golang.org/gos-declaration-syntax
https://se77en.cc/2014/06/30/array-slice-map-and-set-in-golang/
https://golang.org/doc/faq#overloading
https://www.zhihu.com/question/27158146
https://talks.golang.org/2013/go4python.slide


本文章為 Cloudinsight 技術團隊工程師原創,更多技術文章可訪問 Cloudinsight 技術部落格Cloudinsight 為視覺化系統監控工具,涵蓋 Windows、Linux 作業系統,用 Golang 開發的 Cloudinsight Agent 正式開源了,歡迎 fork,Github:https://github.com/cloudinsight/cloudinsight-agent

golang-for-pythonistas 系列持續更新中,歡迎關注,及時獲取最新文章~

更多原創文章乾貨分享,請關注公眾號
  • Python 程式設計師的 Golang 學習指南(III): 入門篇
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章