Go vs Python: изучение основ языка Go в сравнении с Python

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


2019-03-02 01:42

разработка по

Это не соревнование двух языков, а просто еще один способ обучения. Рассматриваем возможности языка Go, проводя параллели с Python.

Go vs Python: изучение основ языка 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.

В Python она выглядит так:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

definput_():

   returnint(input())

number=input_()

ifnumber==8:

   print("Oxygen")

elifnumber==1:

   print("Hydrogen")

elifnumber==2:

   print("Helium")

elifnumber==11:

   print("Sodium")

else:

   print("I have no idea what %d is"%number)

# Альтернативное решение

number=input_()

db={1:"Hydrogen",2:"Helium",8:"Oxygen",11:"Sodium"}

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!")

}

Изменяемость

Передавая параметром в функцию сложные структуры, например, массив или мапу, вы можете изменить прямо внутри функции, и это автоматически изменится снаружи.

В Python:

Python

1

2

3

4

5

6

7

8

9

10

11

defupone(mutable,index):

   mutable[index]=mutable[index].upper()

list_=["a","b","c"]

upone(list_,1)

print(list_) # ['a', 'B', 'c']

dict_={"a":"anders","b":"bengt"}

upone(dict_,"b")

print(dict_) # {'a': 'anders', 'b': 'BENGT'}

В Golang:

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

packagemain

import(

"fmt"

"strings"

)

funcupone_list(thing[]string,index int){

thing[index]=strings.ToUpper(thing[index])

}

funcupone_map(thing map[string]string,index string){

thing[index]=strings.ToUpper(thing[index])

}

funcmain(){

// mutable

list:=[]string{"a","b","c"}

upone_list(list,1)

fmt.Println(list)// [a B c]

// mutable

dict:=map[string]string{

"a":"anders",

"b":"bengt",

}

upone_map(dict,"b")

fmt.Println(dict)// map[a:anders b:BENGT]

}

Структуры

В Python для создания пользовательских наборов полей используются классы. Метод __init__ вызывается при создании экземпляра класса:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

frommathimportsqrt

classPoint(object):

   def__init__(self,x,y):

       self.x=x

       self.y=y

defdistance(point1,point2):

   returnsqrt(point1.x*point2.x+point1.y*point2.y)

p1=Point(1,3)

p2=Point(2,4)

print(distance(p1,p2)) # 3.74165738677

В Go существует специальное ключевое слово struct:

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

packagemain

import(

"fmt"

"math"

)

typePointstruct{

xfloat64

yfloat64

}

funcdistance(point1 Point,point2 Point)float64{

returnmath.Sqrt(point1.x*point2.x+point1.y*point2.y)

}

// Так как структуры автоматически копируются при передаче,

// лучше передавать их в функцию как указатели

funcdistance_better(point1 *Point,point2 *Point)float64{

returnmath.Sqrt(point1.x*point2.x+point1.y*point2.y)

}

funcmain(){

p1:=Point{1,3}

p2:=Point{2,4}

fmt.Println(distance(p1,p2))         // 3.7416573867739413

fmt.Println(distance_better(&p1,&p2))// 3.7416573867739413

}

Методы

В Python методы определяются прямо внутри классов с помощью ключевого слова def, и первым аргументом принимают ссылку на экземпляр класса:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

frommathimportsqrt

classPoint(object):

   def__init__(self,x,y):

       self.x=x

       self.y=y

   defdistance(self,other):

       returnsqrt(self.x*other.x+self.y*other.y)

p1=Point(1,3)

p2=Point(2,4)

print(p1.distance(p2)) # 3.74165738677

print(p2.distance(p1)) # 3.74165738677

Методы в 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

packagemain

import(

"fmt"

"math"

)

typePointstruct{

xfloat64

yfloat64

}

func(thisPoint)distance(other Point)float64{

returnmath.Sqrt(this.x*other.x+this.y*other.y)

}

func(this*Point)distance_better(other *Point)float64{

returnmath.Sqrt(this.x*other.x+this.y*other.y)

}

funcmain(){

p1:=Point{1,3}

p2:=Point{2,4}

fmt.Println(p1.distance(p2))        // 3.7416573867739413

fmt.Println(p1.distance_better(&p2))// 3.7416573867739413

}

Горутины

Горутины языка Go – это независимые параллельные операции. Сама функция main является горутиной. Для их определения используется оператор 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

packagemain

import(

"fmt"

"io/ioutil"

"net/http"

"sync"

)

funcf(url string){

response,err:=http.Get(url)

iferr!=nil{

panic(err)

}

deferresponse.Body.Close()

body,err:=ioutil.ReadAll(response.Body)

iferr!=nil{

panic(err)

}

fmt.Println(len(body))

}

// Смотрите пример на https://golang.org/pkg/sync/#WaitGroup

funcmain(){

varwg sync.WaitGroup

urls:=[]string{

"https://www.peterbe.com",

"https://python.org",

"https://golang.org",

}

for_,url:=rangeurls{

wg.Add(1)

gofunc(url string){

deferwg.Done()

f(url)

}(url)

}

// ждем, когда горутина закончит работу

wg.Wait()

}

В Python параллельное программирование также возможно с помощью модуля multiprocessing:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

importurllib2

importmultiprocessing

deff(url):

   req=urllib2.urlopen(url)

   try:

       print(len(req.read()))

   finally:

       req.close()

urls=("https://www.peterbe.com","https://python.org","https://golang.org")

if__name__=="__main__":

   p=multiprocessing.Pool(3)

   p.map(f,urls)

Args

Часто бывает необходимо получить аргументы переданные в команде терминала. Рассмотрим эту задачу на примере команды:

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.

Вот аналогичный код для языка 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

packagemain

import"fmt"

funcuniqify(items[]string)[]string{

uniq:=make([]string,0)

seen:=make(map[string]bool)

// для большей эффективности:

// seen := make(map[string]struct{})

// https://stackoverflow.com/questions/37320287/maptstruct-and-maptbool-in-golang

for_,i:=rangeitems{

if_,exists:=seen[i];!exists{

uniq=append(uniq,i)

seen[i]=true

}

}

returnuniq

}

funcmain(){

items:=[]string{"B","B","E","Q","Q","Q"}

items=uniqify(items)

fmt.Println(items)// prints [B E Q]

}

Учиться новому проще на фундаменте имеющихся знаний. Сравнивая особенности Python и языка Go, мы находим множество сходств между ними. Это позволяет глубже понять концепции программирования и проще изучить новое.

Оригинальная статья

Книги по Go и другие полезные материалы:


Источник: proglib.io

Комментарии: