前言
难产小王子前来报道,这段时间忙着结婚,博客基本停了,可能几个月才出一篇,唉,难受啊(不,其实就是懒)_(:з」∠)_
回归正题,虽然我最爱的依然是python,但是截止目前(2021年4月),go语言已经在互联网范围内越来越流行了。
简洁的语法,优异的性能使得go
语言受到各类大中小公司的青睐,尤其是一些前期依靠python搭建起系统的公司,都在尝试使用go
进行系统重构(比较出名的有B站,知乎等),笔者的公司亦是如此。由于go语言的简洁,对于python开发者而言,学习起来还是比较简单的,比较麻烦的是思维方式的转变(面向对象转为面向过程)。
笔者希望将自身python的开发经验与思维运用到go
语言的开发上,从细节上来讲,就是将python语法(以python3为例)用go来进行诠释。
语法
打印print
在go
中存在包的概念,每个目录就是一个包,其下每个文件首行都会带有相同的package
python
sth = 'world'
print(f'hello {sth}')
go
package main
import (
"fmt"
)
func main() {
fmt.Printf("hello %s\n", "world")
fmt.Printf("hello %d\n", 12)
fmt.Printf("hello %f\n", 12.113)
fmt.Printf("hello %.2f\n", 12.113)
}
>>> hello world
>>> hello 12
>>> hello 12.113000
>>> hello 12.11
go
的简洁是相对于静态语言而言的,如果拿它和python比就有点欺负人了哈哈,从变量打印操作上来看,go
语言使用较多的是经典的字符串占位符形式。
数据结构
毕竟是两种不同的语言,语法上有很多不同,写法也多种多样,这里只写出比较常见的写法对比,更多不同的语法可以单独继续深入研究
列表
python
numbers = [1, 2, 3, 4]
print(numbers)
print(numbers[1:3])
numbers.append(5)
print(numbers)
>>> [1, 2, 3, 4]
>>> [2, 3]
>>> [1, 2, 3, 4, 5]
go
package main
import (
"fmt"
)
func main() {
numbers := []int{1, 2, 3, 4} // 定义一个不定长的列表
fmt.Println(numbers)
fmt.Println(numbers[1:3]) // 切片
numbers2 := append(numbers, 5) // 添加元素,返回一个新的列表
fmt.Println(numbers2)
fmt.Println(numbers)
}
>>> [1 2 3 4]
>>> [2 3]
>>> [1 2 3 4 5]
>>> [1 2 3 4]
列表操作都还是挺类似的,需要注意的是go添加列表元素,返回的是一个新的对象,强类型的语言定义数据结构是非常严格的,上述numbers
我们定义的是[]int
, 如果给定一个长度[5]int
,那么这个列表就无法新增元素了
Map
python
members = {"python": 1, "java": 2, "go": 3}
print(members)
members.update({"C++": 4})
print(members)
members["ruby"] = 5
print(members)
members.pop("python")
print(members)
print("python" in members)
for k, v in members.items():
print('k:', k, 'v:', v)
>>> {'python': 1, 'java': 2, 'go': 3}
>>> {'python': 1, 'java': 2, 'go': 3, 'C++': 4}
>>> {'python': 1, 'java': 2, 'go': 3, 'C++': 4, 'ruby': 5}
>>> {'java': 2, 'go': 3, 'C++': 4, 'ruby': 5}
>>> False
>>> k: java v: 2
>>> k: go v: 3
>>> k: C++ v: 4
>>> k: ruby v: 5
go
package main
import (
"fmt"
)
func main() {
members := map[string]int{"python": 1, "java": 2, "go": 3}
fmt.Println(members)
members["C++"] = 4
fmt.Println(members)
delete(members, "python")
fmt.Println(members)
member, err := members["python"]
fmt.Println("member:", member, "err:", err)
for k, v := range members {
fmt.Println("k:", k, "v:", v)
}
}
>>> map[go:3 java:2 python:1]
>>> map[C++:4 go:3 java:2 python:1]
>>> map[C++:4 go:3 java:2]
>>> member: 0 err: false
>>> k: C++ v: 4
>>> k: java v: 2
>>> k: go v: 3
这里有个小细节,在python3当中,默认的字典底层实现已经能够保证其顺序的一致性(即写入顺序和输出顺序一致),在go当中则还是传统的无序字典。
逻辑控制
布尔判断
python
for x in [1, [1], {"1": 1}]:
print(f"x={x} is {bool(x)}")
for x in [0, [], {}, "", None]:
print(f"x={x} is {bool(x)}")
>>> x=1 is True
>>> x=[1] is True
>>> x={'1': 1} is True
>>> x=0 is False
>>> x=[] is False
>>> x={} is False
>>> x= is False
>>> x=None is False
go
package main
import "fmt"
func main() {
x := 1
if x != 0 {
fmt.Println("yes")
}
y := [1]int{1}
if len(y) != 0 {
fmt.Println("yes")
}
}
在python当中,所有变量都可以用来直接进行bool值判断,就像上述的写法那样。但是在go当中,就需要老老实实的写判断表达式。
循环/遍历
python
i = 1
while i <= 10:
print(i)
i += 1
for i in range(1, 11):
print(i)
i += 1
for idx, val in enumerate([1, 2, 3, 4, 5]):
print(f'idx: {idx}, val: {val}')
go
package main
import "fmt"
func main() {
i := 1
for i <= 10 {
fmt.Println(i)
i += 1
}
for i := 1; i <= 10; i++ {
fmt.Println(i)
i += 1
}
for idx, val := range [...]int{1, 2, 3, 4, 5} {
fmt.Println("idx:", idx, "val:", val)
}
}
相较于python,go只有一种循环方式,就是for
语法,想要死循环也很简单,for
后面什么条件都不带就可以了。至于遍历,一般会结合range
使用,每次遍历会自动带出下标以及对应的值。
分支判断
i = int(input())
if i == 0:
print(i)
elif i == 1:
print(i)
elif i == 2:
print(i)
else:
print('i not in 1, 2, 3')
package main
import (
"fmt"
"strconv"
)
func main() {
var numberStr string
fmt.Scanln(&numberStr)
number, err := strconv.Atoi(numberStr)
if err != nil {
panic("input error")
}
switch number {
case 1:
fmt.Println(number)
case 2:
fmt.Println(number)
case 3:
fmt.Println(number)
default:
fmt.Printf("number %d is not in 1, 2, 3", number)
}
}
python里面一直没有switch语法(最新版里加上了,在语法糖方面,python依旧是yyds),go语言与C++其实更为相似,也能够支持switch
语法,而且写法上更加的简洁了。
形参
python
def get_params(a, b, *args, **kwargs):
print(a)
print(b)
for arg in args:
print(arg)
for k, v in kwargs.items():
print(f"k: {k}, v: {v}")
get_params('a', 'b', 1, 2, 3, python=1, go=2, java=3)
>>> a
>>> b
>>> 1
>>> 2
>>> 3
>>> k: python, v: 1
>>> k: go, v: 2
>>> k: java, v: 3
上述参数形式基本就是我们在python中常用的三种形态,默认参数,可变参数以及关键字参数,灵活的参数输入为python的函数提供了非常大的可扩展性(尽管他可能已经被滥用)
go
package main
import "fmt"
func getParmas(a int, b int, numbers ... float64) {
fmt.Println(a)
fmt.Println(b)
fmt.Println(numbers)
}
func main() {
getParmas(1, 2, 3, 4, 5, 6)
}
>>> 1
>>> 2
>>> [3 4 5 6]
go在这方面就没有那么多花里胡哨的东西了,仅仅支持了可变参数,如果一定要像python那样,我们仍然可以通过传递一个复杂结构来实现函数的可拓展性(比如参数传递一个字典),但是不推荐这么做,这会是的代码的可维护性大大降低
语法特色
上下文with语法
python
f = open('./demo.txt')
print(f.readline())
f.close()
with open('./demo.txt') as f:
print(f.readline())
python经常遇到文件相关的操作,当我们打开一个文件操作完毕后,需要手动的去释放掉打开的端口,否则就会有内存泄漏的隐患。比如上述代码打开文件读取内容后手动close
掉。
python在这方面给我们提供了上下文with
语法,能够帮助我们自动完成close
这一步,使得我们能够更加专注于代码的开发。
go
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func openFile(path string) {
//fmt.Println(filepath.Abs(path))
file, err := os.OpenFile(path, os.O_RDWR, 0666)
if err != nil {
fmt.Println("open file error")
return
}
defer file.Close()
// 这一段是获取文件大小的,可以忽略
// stat, err := file.Stat()
// if err != nil {
// panic(err)
// }
//fmt.Println("file size is", stat.Size())
buf := bufio.NewReader(file)
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
fmt.Println(line)
if err != nil {
if err == io.EOF {
fmt.Println("file read ok!")
break
}
} else {
fmt.Println("read file error!", err)
return
}
}
}
func main() {
openFile("./services/ares.cus/demo/demo.txt")
}
同样是按行读取,go的代码量蹭的一下就上去了,主要原因就是语法糖少了,并且需要自己分配buffer,另外就是无处不再的错误处理(python中没有加上错误处理的部分)。需要注意的是go
中的defer
语法,其实作用类似于python中的with
语句,都是为了防止没有关闭文件端口,defer
之后的语句,可以保证在函数运行完毕后执行,但是可以写在函数的任何地方。比如你可以在打开文件IO之后立马加上defer
关闭文件IO,防止后面忘了~
异常捕获
python
异常捕获方面个大主流语言的语法基本一直,均为try…catch的模式,这个大家都很熟悉了,我个人使用python这么久,对这种语法也是颇为喜爱的。
def use_exception():
try:
1 / 0
except Exception as e:
print(f"Error: {e}")
try:
f = open("./demo")
print(f.readline())
f.close()
except FileNotFoundError:
print("can not find file")
use_exception()
go
package main
import (
"fmt"
"os"
)
func openFile(path string) {
file, err := os.OpenFile(path, os.O_RDWR, 0666)
if err != nil {
fmt.Println("open file error")
return
}
defer file.Close()
}
func main() {
openFile("./services/ares.cus/demo/demo.txt")
}
go的异常捕获是比较新颖的,在调用函数时,出了通常会返回的结果外,还会返回一个err
参数,当这个参数不为空时,代表函数运行结果不符合预期。此时,我们就需要做相应的处理。你可能会看到代码里有很多的if err != nil...
,如果不习惯,我这里给出的建议是,多写写,就习惯了:)
或者可以把这一段重复率相当高的代码简单封装一下,看着好看一点。
面向对象
python
对于python来说,既可以支持面向对象,也可以支持面向过程编程,比较的灵活。
面向对象涉及的概念非常多,但这并非文本的重点,这里只做一个简单的语法级别的呈现。
class Demo:
def __init__(self, member1, member2):
self.member1 = member1
self.member2 = member2
def see_members(self):
return [self.member1, self.member2]
def sum_member(self):
return sum(self.see_members())
demo = Demo()
print(demo.sum_member())
go
package main
import (
"fmt"
)
type Demo struct {
member1 int
member2 int
}
func (cls *Demo) seeMembers() []int {
demo := cls
return []int{demo.member1, demo.member2}
}
func (cls *Demo) addMembers() (res int) {
res = 0
for _, val := range cls.seeMembers() {
res += val
}
return res
}
func main() {
demo := Demo{member1: 1, member2: 2}
res := demo.addMembers()
fmt.Println(res)
}
在go语言中,可以通过非常经典的结构体的形式对代码进行组织,看上去十分像是面向对象的一种简易替代。实际上,这仅仅是为代码组织提供了一种便利,这种形式从本质上来说,仍然是面向过程的,其中的cls
实际上也无法完成类对象的职能,仅仅是起到了传递参数的作用,这也是go中为数不多的语法糖之一~
并发控制
python
在python3中采用asyncio
实现代码层面的协程并发控制,并且如果使用协程,所有代码都必须携带协程的语法,这就是经典的“开弓没有回头箭”~
import asyncio
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(1.0)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()
go
package main
import "fmt"
func f(from string) {
for i := 0; i < 3; i++ {
fmt.Println(from, ":", i)
}
}
func main() {
// 正常待哦用
f("direct")
// 使用 `go f(s)` 在一个 Go 协程中调用这个函数。
// 这个新的 Go 协程将会并行的执行这个函数调用。
go f("goroutine")
// 使用匿名函数启动一个 Go 协程。
go func(msg string) {
fmt.Println(msg)
}("going")
// 现在这两个 Go 协程在独立的 Go 协程中异步的运行,所以
// 我们需要等它们执行结束。这里的 `Scanln` 代码需要我们
// 在程序退出前按下任意键结束。
var input string
fmt.Scanln(&input)
fmt.Println("done")
}
>>> direct : 0
>>> direct : 1
>>> direct : 2
>>> goroutine : 0
>>> going
>>> goroutine : 1
>>> goroutine : 2
>>> done
go协程在执行上来说是轻量级的线程。上述两个被go
修饰的代码都是在独立的go协程中异步的运行,相较于python来说在语法结构方面简单清晰了不止一点,也是go语言的最大的魅力之一。
写在结尾
到目前为止,python仍是我最喜欢的语言之一,而go语言的特性及魅力也远不止于此,由于篇幅限制,本篇文章无法完全将python与go的对比列举完全。本篇涉及的内容相对来说还是较为基础的。而我们的目标是能够实现python与go的无缝切换,具体来说就是将我们在python中实现的代码快速切换到go语言当中。
对于即将python转型go的开发者而言,这是一个很有趣的也很实用的挑战。我会在未来的持续开发当中不断总结python的开发思维,并将其精华运用到go上。