Это не соревнование двух языков, а просто еще один способ обучения. Рассматриваем возможности языка Go, проводя параллели с Python.
Знаете Python? Бесплатные книги и другие туториалы по столь популярному языку программирования доступны повсеместно. Давайте же разберем на примере Python язык Go.
Go гораздо многословнее, чем Python. Чтобы сказать то же самое, ему требуется больше строк кода. Он меньше, чем Python, подходит для скриптового программирования, однако его популярность в этом качестве растет.
Hello World
Начнем с классики – незаменимой для обучения программы Hello World. В Python мы выводим легендарную фразу всего одной строчкой:
Python
1
print("Hello world")
В Go придется приложить чуть больше усилий: указать имя главного пакета, подключить модуль fmt и оформить функцию main:
Go
1
2
3
4
5
6
7
8
9
packagemain
import"fmt"
funcmain(){
fmt.Println("Hello world")
}
Вывод данных
Продолжим с выводом и решим три фундаментальные задачи:
вывод строки с переходом на следующую строку;
вывод строки без перехода на следующую строку;
форматированный вывод.
В Python все три вещи может сделать единственная функция print:
Python
1
2
3
print("Some string")
print("Some string",end="")
print("Name: {}, Age: {}".format("Peter",35))
У языка Go есть три разных метода:
Go
1
2
3
4
5
6
7
8
9
packagemain
import"fmt"
funcmain(){
fmt.Println("Some string")
fmt.Print("Some string")
fmt.Printf("Name: %s, Age: %d
","Peter",35)
}
Комментарии
Комментарии – важная часть документации кода, и программирование на Go – не исключение.
В языке Python они выглядят так:
Python
1
2
3
4
5
6
7
8
9
10
"""doc string для целого модуля"""
# строчный комментарий
classClass(object):
"""doc string для класса"""
print(__doc__)
print(Class.__doc__)
Язык программирования Go предлагает широко распространенный синтаксис комментирования:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
packagemain
// обычный строчный комментарий
/* тоже комментарий
только на несколько строк
*/
/* Многострочный комментарий для функции main().
Чтобы увидеть его, запустите в консоли команду:
godoc comments.go
*/
funcmain(){
}
Многострочные блоки
В языке программирования Python за многострочность отвечают тройные кавычки. Также для оформления строк можно использовать двойные и одинарные кавычки.
Python
1
2
3
4
5
6
print(
"""Это
многострочная строка.
"""
)
print("O'word "'Another "word" '"Last word.")
В Golang для строк используются двойные кавычки, следовательно, сам символ двойной кавычки внутри строки нужно экранировать. Многострочность обеспечивают обратные кавычки.
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
packagemain
import"fmt"
funcmain(){
fmt.Println(`Это
многострочнаястрока.
`)
fmt.Println(
"O'word "+
"Another "word" "+
"Last word.")
}
Списки
Срез (slice) – это последовательность элементов, длина которой может изменяться. Основное различие между массивом и срезом состоит в том, что, работая с массивом, вы должны знать его размер.
Списки в Python:
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# инициализируем список
numbers=[0]*5
# изменяем один элемент
numbers[2]=100
some_numbers=numbers[1:3]
print(some_numbers)# [0, 100]
# получаем размер
print(len(numbers))# 5
# инициализируем другой список
scores=[]
scores.append(1.1)
scores[0]=2.2
print(scores)# [2.2]
У языка Go нет способа так же легко добавлять значения в существующий срез, поэтому вы можете инициализировать срез с максимальной длиной и постепенно добавлять в него элементы.
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
packagemain
import"fmt"
funcmain(){
// инициализируем массив
varnumbers[5]int// [0, 0, 0, 0, 0]
// меняем один элемент
numbers[2]=100
// создаем срез из массива
some_numbers:=numbers[1:3]
fmt.Println(some_numbers)// [0, 100]
// получаем размер
fmt.Println(len(numbers))
// инициализируем срез
varscores[]float64
scores=append(scores,1.1)// пересоздаем, чтобы добавить элемент
scores[0]=2.2// изменяем
fmt.Println(scores)// [2.2]
// когда вы точно не знаете, сколько элементов
// собираетесь положить в срез, можно сделать так
varthings[100]string
things[0]="Peter"
things[1]="Anders"
fmt.Println(len(things))// 100
}
Ассоциативные массивы
В ассоциативных массивах ключ может иметь нечисловое значение.
В Python эту функцию выполняют словари (dictionaries):
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
elements={}
elements["H"]=1
print(elements["H"])# 1
# удалить по ключу
elements["O"]=8
elements.pop("O")
# проверка наличия ключа
if"O"inelements:
print(elements["O"])
if"H"inelements:
print(elements["H"])
В Go для этой же цели мы можем использовать отображения:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
packagemain
import"fmt"
funcmain(){
elements:=make(map[string]int)
elements["H"]=1
fmt.Println(elements["H"])
// удаление по ключу
elements["O"]=8
delete(elements,"O")
// сделать что-то с элементом, если он есть в отображении
ifnumber,ok:=elements["O"];ok{
fmt.Println(number)// не будет выведено }
ifnumber,ok:=elements["H"];ok{
fmt.Println(number)// 1
}
}
В Golang даже можно создать отображение отображений:
Go
1
2
3
4
5
elements:make(map[string]map[string]int)
elements["H"]=map[string]int{
"protons":1,
"neutrons":0,
}
Но помните, что для этой цели существует struct.
Логические значения
В Python внутри условия if можно использовать выражения с разными типами данных – в большинстве случаев они автоматически конвертируются в логическое значение True или False:
Python
1
2
3
4
5
6
7
8
9
10
x=1
ifx:
print"Yes"
y=[]
ify:
print"не будет выведено"
print(TrueandFalse)# False
print(TrueorFalse)# True
print(notTrue)# False
У языка Go нет быстрого способа оценить истинность выражения, поэтому приходится делать это явно для каждого типа данных:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
packagemain
import"fmt"
funcmain(){
x:=1
ifx!=0{
fmt.Println("Yes")
}
vary[]string
iflen(y)!=0{
fmt.Println("не будет выведено")
}
fmt.Println(true&&false)// false
fmt.Println(true||false)// true
fmt.Println(!true)// false
}
В этих примерах также видна работа основных логических операторов.
Циклы
В Python существует несколько видов циклов:
Python
1
2
3
4
5
6
7
8
9
i=1
whilei<=10:
print(i)
i+=1
# ...или...
foriinrange(1,11):
print(i)
В языке программирования Go – всего один, и это цикл for:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
packagemain
import"fmt"
funcmain(){
i:=1
fori<=10{
fmt.Println(i)
i+=1
}
// то же самое, но немного удобнее
fori:=1;i<=10;i++{
fmt.Println(i)
}
}
Перебор элементов
Цикл for можно использовать для перебора элементов коллекции и в Python:
Python
1
2
3
names=["Peter","Anders","Bengt"]
fori,name inenumerate(names):
print("{}. {}".format(i+1,name))
и в Go:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
packagemain
import"fmt"
funcmain(){
names:=[]string{
"Peter",
"Anders",
"Bengt",
}
/* будет выведено:
1. Peter
2. Anders
3. Bengt
*/
fori,name:=rangenames{
fmt.Printf("%d. %s
",i+1,name)
}
}
Switch
Множественные условия могут быть определены с помощью конструкции switch.
print(db.get(number,"I have no idea what %d is"%number))
А вот вариант кода для Go:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
packagemain
import(
"fmt"
"strconv"
)
funcstr2int(sstring)int{
i,err:=strconv.Atoi(s)
iferr!=nil{
panic("Not a number")
}
returni
}
funcmain(){
varnumber_string string
fmt.Scanln(&number_string)
number:=str2int(number_string)
switchnumber{
case8:
fmt.Println("Oxygen")
case1:
fmt.Println("Hydrogen")
case2:
fmt.Println("Helium")
case11:
fmt.Println("Sodium")
default:
fmt.Printf("I have no idea what %d is
",number)
}
// Альтернативное решение
fmt.Scanln(&number_string)
db:=map[int]string{
1:"Hydrogen",
2:"Helium",
8:"Oxygen",
11:"Sodium",
}
number=str2int(number_string)
ifname,exists:=db[number];exists{
fmt.Println(name)
}else{
fmt.Printf("I have no idea what %d is
",number)
}
}
Как видно из примеров, оба языка предлагают более удобные альтернативные решения задачи с использованием ассоциативных массивов.
Вариативные функции
В Python функции могут принимать переменное число аргументов различных типов с помощью somefunction (*args).
Python
1
2
3
4
5
defaverage(*numbers):
returnsum(numbers)/len(numbers)
print(average(1,2,3,4))# 10/4 = 2.5
В Go так нельзя. Однако есть возможность указать параметр, представляющий собой неопределенное количество значений одного конкретного типа:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
packagemain
import"fmt"
funcaverage(numbers...float64)float64{
total:=0.0
for_,number:=rangenumbers{
total+=number
}
returntotal/float64(len(numbers))
}
funcmain(){
fmt.Println(average(1,2,3,4))// 2.5
}
Измерение времени
В Python методы работы со временем собраны в модуле time:
Python
1
2
3
4
5
6
importtime
t0=time.time()
time.sleep(3.5)
t1=time.time()
print("Took {:.2f} seconds".format(t1-t0))
В языке программирования Go, как ни странно, тоже есть такой модуль:
Go
1
2
3
4
5
6
7
8
9
10
packagemain
import"fmt"
import"time"
funcmain(){
t0:=time.Now()
elapsed:=time.Since(t0)
fmt.Printf("Took %s",elapsed)
}
Замыкания функций
Внутри одной функции можно спрятать другую, так что она становится недоступной для внешнего кода.
Вот так можно реализовать замыкание в Python:
Python
1
2
3
4
5
6
7
8
9
10
11
defrun():
defincrement(amount):
returnnumber+amount
number=0
number=increment(1)
number=increment(2)
print(number)# 3
run()
Обратите внимание, что используемая во внутренней функции переменная number доступна из скоупа внешней функции. Если бы ее там не было, возникла бы ошибка UnboundLocalError.
Python
1
2
3
4
defincrement(amount):
number+=amount
increment(1)
increment(2)
Нужную переменную можно объявить прямо внутри increment. А чтобы она была доступна на внешнем уровне, используется ключевое слово global.
Python
1
2
3
4
5
defincrement(amount):
globalnumber
number+=amount
increment(1)
increment(2)
А вот пример замыкания в Golang:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
packagemain
import"fmt"
funcmain(){
number:=0
/* Это должна быть именно локальная переменная.
Нельзя написать `func increment(amount int) {` */
increment:=func(amount int){
number+=amount
}
increment(1)
increment(2)
fmt.Println(number)// 3
}
Defer
В Go defer имеет замечательный синтаксис. Отложенная операция стоит сразу после ключевого слова, так что смысл конструкции сразу же становится очевиден:
Go
1
2
3
4
5
6
7
8
9
10
11
12
packagemain
import(
"os"
)
funcmain(){
f,_:=os.Open("defer.py")
deferf.Close()
// теперь вы можете читать из f
// он будет закрыт в конце программы
}
В Python вы можете добиться того же, поместив код в try-finally блок:
Python
1
2
3
4
5
f=open("defer.py")
try:
f.read()
finally:
f.close()
Panic Recover
Чтобы сгенерировать и поймать ошибку, в Python используется ключевое слово raise и конструкция try-except:
Python
1
2
3
4
try:
raiseException("Shit")
exceptExceptionase:
print("error was:",e)
В Go для этого есть оператор panic и функция recover:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
packagemain
import"fmt"
funcmain(){
// будет выведено:
// error was: Shit!
deferfunc(){
fmt.Println("error was:",recover())
}()
panic("Shit!")
}
Изменяемость
Передавая параметром в функцию сложные структуры, например, массив или мапу, вы можете изменить прямо внутри функции, и это автоматически изменится снаружи.
Часто бывает необходимо получить аргументы переданные в команде терминала. Рассмотрим эту задачу на примере команды:
1
go run args.go peter anders bengt
Программа должна вернуть все три аргумента в верхнем регистре:
1
2
3
PETER
ANDERS
BENGT
В Python для разбора неопределенного количества неименованных параметров удобно воспользоваться синтаксисом *args. Список аргументов находится в sys.argv.
Python
1
2
3
4
5
6
7
8
9
10
importsys
deftransform(*args):
forarg inargs:
print(arg.upper())
if__name__=="__main__":
transform(*sys.argv[1:])
В Go аргументы командной строки хранятся в os.Args:
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
packagemain
import(
"fmt"
"os"
"strings"
)
functransform(args[]string){
for_,arg:=rangeargs{
fmt.Println(strings.ToUpper(arg))
}
}
funcmain(){
args:=os.Args[1:]
transform(args)
}
Псевдонимы для импорта
В Python для импортируемых модулей можно использовать алиасы – короткие псевдонимы:
Python
1
2
3
importstringass
print(s.upper("world"))
У языка Go тоже есть такая возможность:
Go
1
2
3
4
5
6
7
8
9
10
packagemain
import(
"fmt"
s"strings"
)
funcmain(){
fmt.Println(s.ToUpper("world"))
}
Конечно, вы обычно не используете псевдонимы для встроенных модулей с короткими именами, но вот здесь, например, это может быть удобно:
Go
1
2
3
import(
pb"github.com/golang/groupcache/groupcachepb"
)
Также можно импортировать пакеты, которые фактически не будут использоваться:
Go
1
2
3
import(
_"image/png"
)
Быстрое форматирование строк
Строки не всегда нужно печатать, иногда требуется просто быстро их отформатировать и передать, куда следует. В Python для этого есть простая функция f:
Python
1
2
max=10
raiseException(f"The max. number is {max}")
В Golang для быстрого форматирования используется метод Sprintf модуля fmt:
Go
1
2
3
4
5
6
7
8
packagemain
import"fmt"
funcmain(){
max:=10
panic(fmt.Sprintf("The max. number is %d",max))
}
Отбор уникальных значений
Задача убрать из коллекции все повторяющиеся элементы встречается очень часто. Python предлагает аккуратное решение, которое полностью зависит от типа, если значения поддерживают хэширование.
Python
1
2
3
4
5
6
7
8
9
10
11
12
defuniqify(seq):
seen={}
unique=[]
foritem inseq:
ifitem notinseen:
seen[item]=1
unique.append(item)
returnunique
items=["B","B","E","Q","Q","Q"]
print(uniqify(items))# prints ['B', 'E', 'Q']
А здесь вы можете найти самый быстрый способ унифицировать список в Python.
Учиться новому проще на фундаменте имеющихся знаний. Сравнивая особенности Python и языка Go, мы находим множество сходств между ними. Это позволяет глубже понять концепции программирования и проще изучить новое.