From ๐Ÿ Python to ๐Ÿฆซ Go

Introduction to Go for Python devs

Selected References

๐Ÿงญ Typing

Dynamic vs Static

๐Ÿ The Good Old Days

$ python factorial.py 10
3628800
# filename: factorial.py
import sys

def factorial(n):
    fact_n = 1
    for i in range(n):
        fact_n = fact_n * (i + 1)
    return fact_n

n = sys.argv[1]
print(factorial(n))

๐Ÿชฒ Ooops

The program fails at runtime:

$ python factorial.py 10
Traceback (most recent call last):
  File "/home/boisgera/tmp/tmp/factorial.py", line 11, in <module>
    print(factorial(n))
          ^^^^^^^^^^^^
  File "/home/boisgera/tmp/tmp/factorial.py", line 6, in factorial
    for i in range(n):
             ^^^^^^^^
TypeError: 'str' object cannot be interpreted as an integer

Traceback & Program Analysis

๐Ÿค”

  1. n should be an integer for range(n) to work,

  2. factorial(n): same analysis,

  3. but instead n a string,

  4. because the elements of sys.argv are strings !

๐Ÿ˜Œ

# filename: factorial.py
import sys

def factorial(n):
    fact_n = 1
    for i in range(n):
        fact_n = fact_n * (i + 1)
    return fact_n

n = sys.argv[1]  # ๐Ÿชฒ n should be an int
print(factorial(n))

๐Ÿฉน Fix

# filename: factorial.py
import sys

def factorial(n):
    fact_n = 1
    for i in range(n):
        fact_n = fact_n * (i + 1)
    return fact_n

n = int(sys.argv[1])  # ๐Ÿฉน str -> int
print(factorial(n))
$ python fact.py 10
3628800

Nowadays: Gradual Typing

The same program, with a few type hints:

# filename: factorial.py
import sys

def factorial(n: int) -> int:
    for i in range(n):
        fact_n = fact_n * (i + 1)
    return fact_n

n = sys.argv[1]
print(factorial(n))

Static Type Checking

With mypy:

$ mypy factorial.py 
factorial.py:11: error: 
Argument 1 to "factorial" has incompatible type "str"; 
expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

in Visual Studio Code, with mypy enabled:

All types declared

# filename: factorial.py
import sys

def factorial(n: int) -> int:
    i: int
    fact_n: int = 1
    for i in range(n):
        fact_n = fact_n * (i + 1)
    return fact_n

n: int = sys.argv[1]
print(factorial(n))

๐Ÿ

  • Python is dynamically typed: the same variable may refer to different types of object at different moments.

  • Developers may use (static) type hints + static type checkers to willingly restrict this freedom.

๐Ÿฆซ

  • Go is statically typed: the type of every variable is known at compile-time.

  • Because it has been either:

    • declared by the developper or,

    • inferred by the compiler.

var message string       // declaration
message = "Hello world!" // assignment
var message string = "Hello world!" // combined

In this context, the type of message is obvious;
we can drop the explicit type information.

var message = "Hello world!"  // combined

In the body of functions (โš ๏ธ but not at the top-level!)

var message = "Hello world!"  // combined

can be replaced with the short variable declaration:

message := "Hello world!"

