Golang for Python Devs

Image credit: Photo by Aleksandar Pasaric from Pexels

After doing Python for the past 10 years, Go seemed totally foreign. But a few days and a few hundred online searches later, I had finally cobbled together my first “real” Go program. The one thing I wish I had was a 1:1 guide mapping Python language concepts to Go... and not just the equivalent syntax, but the correct way of expressing these same ideas in Go.

This is not going to be a tutorial, but more of a guide showing the most common Python idioms and their equivalent in Go:

Culture

Python programmers go by the demonym “Pythonistas”. Go programmers are “Gophers”. So don't get a big head once you’ve mastered Go - you’re still in the animal kingdom!

While both are open source languages, Python is totally owned by the community and run by the non-profit Python Software Foundation (PSF). Go is owned and run by Google, and was designed to serve Google’s needs. This information is not relevant to your own development, but it is always good context to bear in mind when reading through mailing lists, forums, etc.

Naming Conventions

Python follows a few style conventions:

  • Functions and variables are snake case snake_case.
  • Classes are Pascal case PascalCase.
  • Globals or “constants” are often all caps CONSTANT.
  • Private members begin with an underscore _private.

Golang has a few different, simpler rules:

  • Anything that begins with a lowercase letter is private.
  • Anything that begins with a capital letter is public.
  • Depending on public or private, everything follows camelCase (private) or PascalCase (public).

Python:

# A public "constant" that should not be changed.
MY_CONSTANT = "Apple"

# Public function.
def do_thing():
    return "Banana"

# "Private" function.
def _do_thing():
    return "Cherry"

# A public class.
class MyClass:
    pass

Golang:

// A public constant that cannot be changed.
const MyConstant = "Apple"

// Public function.
func DoThing() string {
    return "Banana"
}

// Private function.
func doThing() string {
    return "Cherry"
}

// A public struct.
type MyStruct struct {}

Comments & Docstrings

Python has one primary comment delimiter, the octothorpe #. Likewise, Go’s primary comment delimiter is the double-slash //. Go also supports multi-line C-style comments /* */, but they are seldom used.

Python also has a special docstring which is the usage of a triple-quote string directly below a class or function definition. The Golang equivalent to a Python docstring is an ordinary comment placed before the function definition, starting with the function’s name.

Similar to Python, docstrings are applicable to functions, global/constant variables, and classes (structs in Golang). Basically, anything that is not indented should have a docstring. And similar to Sphinx, Go tools will generate human-readable HTML documentation based on these comments.

Python:

def add_numbers(a, b):
    """
    Adds numbers together and returns the sum.

    Docstrings can also be multi-line as so.
    """

    # This is a comment. Adds the numbers.
    return a + b

Golang:

// AddNumbers adds numbers together and returns the sum.
//
// Comments can also be multi-line as so.
func AddNumbers(a int, b int) int {

    // This is a comment. Adds the numbers.
    return a + b
}

Variables

The biggest difference in Go syntax versus Python is how variables are defined. This primarily relates to the assignment (= and :=) operators.

Python:

# Define a string variable.
fruit = "Apple"

Golang: In Go, we first have to define the variable, before we can assign to it. This is essentially "long form" Go.

// Define a string variable.
var fruit string
fruit = "Apple"

This can be condensed into one line:

// Define a string variable.
var fruit string = "Apple"

And this can be condensed even further with the ”short assignment” operator :=. This operator infers the type of the object, and is very heavily used in Go code due to its conciseness.

// Define a string variable.
fruit := "Apple"

Be aware though, that once a variable is first defined, you only use the normal assignment operator = to change its value afterwards.

// Define a string variable.
fruit := "Apple"
// Change its value.
fruit = "Banana"
fruit = "Cherry"

String Formatting

Python has three ways of doing string formatting: C-style % operator, .format(), and the newer f-strings f"".

Golang uses the traditional C-style formatting syntax, which is provided by the fmt package in the standard library.

Python:

name = "Monty"
age = 40

# C-style.
info = "%s is %d years old" % (age, name)

# Format.
info = "{0} is {1} years old".format(age, name)

# F-string.
info = f"{name} is {age} years old"

Golang:

import "fmt"

name := "Monty"
age := 40

info := fmt.Sprintf("%s is %d years old", name, age)

Similar to Python, Golang format strings can handle various types (strings, ints, etc.) and can do various things such as padding, decimal point precision, etc. Refer to the fmt package documentation. The overal formatting features are not nearly as robust as what you get with Python, but Go provides enough basics for doing console and log output.

If you need really advanced string formatting, Go provides a templating language in the standard library, which is very similar to Jinja.

For Loops

The for loop in Python has super simple syntax compared to other languages. The behavior of Python’s for loop (for x in y) is often referred to as a for-each loop in other languages. The for loop in Go can behave like a for-each using the range keyword which will iterate over the contents.

range behaves similar to Python’s enumerate function, which returns both the current index number and the object.

Python:

# A list of strings.
fruits = ["Apple", "Banana", "Cherry"]

# Print each fruit with its index.
for (index, fruit) in enumerate(fruits):
    print(f"Fruit {index} is {fruit}")

# Print each fruit.
for fruit in fruits:
    print(fruit)

Golang:

import "fmt"

// A list of strings.
fruits := []string{"Apple", "Banana", "Cherry"}

// Print each fruit with its index.
for index, fruit := range fruits {
    fmt.Printf("Fruit %d is %s\n", index, fruit)
}

// Print each fruit. Here we are throwing away the index by assigning it to _
// which is a special variable in Go used for ignoring the output.
for _, fruit := range fruits {
    fmt.Println(fruit)
}

Lists

Slices in Golang are equivalent to Lists in Python. Golang also has more traditional arrays, like you’d see in C, but when starting out just focus on slices.

