對比學習:Golang VS Python3

ZetaChow曉程式碼發表於2019-05-15

對比學習:Golang VS Python3

Golang和Python都是目前在各自領域最流行的開發語言之一。

Golang其高效而又友好的語法,贏得了很多後端開發人員的青睞,最適用於高併發網路程式設計的語言之一。

Python不用說,TIOBE排行榜的前十常駐居民,現在已經穩定在前五了。在機器學習、AI、資料分析領域成為必學語言。

兩門程式語言在語法上都有各自的特點,而且都易學易用

本文對比這兩門語言目的不是爭誰優誰略,只是為了對比學習,適合掌握Python想學Go或者掌握Go想學Python的同學們參考。

Go和Python,一個是靜態語言一個是動態語言,從各個方面來看,都有根本性的差異,所以,文中很多內容不進行深入的比較了,我們只從程式設計師最直觀的語法面做對比。

為了便於閱讀,文中涉及程式碼都採用儘量簡單的語句呈現

字元編碼

Python

Python中預設的編碼格式是 ASCII 格式,程式檔案中如果包含中文字元(包括註釋部分)需要在檔案開頭加上 # -*- coding: UTF-8 -*- 或者 #coding=utf-8 就行了

Golang

原生支援Unicode

保留字(關鍵字)

Python

30個關鍵字

and	exec	not
assert	finally	or
break	for	pass
class	from	print
continue global	raise
def	if	return
del	import	try
elif	in	while
else	is	with
except	lambda	yield
複製程式碼

Golang

25個關鍵字

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
複製程式碼

註釋

Python

# 單行註釋

'''
多行註釋
多行註釋
'''

"""
多行註釋
多行註釋
"""

複製程式碼

Golang

//單行註釋

/*
多行註釋
多行註釋
*/
複製程式碼

變數賦值

Python

Python是動態語言,所以在定義變數的時候不需要申明型別,直接使用即可。 Python會根據值判斷型別。

name = "Zeta" # 字串變數
age = 38 # 整數
income = 1.23 # 浮點數
複製程式碼

多變數賦值

a,b = 1,2 # a=1; b=2
c = d = 3 # c=3; d=3
複製程式碼

Golang

Go是靜態語言,是強型別的,但是Go語言也允許在賦值變數時確定型別。

因此Go有多種申明變數的方式

// 1. 完整的申明並賦值
var a int
a = 1

// 2. 宣告變數型別同時賦值
var a int = 1

// 3. 不宣告型別,賦值時確定
var a = 1

// 4. 不用 var 關鍵字申明變數並賦值後確定型別
a := 1
複製程式碼

注意,Go中的new關鍵字並不是宣告變數,而是返回該型別的指標

a := new(int) //這時候a是一個*int指標變數
複製程式碼

標準資料型別

Python 的標準資料型別有:

  • Boolean(布林值)
  • Number(數字)
  • String(字串)
  • List(列表)
  • Tuple(元組)
  • Set(集合)
  • Dictionary(字典)

Golang

  • boolean(布林值)
  • numeric(數字)
  • string(字串)
  • 陣列(陣列)
  • slice(切片:不定長陣列)
  • map(字典)
  • struct(結構體)
  • pointer(指標)
  • function(函式)
  • interface(介面)
  • channel(通道)

總結

Python中的List列表對應Go語言中的Slice切片

Python中的Dictionary字典對應Go語言中的map

有一些值得注意的地方:

  • Go是支援函式程式設計的語言,所以在Go語言中函式是一個型別
  • Go語言不是物件導向的語言,沒有定義類的關鍵字Class,要實現OOP風格程式設計,是通過struct、interface型別實現的
  • Python中的元組和集合在Go中都沒有
  • channel是Go裡獨有的型別,多執行緒之間的通訊就靠它

資料型別轉換

Python

Python型別轉換非常簡單,用型別名作為函式名即可。

int(n)            # 將數字n轉換為一個整數
float(n)          # 將數字n轉換到一個浮點數
str(o)            # 將物件 obj 轉換為字串
tuple(s)          # 將序列 s 轉換為一個元組
list(s)           # 將序列 s 轉換為一個列表
set(s)            # 將序列 s 轉換為一個集合
複製程式碼

Golang

Go語言的基礎型別轉換和Python差不多,也是用型別名作為函式名

i := 1024
f := float32(i)
i = float32(f)
複製程式碼

另外,Python中可以直接轉換數字字串和數字:

s = "123"
i = 456
print(int(s), str(i))
複製程式碼

但是Go是不可以的。

