Packages & Modules
Python has modules (files), packages (directories with __init__.py), and a sprawling packaging ecosystem. Go has packages (directories) and modules (versioned dependency units defined by go.mod). The system is simple and deterministic.
Package Basics
// Every file starts with a package declaration
package store // all .go files in this directory share this package
// Importing
import (
"fmt" // standard library
"encoding/json" // nested stdlib package
"github.com/you/project/internal/store" // your package
)
Visibility
Python uses _underscore convention. Go uses capitalization. It's enforced by the compiler.
| Python | Go | Rule |
|---|---|---|
def public_func() | func PublicFunc() | Uppercase first letter = exported |
def _private_func() | func privateFunc() | Lowercase first letter = unexported |
class MyClass | type MyStruct struct | Same rule: MyStruct exported, myStruct not |
self.public_field | s.PublicField | Applies to struct fields too |
The internal Directory
// Code in internal/ can only be imported by parent packages
project/
├── cmd/server/main.go // can import internal/
├── internal/
│ └── store/store.go // private to this module
└── pkg/
└── api/api.go // public — importable by anyone
Dependency Management
# Initialize module
go mod init github.com/you/project
# Add dependency
go get github.com/gorilla/[email protected]
# Tidy — remove unused, add missing
go mod tidy
# Vendor — copy deps locally (optional)
go mod vendor
Go prohibits circular package dependencies at compile time. If package A imports B, B cannot import A. This forces clean dependency graphs. Python allows circular imports (with hacks) — Go does not. If you hit a cycle, extract shared types into a third package.
Testing
Python uses pytest (third-party, installed separately). Go has testing built into the language and toolchain. Test files live next to production code, named *_test.go.
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, 1) == 0
func TestAdd(t *testing.T) {
got := Add(2, 3)
if got != 5 {
t.Errorf("Add(2, 3) = %d, want 5", got)
}
}
func TestAddNegative(t *testing.T) {
got := Add(-1, 1)
if got != 0 {
t.Errorf("Add(-1, 1) = %d, want 0", got)
}
}
Table-Driven Tests
Go's idiomatic pattern for parameterized tests — replaces pytest's @pytest.mark.parametrize.
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 2, 3, 5},
{"negative", -1, 1, 0},
{"zeros", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("got %d, want %d", got, tt.expected)
}
})
}
}
Benchmarks
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
// Run: go test -bench=. -benchmem
// Output: BenchmarkAdd-8 1000000000 0.3 ns/op 0 B/op 0 allocs/op
Go's testing uses if statements and t.Errorf. No assert functions in the standard library. This is intentional — the team believes conditionals are clearer than assert DSLs. Third-party libraries like testify exist if you disagree, and many codebases use them.