Python:

# Define a list.
# The trailing comma after "Cherry" is not required, but recommended.
fruits = [
    "Apple",
    "Banana",
    "Cherry",
]

# Zero-indexed random access.
apple = fruits[0]
cherry = fruits[2]

# Negative index are also supported.
cherry = fruits[-1]

# Slices give you a subset of the list, using colons.
apple_banana = fruits[0:2]
apple_banana = fruits[:2]

# Append to the list.
fruits.append("Durian")

# Combine two lists.
veggies = ["Tomato", "Lettuce"]
foods = fruits + veggies

Golang:

// Define a slice.
// The trailing comma after "Cherry" is required or it won't compile!
fruits := []string{
    "Apple",
    "Banana",
    "Cherry",
}

// Zero-indexed random access.
apple := fruits[0]
cherry := fruits[2]

// Golang cannot do negative indexes. So you'll have to count backwards instead.
cherry = fruits[len(fruits)-1]

// As the name indicates, you can take slices of a slice.
apple_banana := fruits[0:2]
apple_banana = fruits[:2]

// Append to the slice. The append function in Go actually returns a new slice.
fruits = append(fruits, "Durian")

// Combine two lists.
// Here we are using the special variadic syntax `...` which has a similar
// behavior to the commonly used `*args` in Python.
veggies := []string{"Tomato", "Lettuce"}
foods := append(fruits, veggies...)
// This is equivalent to fully exploding the veggies slice and appending each
// element separately.
foods = append(fruits, "Tomato", "Lettuce")

Dictionaries

Maps are the Golang equivalent to Python dictionaries. Functionally they are very similar, as maps provide instant access to a value by a key. And similar to dictionaries, keys and values can be any object type, and maps can be nested.

The one difference is that there is no equivalent to Python’s update() in Go, however a simple 3-line for loop can accomplish the same thing.

Python:

# Define the dictionary.
fruits = {
    "a": "Apple",
    "b": "Banana",
    "c:" "Cherry",
}

# Access.
word = fruits["a"]
# or...
word = fruits.get("a", "Fallback value")

# Assignment.
fruits["d"] = "Dragonfruit"

# Looping/iterating over a dictionary.
for key in fruits:
    value = fruits[key]
    print(f"{key}: {value}")

# Updating or merging two dictionaries together.
fruits.update({
    "c": "Cantaloupe",
    "d": "Durian",
})

Golang:

// First we must allocate a new map with using ``make()``,
// declaring key & value types. The syntax here is:
//
//     map[key]value
//
var fruits map[string]string
fruits = make(map[string]string)
// As a shortcut, you could instead allocate it with empty contents.
fruits = map[string]string{}
// Or you can allocate one with pre-defined contents.
fruits = map[string]string{
    "a": "Apple",
    "b": "Banana",
    "c": "Cherry",
}

// Access.
word := fruits["a"]

// Assignment.
fruits["d"] = "Dragonfruit"

// Looping/iterating over a map.
for _, key := range fruits {
    value := fruits[key]
    fmt.Printf("%s: %s", key, value)
}

// Updating or merging two maps together.
// There is no direct equivalent to Python's `update()` in Go,
// but you can accomplish it with a simple loop.
updateFruits := map[string]string{
    "c": "Cantaloupe",
    "d": "Durian",
}
for _, key := range updateFruits {
    fruits[key] = updateFruits[key]
}

Classes

Go is not object oriented, and therefore does not have classes. This is a big shift from thinking in Python. However, Go does have the struct which is essentially a very simple class used to hold data. By combining structs with functions, you can approximate similar feature parity with Python classes.

After adjusting to the Golang way of using structs with functions, I actually prefer it as it is even simpler than Python classes, albeit less flexible. It actually feels more Pythonic than Python in some respects!

Python:

class Fruit:
    """
    Holds nutrition facts about a fruit.
    """

    def __init__(self, name, sugar, calories):
        self.name = name
        self.sugar = sugar
        self.calories = calories

    def take_bite(self):
        """
        Taking a bite of the fruit reduces the calories by 10.
        """
        self.calories = self.calories - 10


# Create a Fruit instance.
apple = Fruit(
    name="Apple",
    sugar="96 grams",
    calories=100
)

# Take a bite!
apple.take_bite()

Golang:

// Fruit holds nutrition facts about a fruit.
type Fruit struct {
    Name     string
    Sugar    string
    Calories int
}

// TakeBite reduces the calories of the fruit by 10.
//
// Note that instead of `self`, we are providing the function definition with a
// reference to a `Fruit` object `f` (indicated by the asterisk `*Fruit`).
func (f *Fruit) TakeBite() {
    f.Calories = f.Calories - 10
}

func main() {

    // Create a Fruit instance.
    apple := Fruit{
        Name:    "Apple",
        Sugar:   "96 grams",
        Calories: 100,
    }

    // Take a bite!
    apple.TakeBite()
}

Keep in mind, Go is not object oriented. Therefore there is no direct equivalent to Python class inheritance, super(), reflection/introspection, etc. If you think in terms of parent-child objects, your biggest challenge will probably be in how to shift your thinking to structs and functions.

Next Steps

Hopefully this guide will help as you attempt to re-wire your Python brain. I would recommend first following the guides in the Getting Started section of the Go docs. The best way to get started after that is to dive right in and start writing a little command line tool or web app.

The best way to pick up the general “feel” of Go is to read through some of the standard library code and comments. I would also highly recommend reading Effective Go once you get past your first Golang tutorial. It really clarifies all of the language’s conventions and best practices.

Go has many other analogs to Python such as datetimes, file handling, internet protocols, etc. But once you know the basic conventions and built-ins described above, you will start picking up Go’s other functionality in no time.

Happy gophering!