Go語言的字串處理很不同,string()只能用於[]byte型別轉換成字串,其他基礎型別的轉換需要用strconv包,另外,其他型別轉換成為string型別除了用strconv包,還可以用fmt.Sprintf函式:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    s := "123"
    i, _ := strconv.Atoi(s)
    println(i)

    s2 := fmt.Sprintf("%d", 456)
    println(s2)
}
複製程式碼

Go中的interface型別是不能直接轉換成其他型別的,需要使用到斷言

package main

func main() {
var itf interface{} = 1
i, ok := itf.(string)
println("值:", i, "; 斷言結果", ok)

j, ok := itf.(int)
println("值:", j, "; 斷言結果", ok)
}
複製程式碼

輸出為:

值:  ; 斷言結果 false
值: 1 ; 斷言結果 true
複製程式碼

條件語句

Python

Python傳統的判斷語句如下

if name == 'zeta':          # 判斷變數是否為 zeta 
    print('Welcome boss')   # 並輸出歡迎資訊
else:
    print('Hi, ' + name)  
複製程式碼

Python不支援三元表示式,但是可以用一種類似的替代辦法

title = "boss"
name = "zeta" if title == "boss" else "chow"
print(name)
複製程式碼

邏輯與用 and ,邏輯或用 or

Golang

Go的if的語法類似Java,但是表示式不需要使用()

if a > b{
    println("a > b")
} else {
    println("a <= b")
}
複製程式碼

Go同樣沒有三元表示式,並且也沒有什麼替代方法。

另外,Go允許在if的表示式裡定義變數,定義並賦值的表示式與判斷的表示式用;隔開,常見的情況是獲取函式返回error,然後判斷error是否為空:

if err := foo(); err != nil {
    println("發生一些錯誤")
} 
複製程式碼

與Python不同,邏輯與用 &&, 邏輯或用||

迴圈語句

Python

Python中有whilefor兩種迴圈,都可以使用break跳出迴圈和continue立即進入下一輪迴圈,另外,Python的迴圈語句還可以用else執行迴圈全部完畢後的程式碼,break跳出後不會執行else的程式碼

while 條件迴圈,

count = 0
while (count < 9):
    print('The count is:', count)
    count = count + 1
    if count == 5:
        break   # 可以比較以下break和不break的區別
        pass
else:
    print('loop over')
複製程式碼

for 遍歷迴圈,迴圈遍歷所有序列物件的子項

names = ['zeta', 'chow',  'world']
for n in names:
    print('Hello, ' + n)
    if n == 'world':
        break
        pass
else:
    print('Good night!')
複製程式碼

for迴圈中也可以用else,(註釋掉程式碼中的break試試看。)

Golang

Go語言只有一個迴圈語句for,但是根據不同的表示式,for有不同的表現

for 前置表示式; 條件表示式; 後置表示式 {
	//...
}
複製程式碼

前置表示式 在每輪迴圈前執行,可以用於宣告變數或呼叫函式返回; 條件表示式 滿足該表示式則執行下一輪迴圈,否則退出迴圈; 後置表示式 在迴圈完成後執行

經典的用法:

for i := 0; i < 10; i++ {
    println(i)
}
複製程式碼

我們可以忽略掉前置和後置表示式

sum := 1
for sum < 10 {
	sum += sum
}
複製程式碼

設定可以忽略掉全部表示式,也就是無限迴圈

for {
    print(".")
}
複製程式碼

Go的for迴圈同樣可以使用 break退出迴圈和continue立即進行下一輪迴圈。

for除了配合表示式迴圈,同樣也可以用於遍歷迴圈,需要用到range關鍵字

names := []string{"zeta", "chow", "world"}
for i, n := range names {
    println(i,"Hello, " + n)
}
複製程式碼

函式

Python

def關鍵字定義函式,並且在Python中,作為指令碼語言,呼叫函式必須在定義函式之後。

def foo(name):
    print("hello, "+name)
    pass

foo("zeta")
複製程式碼

預設引數 Python定義函式引數時,可以設定預設值,呼叫時如果沒有傳遞該引數,函式內將使用預設值,預設值引數必須放在無預設值引數後面。

def foo(name="zeta"):
    print("hello, "+name)
    pass

foo()
複製程式碼

關鍵字引數 一般函式傳遞引數時,必須按照引數定於的順序傳遞,但是Python中,允許使用關鍵字引數,這樣通過指定引數明,可以不按照函式定義引數的順序傳遞引數。

def foo(age, name="zeta"):
    print("hello, "+name+"; age="+str(age))
    pass

foo(name="chow", age=18)
複製程式碼

不定長引數,Python支援不定長引數,用*定義引數名,呼叫時多個引數將作為一個元祖傳遞到函式內

