DEV Community

Jonatas Baldin
Jonatas Baldin

Posted on

Python Idioms in Go

Originally posted at Deploy Everyday.


In the development world, Python is my native language. It all started in 2015 with a little project called itopy. Python is my favorite way to translate thought into applications due to its minimal learning curve, simplicity and elegance.

But like in real life, learning a second language has tons of benefits. You become able to navigate within other countries, talk to strangers across borders, understand their culture and way of living. It opens your mind to think differently about things and to construct new knowledge on top of established ideas. The last point is very important: naturally, we learn new concepts by comparing them to the ones we already know.

Studying a second programming language is no different. We compare their syntax, semantics, patterns and idioms. If something is the same, it just clicks! Otherwise, time is needed to build up knowledge.

In 2019 I choose Golang for my next adventure. It is vastly used in infrastructure projects (which I love!), has a community full of Pythonistas and is becoming very very popular. My path to get it was full of: "I can do X in Python, how do I do it in Go"? This article is a collection of those moments.

PS1: I won't go in detail for the examples. A quick Googling will help you to understand them in depth.

PS2: I didn't include import statements in Go for the sake of brevity.

Popping from a List

List are beautifully managed in Python:

words = ["guess", "whos", "back"]
word = words.pop()

print(word)
# back
print(words)
# ['guess', 'whos']
Enter fullscreen mode Exit fullscreen mode

Slices – Go's lists – are a bit more complicated, but the SliceTricks guide will help out.

words := []string{"guess", "whos", "back"}
word, words := words[len(words)-1], words[:len(words)-1]

fmt.Println(word)
// back
fmt.Println(words)
// [guess whos]
Enter fullscreen mode Exit fullscreen mode

Pushing to a List

Pushing – or appending – is very similar:

🐍:

words = ["mangoes", "peaches", "limes"]
words.append("the sweet life")

print(words)
# ['mangoes', 'peaches', 'limes', 'the sweet life']
Enter fullscreen mode Exit fullscreen mode

❗️🐍:

words := []string{"mangoes", "peaches", "limes"}
words = append(words, "the sweet life")

fmt.Println(words)
// [mangoes peaches limes the sweet life]
Enter fullscreen mode Exit fullscreen mode

List Comprehension

Personally, the most beautiful sugary diet of Python:

numbers = [1,2,3,4]                        
double = [number * 2 for number in numbers]

print(double)
# [2, 4, 6, 8]
Enter fullscreen mode Exit fullscreen mode

Unfortunately, in Go, more work is needed:

numbers := []int{1, 2, 3, 4}
var double []int

for _, number := range numbers {
    double = append(double, number*2)
}

fmt.Println(double)
// [2 4 6 8]
Enter fullscreen mode Exit fullscreen mode

Dictionary Comprehension

Like a list comprehension, but iterating over values and keys in dictionary:

numbers = {'a': 1, 'b': 2, 'c': 3}
double = {key: value * 2 for key, value in numbers.items()}

print(double)
# {'a': 2, 'b': 4, 'c': 6}
Enter fullscreen mode Exit fullscreen mode

Again, with Golang, more work:

numbers := map[string]int{"a": 1, "b": 2, "c": 3}
double := make(map[string]int)

for key, value := range numbers {
    double[key] = value * 2
}

fmt.Println(double)
// map[a:2 b:4 c:6]
Enter fullscreen mode Exit fullscreen mode

Sets (and intersection)

Sets are like lists, but they have no duplicated items and some special properties. One of them is called intersection, which returns items showing up in both sets.

Python has a set data structure with all these features:

dna1 = {"loyalty", "royalty", "dna"}
dna2 = {"power", "poison", "pain", "dna"}

print(dna1 & dna2)
# {'dna'}
Enter fullscreen mode Exit fullscreen mode

Sometimes Golang is too simple. It has no concept of set nor intersection. However, the same can be accomplish using a map and two loops. Not as elegant as Python, that's for sure.

dna1 := make(map[string]bool)
dna1["loyalty"] = true
dna1["royalty"] = true
dna1["dna"] = true

dna2 := make(map[string]bool)
dna2["power"] = true
dna2["poision"] = true
dna2["pain"] = true
dna2["dna"] = true

intersection := make(map[string]bool)
for d1, _ := range dna1 {
    _, exists := dna2[d1]
    if exists {
        intersection[d1] = true
    }
}

fmt.Println(intersection)
Enter fullscreen mode Exit fullscreen mode

Enumerating

In Python, we use enumerate() to loop over a list with its index:

words = ["i'll", "tell", "what", "I", "want"]

for index, word in enumerate(words):
    print(index, word)
Enter fullscreen mode Exit fullscreen mode

In Go, this is the default behavior:

words := []string{"i'll", "tell", "what", "I", "want"}

for index, word := range words {
    fmt.Println(index, word)
}
Enter fullscreen mode Exit fullscreen mode

Output:

0 i'll
1 tell
2 what
3 I
4 want
Enter fullscreen mode Exit fullscreen mode

x in y

Python's in operator is used to tell if something is inside another thing, like in a list:

words = ["i'm", "a", "bad", "guy"]
if "bad" in words:
    print("duh!")

# duh!
Enter fullscreen mode Exit fullscreen mode

Or to verify if a key exists in a dict:

words = {"tough": "guy", "rough": "guy", "enough": "guy"}
if "tough" in words:
    print("billie is the best")

# billie is the best
Enter fullscreen mode Exit fullscreen mode

Go has no in operator 😢 The verbose road needs to be taken:

words := []string{"i'm", "a", "bad", "guy"}

for _, word := range words {
    if word == "bad" {
        fmt.Println("duh!")
    }
}

// duh!
Enter fullscreen mode Exit fullscreen mode

A bit of sugar is available to check keys in a map:

words := map[string]string{"tough": "guy", "rough": "guy", "enough": "guy"}

_, ok := words["tough"]
if ok {
    fmt.Println("billie is the best")
}

// billie is the best
Enter fullscreen mode Exit fullscreen mode

Lambda Functions

Lambdas, or anonymous functions, are nameless functions usually doing only one very simple thing. In Python, a lambda function any number of arguments but is restricted to a single expression – just one thing can happen after the ::

multiply = lambda x: x*x

print(multiply(2))
# 4
Enter fullscreen mode Exit fullscreen mode

Golang also has lambda functions, but they can spawn multiple statements:

double := func(x int) int { return x * 2 }

fmt.Println(double(2))
// 4

printAndDouble := func(x int) int { fmt.Println("one thing"); return x * 2}

fmt.Println(printAndDouble(2))
// one thing
// 4
Enter fullscreen mode Exit fullscreen mode

Context Manager

A context manager is a very powerful pattern in Python. It's usually used to manage resources, like closing a file automatically so you don't forget it. This is a very simplified explanation, it can do a lot more. Here is a link for further explanation.

with open('/etc/passwd', 'r') as file:
    print(file.read())

# all your passwords 👺

file.read()
# ValueError: I/O operation on closed file.
Enter fullscreen mode Exit fullscreen mode

Golang has a kinda similar concept called defer. It ensures that a function call is performed at the end of the current function, so you don't forget to close your file after you've opened it.

func main() {
    file, _ := os.Open("/etc/passwd")
    // called when this function block finishes 
    defer file.Close()

    data, _ := ioutil.ReadAll(file)

    fmt.Println(data)
    // all your passwords, in bytes 👺
}
Enter fullscreen mode Exit fullscreen mode

Assignment Expressions

In Python 3.8, assignment expressions define a new notation: NAME := expr, where you can assign a value to a variable during an expression. An example is using the if statement:

def x():
    return 10

if (number := x()) is number == 10:
    print(10)

# 10
Enter fullscreen mode Exit fullscreen mode

Go also supports it:

func x() int { return 10 }

func main() {
    if x := x(); x == 10 {
        fmt.Println(x)
    }
}

// 10
Enter fullscreen mode Exit fullscreen mode

Classes with initialization and methods

Python has Classes to create a new type with properties and methods, which can be used to create instances of that type:

class Double: 
    def __init__(self, x): 
        self.x = x 

    def double(self): 
        return self.x * 2 

instance = Double(2) 

print(instance.double())     
# 4
Enter fullscreen mode Exit fullscreen mode

You can accomplish basically the same with Golang's struct:

type Double struct {
    x int
}

func (d *Double) double() int {
    return d.x * 2
}

func main() {
    instance := Double{x: 2}

    fmt.Println(instance.double())
    // 4
}
Enter fullscreen mode Exit fullscreen mode

Sugary Python, Simple Go

Even though I don't add sugar to my coffee, I'd pour some over Go. Learning Go after Python was full of surprises: I was used to a lot of tricks and one-liners. To this day, I read the SliceTricks page more than I'd be proud of.

Besides frustrations here and there, I love Go. Statically typed, easy coroutines, single binary, multi-architecture compilation and more! It has so many great features!

Both languages have their place in my tool belt and my heart ❤️

Thanks to the people from Twitter and DEV to share their favorite idioms 💙

Share your favorite idioms in the comments below and let's try to find its Go-like implementation ✨

Discussion (7)

Collapse
jrezzende profile image
jrezzende

Baita post, Jonatas!

Eu também estou no processo de aprender Go, sendo um dev que utiliza na maioria do tempo Python.

Teria alguma dica de material que você utilizou/utiliza neste processo? (a dica do SliceTricks já vai me ajudar muito!)

Collapse
jonatasbaldin profile image
Jonatas Baldin Author

Valeu, meu querido!

Eu recomendo o livro The Go Programming Language (amazon.com/Programming-Language-Ad...). Esse é o Fluent Python pra Go heh

Collapse
jrezzende profile image
jrezzende

Perfeito, Luciano Ramalho nunca errou. Valeu!

Collapse
delta456 profile image
Swastik Baranwal

I think you should also state that Python is OOP and Go is not.

Great comparison though!

Collapse
jonatasbaldin profile image
Jonatas Baldin Author

It's OOP-ish, right? They just picked up a flavor of OOP:

  • Structs are like classes
  • Structs methods are like classes methods
  • Encapsulation is done through lower/upper case methods
  • Inheritance was not picked, but you can do composition

So, I'd say it's a kinda of OOP, just with less complexity.

What do you think?

Collapse
delta456 profile image
Swastik Baranwal • Edited on

Visit this for more info.

I think that Go is based on OOPs. So technically yeah.

Thread Thread
jonatasbaldin profile image
Jonatas Baldin Author

Yeah, this FAQ helps, thanks :)