In Python, a collection of (key, value)
pairs with unique keys is known as a dictionary. It is a built-in type called dict
and is declared either of the following ways (although the first is far more prominent):
aliases = {}
aliases = dict()
This same structure exists within Go as a map
. But since Go is a typed language, maps have types for both keys and values:
var aliases map[string]string
However, attempting to insert into this variable will cause a runtime panic:
aliases["Batman"] = "Bruce Wayne"
panic: assignment to entry in nil map
Before use, it must be "made" with the built-in function make
:
aliases = make(map[string]string)
This declare and make syntax is clunky. Map initialization is better done with short variable declarations - if possible - by using either of the following:
aliases := map[string]string{}
aliases := make(map[string]string)
Since type is inferred in the short form, it is impossible to use an un-made map. Any syntax mistakes made during declaration become compile-time errors:
aliases := map[string]string
syntax error: unexpected semicolon or newline, expecting {
Using the first short format also allows the declaration of initial values, much as like python:
aliases := map[string]string{
"Superman": "Clark Kent",
}
Declaring duplicate keys during initialization is a compile-time error:
aliases := map[string]string{
"Green Lantern": "John Stewart",
"Green Lantern": "Hal Jordan",
}
duplicate key "Green Lantern" in map literal
In contrast, Python allows duplicate keys during initialization, and will use the last given duplicate as the key's value:
aliases = {
"Green Lantern": "John Stewart",
"Green Lantern": "Hal Jordan",
}
{'Green Lantern': 'Hal Jordan'}
Although Python dictionaries are untyped - not all types are valid keys. Only hashable Python types can be used as keys, which excludes list
, set
, and other dictionaries. Using these types as keys will generate a runtime TypeError
exception:
aliases[dict()] = 'value'
TypeError: unhashable type: 'dict'
There are also illegal key types in Go, but using them will generate a compile-time error:
var nested map[map[string]string]string
invalid map key type map[string]string
In Python, frozenset
, tuple
, and custom classes can be used to provided multi-dimensional keys. In Go, this can also done with using custom struct
types or using arrays - which are hashable.
matrix := map[[2]int]string{
[2]int{1, 2}: "Battleship",
}
As opposed to slices - which are not.
matrix := map[[]int]string{
[]int{1, 2}: "Battleship",
}
invalid map key type []int
TODO
- Python can build dictionaries from iterables - Go equivalent?
- Caveats of interface{} as a key in Go - especially vis-à-vis type equality
Both Python and Go maps are mutable in the sense that keys can be overwritten post-initialization.
For maps, the built-in length function len
in Go is equivalent to that in Python:
len(aliases)
To delete, Python uses the del
keyword, whereas Go uses another built-in function called delete
:
delete(aliases, "Green Lantern")
Existence is checked in Python using the in
operator:
print("Green Arrow" in aliases) # False
There is no existence function, operator, or method in Go. Checking existence in Go is further complicated by non-existent keys returning the zero values of their value types:
ages := map[string]int{}
fmt.Println(ages["Bill"]) // 0
Existence must be checked by using the two variable assignment syntax:
ages := map[string]int{}
age, exists := ages["Bill"]
fmt.Println(age, exists) // 0, false
If the first variable is irrelevant, it can be ignored using the blank identifier _
:
ages := map[string]int{}
_, exists := ages["Bill"]
fmt.Println(exists) // false
The benefit of Go maps returning zero values for non-existent keys is that many values can be modified without initialization, for example:
villians := map[string][]string{}
villians["Batman"] = append(villians["Batman"], "The Joker", "Ra's al Ghul")
The equivalent behavior in Python would require the usage of the collections
module's defaultdict
:
from collections import defaultdict
villians = defaultdict(list)
villians["Batman"].extend(["The Joker", "Ra's al Ghul"])
TODO
Iterating over a dictionary in Python returns the key as the single iterable variable.
for key in aliases:
print(key)
Using range
in Go will also return the key:
for key := range aliases {
fmt.Println(key)
}
But iteration over key - value pairs is as simple as providing another destination variable:
for key, value := range aliases {
fmt.Println(key, value)
}
Whereas Python requires usage of the items
method:
for key, value in aliases.items():
print(key, value)
In the CPython implementation of Python, dictionary iteration can vary, but will remain consistent as long as the dictionary is not modified.link
Go does not guarantee iteration order, even between back-to-back iterations.link
Python provides a number of methods for dictionaries, including the keys
and values
methods that return a dictionary's keys and values, respectively, as lists.
Go maps have no built-in methods, though the keys
and values
methods can be replicated by attaching them to custom types:
type Counter map[string]int
func (c Counter) Keys() (keys []string) {
for key := range c {
keys = append(keys, key)
}
return
}
However, since Go lacks generics, these methods must be declared for each custom type.
Happy hacking!
aodin, 2015