Python思维遇到Go

前言

难产小王子前来报道,这段时间忙着结婚,博客基本停了,可能几个月才出一篇,唉,难受啊(不,其实就是懒)_(:з」∠)_

回归正题,虽然我最爱的依然是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上。

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值