def foo(*names):
    for n in names:
        print("hello, "+n)
    pass

foo("zeta", "chow", "world")
複製程式碼

return 返回函式結果。

Golang

Go用func定義函式,沒有預設值引數、沒有關鍵字引數,但是有很多其他特徵。

func main() {
    println(foo(18, "zeta"))
}

func foo(age int, name string) (r string) {
    r = fmt.Sprintf("myname is %s , age %d", name, age)
    return 
}
複製程式碼

函式的定義和呼叫沒有順序的限制。

Go的函式不僅可以定義函式返回值型別,還可以申明返回值變數,當定義了返回值變數時,函式內的return語句可以不需要帶返回值,函式會預設使用返回值變數返回。

可變引數

使用…型別定義可變引數,函式內獲得的引數實際是該型別slice物件

func main() {
	println(foo(18, “zeta”, “chow”, “world”))
}

func foo(age int, names …string) (r string) {
	for _, n := range names {
		r += fmt.Sprintf(“myname is %s , age %d \n”, n, age)
	}

	return
}
複製程式碼

defer句

defer語句後面指定一個函式,該函式會延遲到本函式return後再執行。

defer語句在Go語言中非常有用,詳細可以查閱本專欄的另一篇文章《Golang研學:如何掌握並用好defer(延遲執行)

func foo() {
	defer fmt.Println("defer run")
	fmt.Println("Hello world")
	return
}
複製程式碼

執行結果:

Hello world
defer run
複製程式碼

另外,在Go語言中函式也是型別,可以作為引數傳遞給別的函式

func main() {
	n := foo(func(i int, j int) int {
		return i + j
	})
	println(n)
}

func foo(af func(int, int) int) int {
	return af(1, 2)
}
複製程式碼

上面這個例子直接在引數定義時使用函式型別,看上去有點混亂

再看來看一個清晰並完整的例子,說明全在註釋裡。

package main

type math func(int, int) int //定義一個函式型別,兩個int引數,一個int返回值

//定義一個函式add,這個函式兩個int引數一個int返回值,與math型別相符
func add(i int, j int) int {
	return i + j
}

//再定義一個multiply,這個函式同樣符合math型別
func multiply(i, j int) int {
	return i * j
}

//foo函式,需要一個math型別的引數,用math型別的函式計算第2和第3個引數數字,並返回計算結果
//稍後在main中我們將add函式和multiply分別作為引數傳遞給它
func foo(m math, n1, n2 int) int {
	return m(1, 2)
}

func main() {
	//傳遞add函式和兩個數字,計算相加結果
	n := foo(add, 1, 2)
	println(n)

	//傳遞multply和兩個數字,計算相乘結果
	n = foo(multiply, 1, 2)
	println(n)
}
複製程式碼

結果

3
2
複製程式碼

模組

Python

  • 模組是一個.py檔案
  • 模組在第一次被匯入時執行
  • 一個下劃線定義保護級變數和函式,兩個下劃線定義私有變數和函式
  • 匯入模組習慣性在指令碼頂部,但是不強制

Golang

  • 與檔案和檔名無關,每一個檔案第一行用package定義包名,相同包名為一個包
  • 包中的變數第一次引用時初始化,如果包中包含init函式,也會在第一次引用時執行(變數初始化後)
  • 保重首寫字母大寫的函式和變數為共有,小寫字母為私有,Golang不是物件導向的,所以不存在保護級。
  • 匯入模組必須寫在package之後,其他程式碼之前。

匯入包

Python

在Python中,使用import匯入模組。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
# 匯入模組
import support
 
support.print_func(“Runoob”)
複製程式碼

還可以使用from import匯入模組指定部分

from modname import name1[, name2[, ... nameN]]
複製程式碼

為匯入的包設定別名用 as關鍵字

import datetime as dt
複製程式碼

Golang

也是使用import匯入包,匯入包指定的是包的路徑,包名預設為路徑中的最後部分

import "net/url" //匯入url包
複製程式碼

多個包可以用()組合匯入

import (
	"fmt"
	"net/url"
)
複製程式碼

為匯入的包設定別名, 直接在匯入包時,直接在報名前面新增別名,用空格隔開

import (
	f "fmt"
  u "net/url"
)
複製程式碼

錯誤和異常

Python

Python中用經典的 try/except 捕獲異常

try:
<語句>        #執行別的程式碼
except <異常名稱>:
<語句>        #
except <異常名稱>,<資料>:
<語句>        #如果引發了指定名稱的異常,獲得附加的資料
複製程式碼

還提供了 elsefinally