which feels like dynamical typing (but isn't ๐Ÿ˜€).

Buggy Program (invalid types)

package main

func main() {
    a := "Hello world!"
    a = "Hello gophers! ๐Ÿฆซ"
    a = 42
    println(a)
}

Compilation Step

$ go build sketch.go 
# command-line-arguments
./sketch.go:6:6: 
cannot use 42 (untyped int constant) as string value in assignment

Basic Types

Python Go
bool bool
int int
float float64
str string

๐Ÿงญ Functions

๐Ÿ Python

from datetime import date

def get_duration(year: int) -> int:
    return date.today().year - year

๐Ÿฆซ Go

import "time"

func GetDuration(year int) int {
    return time.Now().Year() - year
}

๐Ÿ Lists ๐Ÿฆซ Slices

>>> l = [1, 2, 4]
>>> l[0]
1
>>> l.append(8)
>>> l
[1, 2, 4, 8]
>>> for i, n in enumerate(l):
...     print(i, n)
... 
0 1
1 2
2 4
3 8   

(with the go interpreter/REPL yaegi)

> s := []int{1, 2, 4}
: [1 2 4]
> s[0]
: 1
> s = append(s, 8)
: [1 2 4 8]
> for i, n := range s { println(i, n) }
0 1
1 2
2 4
3 8
: [1 2 4 8]

๐Ÿ Dicts ๐Ÿฆซ Maps

>>> d = {"a": 1, "b": 2}
>>> d["a"]
1
>>> d["c"] = 3
>>> d
{'a': 1, 'b': 2, 'c': 3}
>>> for k, v in d.items():
...     print(k, v)
... 
a 1
b 2
c 3
> m := map[string]int{"a": 1, "b": 2}
: map[a:1 b:2]
> m["a"]
: 1
> m["c"] = 3
: 0
> m
: map[a:1 b:2 c:3]
> for k, v := range m { println(k, v) }
a 1
b 2
c 3
: map[a:1 b:2 c:3]

๐Ÿ Objects ๐Ÿฆซ Structs

class Person:
    def __init__(self, name, year):
        self.name = name
        self.year = year

def main():
    guido = Person(name="Guido van Rossum", year=1956)
    print(guido.name)
    print(guido.year)
package main

type Person struct {
    Name string
    Year int
}

func main() {
    rob := Person{Name: "Robert Pike", Year: 1956}
    println(rob.Name)
    println(rob.Year)
}

๐Ÿงญ Object Methods

๐Ÿ Python methods

from datetime import date

class Person:
    def __init__(self, name, year):
        self.name = name
        self.year = year
    def get_age(self):
        return date.today().year - self.year

def main():
    guido = Person(name="Guido van Rossum", year=1956)
    print(guido.name)
    print(guido.year)
    print(guido.get_age())

๐Ÿฆซ Go Methods

package main

import "time"

type Person struct {
    Name string
    Year int
}

func (p Person) Age() int {
    return time.Now().Year() - p.Year
}

func main() {
    rob := Person{Name: "Robert Pike", Year: 1956}
    println(rob.Name)
    println(rob.Year)
    println(rob.Age())
}

๐Ÿฆซ Go Methods (continued)

...

func (p Person) SetName(name string) {
    p.Name = name
}

func (p Person) SetYear(year int) {
    p.Year = year
}

func main() {
    rob := Person{} // โ„น equivalent to Person{Name: "", Year: 0}
    rob.SetName("Robert Pike")
    rob.SetYear(1956)
    println(rob.Name)
    println(rob.Year)
    println(rob.Age())
}
$ go run app.go 

0
2024

Uhu?

Assignment

What is the semantics of assignment ?

sum = 1 + 1
  • ๐Ÿงฎ Evaluate the right-hand side

  • ๐Ÿคจ ???

  • ๐Ÿ’ธ Profit!

๐Ÿ Assignment in Python

Variables refer to a location in memory (via a pointer).

Assignment rebinds the pointer to a new location.

>>> a = 1 + 1
>>> print(hex(id(a)))
0x559b96062d60
>>> a = 42
>>> print(hex(id(a)))
0x559b96063260

๐Ÿฆซ Assignment in Go

Variables store data in a fixed location:

package main

func main() {
    a := 1 + 1
    println(&a)
    a = 42
    println(&a)
}

Output:

0xc00003e728
0xc00003e728

We can also store content at a location

package main

func main() {
    a := 1 + 1
    p := &a
    *p = 42
    println(a)
}

Output:

42

Back to the Go methods

func (p *Person) SetName(name string) {
    p.Name = name
}

func (p *Person) SetYear(year int) {
    p.Year = year
}

func main() {
    rob := Person{}
    rob.SetName("Robert Pike")
    rob.SetYear(1956)
    println(rob.Name)
    println(rob.Year)
    println(rob.Age())
}
$ go run app.go 
Robert Pike
1956
68

๐Ÿ‘

๐Ÿงน Clean-up

$ go mod init app

Crรฉe le fichier go.mod:

module app

go 1.25.1

person.go

package main

import "time"

type Person struct {
    Name string
    Year int
}

func (p Person) Age() int {
    return time.Now().Year() - p.Year
}

func (p *Person) SetName(name string) {
    p.Name = name
}

func (p *Person) SetYear(year int) {
    p.Year = year
}

app.go

package main

func main() {
    rob := Person{}
    rob.SetName("Robert Pike")
    rob.SetYear(1956)
    println(rob.Name)
    println(rob.Year)
    println(rob.Age())
}

Compilation

On my (Linux) computer,

$ go build

creates a app executable for Linux.

$ ./app
Robert Pike
1956
68

๐Ÿ–ฅ๏ธ Cross-compilation

Set the environment variables GOOS and GOARCH, then go build as usual:

  • ๐ŸชŸ GOOS=windows and GOARCH=amd64

    windows/amd-64 executable app.exe.

  • ๐Ÿ GOOS=darwin GOARCH=arm64

    macOS/arm64 executable app.

  • ๐Ÿง GOOS=linux and GOARCH=arm64

    raspberry-pi/arm-64 executable app.

Embedded Systems

The Go Programming Language

Try TinyGo!

(Arduino, Raspberry Pi Pico, etc.)