如果沒發生異常的執行else語句塊,finally塊的程式碼無論是否捕獲異常都會執行

Python內建了很全面的異常型別名稱,同時能自定義異常型別

Golang

Golang裡沒有用經典的 try/except捕獲異常。

Golang提供兩種錯誤處理方式

  1. 函式返回error型別物件判斷錯誤
  2. panic異常

一般情況下在Go裡只使用error型別判斷錯誤,Go官方希望開發者能夠很清楚的掌控所有的異常,在每一個可能出現異常的地方都返回或判斷error是否存在。

error是一個內建的介面型別

type error interface {
	Error() string
}
複製程式碼

通常,使用error異常處理類似這樣:

package main

import "fmt"

func foo(i int, j int) (r int, err error) {
    if j == 0 {
        err = fmt.Errorf("引數2不能為 %d", j) //給err變數賦值一個error物件
        return //返回r和err,因為定義了返回值變數名,所以不需要在這裡寫返回變數
    }

    return i / j, err //如果沒有賦值error給err變數,err是nil
}

func main() {
    //傳遞add函式和兩個數字,計算相加結果
    n, err := foo(100, 0)
    if err != nil { //判斷返回的err變數是否為nil,如果不是,說明函式呼叫出錯,列印錯誤內容
        println(err.Error())
    } else {
        println(n)
    }
}
複製程式碼

panic可以手工呼叫,但是Golang官方建議儘量不要使用panic,每一個異常都應該用error物件捕獲。

Go語言在一些情況下會觸發內建的panic,例如 0 除、陣列越界等,修改一下上面的例子,我們讓函式引起0除panic

package main

func foo(i int, j int) (r int) {
	return i / j
}

func main() {
	//傳遞add函式和兩個數字,計算相加結果
	n := foo(100, 0)
	println(n)
}
複製程式碼

執行後會出現

panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.foo(...)
        /lang.go:4
main.main()
        /lang.go:9 +0x12
exit status 2
複製程式碼

手工panic可以這樣:

func foo(i int, j int) (r int) {
	if j == 0 {
		panic("panic說明: j為0")
	}
	return i / j
}
複製程式碼

執行後,可以看到,錯誤訊息的第一句:

panic: panic說明: j為0
複製程式碼

物件導向

Python

Python完全支援物件導向的。

Golang

儘管Go語言允許物件導向的風格程式設計,但是本身並不是物件導向的

官方FAQ原文

Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

多執行緒

Python

  1. 使用thread模組中的start_new_thread()函式
  2. 使用threading模組建立執行緒

Golang

用關鍵 go建立協程goroutine

go關鍵字後指定函式,將會開啟一個協程執行該函式。

package main

import (
    "fmt"
    "time"
)

func foo() {
    for i := 0; i < 5; i++ {
        fmt.Println("loop in foo:", i)
        time.Sleep(1 * time.Second)
    }
}

func main() {
    go foo()

    for i := 0; i < 5; i++ {
        fmt.Println("loop in main:", i)
        time.Sleep(1 * time.Second)
    }
    time.Sleep(6 * time.Second)
}
複製程式碼

Go語言中,協程之間的通訊是通過channel實現的:

package main

import (
    "fmt"
    "time"
)

//接受一個chan型別的引數c
func foo(c chan int) { 
    time.Sleep(1 * time.Second) //等待1秒
    c <- 1024                   //向c中寫入數字
}

func main() {
    c := make(chan int) //建立chan變數c
    go foo(c)           //在子寫成中執行函式foo,並傳遞變數c
    fmt.Println("wait chan 'c' for 1 second")
    fmt.Println(<-c) //取出chan 'c'的值(取值時,如果c中無值,主縣城會阻塞等待)
}
複製程式碼

總結

Python和Go分別在動態語言和靜態語言中都是最易學易用的程式語言之一。

它們並不存在取代關係,而是各自在其領域發揮自己的作用。

Python的語法簡單直觀,除了程式設計師愛不釋手外也非常適合於其他領域從業者使用。

Go兼具語法簡單和執行高效的有點,在多執行緒處理方面很優秀,非常適合已經掌握一定程式設計基礎和一門主流語言的同學學習,不過,Go是不支援物件導向的,對於大多數支援面嚮物件語言的使用者在學習Go語言的時候,需要謹記並且轉換程式設計思路。

後記

文中還有許多應該涉及的知識卻沒有能夠詳細說明,它是不完整的,甚至難免會有一些失誤。

如果,您覺得本文對您有所幫助,希望能夠得到您的點贊支援,如果文中有錯誤的地方,也希望不吝賜教,通過評論或者公眾號和大家一起交流學習。

曉程式碼

相關文章