Go vs Python: quan triar rendiment i quan triar velocitat de desenvolupament
Comparació pràctica entre Go i Python per a backend, APIs, concurrència, automatització i desplegament. Sense fanatismes, amb criteri tècnic real.

Porto anys escrivint Python cada dia. Scripts d’automatització, pipelines de dades, APIs internes amb FastAPI, scrapers, eines de línia de comandes. Python és el meu ganivet suís i no penso deixar-lo. Però fa un temps vaig començar a explorar Go per a serveis backend i hi ha coses que m’han sorprès. No perquè Go sigui “millor” que Python. Sinó perquè resol certs problemes d’una manera que Python, per disseny, no pot.
Aquest article no és una taula de features perquè triïs un bàndol. És el que he après treballant amb tots dos llenguatges en contextos reals: on cada un brilla, on pateix i quan val la pena considerar el canvi. Si véns de Python i estàs pensant en aprendre Go, aquí trobaràs criteri per decidir si et compensa.
Dues filosofies oposades que funcionen
Python i Go van néixer amb objectius diferents. Entendre això és clau per no comparar-los on no toca.
Python segueix la filosofia de “batteries included”. Vols fer scraping, tens BeautifulSoup i Scrapy. Vols una API, tens FastAPI i Flask. Vols machine learning, tens scikit-learn, PyTorch i tot l’ecosistema. Python confia que la productivitat del desenvolupador és el primer i accepta que això té un cost en rendiment.
Go fa exactament el contrari. La seva filosofia és la simplicitat deliberada. Poques formes de fer cada cosa. Sense herència, sense excepcions, sense genèrics fins fa poc. Un sistema de tipus estàtic que t’obliga a ser explícit. La biblioteca estàndard és potent però continguda. Go confia que un codi simple i predictible escala millor que un codi expressiu però impredictible.
La diferència fonamental: Python optimitza per al temps del desenvolupador en escriure el codi. Go optimitza per al temps de l’equip en mantenir-lo.
Això no és teoria. Ho notes en el dia a dia. En Python pots resoldre un problema en deu línies amb list comprehensions aniuades i un parell de lambdes. Elegant, compacte, Pythonic. En Go, el mateix problema et costarà trenta línies amb bucles for explícits i gestió d’errors a cada pas. Més verbós, sí. Però qualsevol persona de l’equip entendrà aquell codi en cinc segons sense necessitat de desenrotllar mentalment l’abstracció.
Cap de les dues aproximacions és millor. Depèn del problema.
Backend APIs: FastAPI vs Go net/http
Aquesta és la comparació més directa i on la majoria de gent comença a plantejar-se Go. Vegem un endpoint simple que retorna un usuari per ID.
Python amb FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
id: int
name: str
email: str
users_db: dict[int, User] = {
1: User(id=1, name="Roger", email="roger@example.com"),
}
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
user = users_db.get(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return userArranques amb uvicorn main:app --reload, tens docs automàtiques a /docs, validació de tipus integrada. Productivitat brutal.
Go amb net/http (biblioteca estàndard)
package main
import (
"encoding/json"
"net/http"
"strconv"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var usersDB = map[int]User{
1: {ID: 1, Name: "Roger", Email: "roger@example.com"},
}
func getUser(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("user_id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
user, exists := usersDB[id]
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{user_id}", getUser)
http.ListenAndServe(":8080", mux)
}Més codi, sí. Però fixa’t en el que obtens: sense dependències externes, un binari compilat, tipat estàtic real i control total sobre la resposta HTTP. No necessites framework, ni servidor ASGI, ni res més.
Si vols alguna cosa més propera a l’experiència de FastAPI, pots usar Gin i la cosa es simplifica:
func main() {
r := gin.Default()
r.GET("/users/:user_id", func(c *gin.Context) {
id, err := strconv.Atoi(c.Param("user_id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
user, exists := usersDB[id]
if !exists {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, user)
})
r.Run(":8080")
}Veredicte per a APIs
| Aspecte | Python (FastAPI) | Go (net/http / Gin) |
|---|---|---|
| Temps fins al primer endpoint | Minuts | Minuts |
| Documentació automàtica | Sí (OpenAPI integrat) | No (necessites eines addicionals) |
| Validació d’entrada | Pydantic integrat | Manual o amb binding de Gin |
| Rendiment sota càrrega | Bo (async) | Excel·lent |
| Dependències necessàries | uvicorn + FastAPI + Pydantic | Cap (biblioteca estàndard) |
| Corba d’aprenentatge | Baixa | Mitjana |
Per a APIs internes, prototips o serveis que no rebran milers de peticions per segon, FastAPI és difícil de superar. Per a serveis de producció amb alta concurrència i requisits de latència, Go té avantatge real. Si vols aprofundir, tinc un article dedicat a muntar una API REST amb Go des de zero.
Concurrència: on Go marca la diferència
Aquí és on la conversa es posa interessant de veritat. I on Python té una limitació estructural que no es resol amb biblioteques.
El problema del GIL a Python
Python té el Global Interpreter Lock (GIL). Això significa que, tot i que usis threads, només un thread executa codi Python alhora. Per a I/O (peticions HTTP, base de dades, fitxers) no importa gaire perquè els threads alliberen el GIL mentre esperen. Però per a CPU (processament de dades, càlculs) els threads de Python no et donen paral·lelisme real.
Python té asyncio per a concurrència cooperativa i multiprocessing per a paral·lelisme real, però cada opció ve amb els seus compromisos:
import asyncio
import httpx
async def fetch_url(client: httpx.AsyncClient, url: str) -> str:
response = await client.get(url)
return response.text
async def main():
urls = [f"https://httpbin.org/delay/1" for _ in range(10)]
async with httpx.AsyncClient() as client:
tasks = [fetch_url(client, url) for url in urls]
results = await asyncio.gather(*tasks)
print(f"Obtinguts {len(results)} resultats")
asyncio.run(main())Funciona bé per a I/O concurrent. Però async/await és contagiós: un cop entres al món async, tot el teu codi ha de ser async. I si necessites paral·lelisme de CPU, necessites multiprocessing, que crea processos separats amb la seva pròpia memòria i la complexitat que això implica.
Goroutines: concurrència com a ciutadà de primera classe
Go no té aquest problema. Les goroutines són lleugeres (uns pocs KB d’stack), les gestiona el runtime de Go (no el sistema operatiu) i pots llançar-ne milers sense pestanyejar:
package main
import (
"fmt"
"io"
"net/http"
"sync"
)
func fetchURL(url string, wg *sync.WaitGroup, results chan<- string) {
defer wg.Done()
resp, err := http.Get(url)
if err != nil {
results <- fmt.Sprintf("Error: %v", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
results <- fmt.Sprintf("OK: %d bytes", len(body))
}
func main() {
urls := make([]string, 10)
for i := range urls {
urls[i] = "https://httpbin.org/delay/1"
}
var wg sync.WaitGroup
results := make(chan string, len(urls))
for _, url := range urls {
wg.Add(1)
go fetchURL(url, &wg, results)
}
go func() {
wg.Wait()
close(results)
}()
for result := range results {
fmt.Println(result)
}
}La diferència clau: a Go la concurrència és part del llenguatge, no un afegit. No hi ha “mode async” i “mode sync”. Tot el codi funciona igual. Llançar una goroutine és tan natural com cridar una funció amb go davant. I els channels et donen un mecanisme elegant per comunicar goroutines sense compartir memòria directament.
Si el teu servei necessita gestionar moltes connexions simultànies, processar tasques en paral·lel o coordinar workers, Go et dóna eines que a Python requereixen molt més esforç i cura.
Per a un exemple pràctic de concurrència a Go, tinc un article on entro més en detall amb goroutines, channels i patrons reals.
Rendiment: on importa i on no
Dir que “Go és més ràpid que Python” és cert però incomplet. La pregunta correcta és: on el teu codi necessita anar ràpid, quant més ràpid va Go?
Números reals
En benchmarks típics de processament CPU, Go és entre 10x i 40x més ràpid que Python pur. No és una diferència marginal. És la diferència entre que un procés trigui 2 segons o 60.
Exemple simple: comptar els nombres primers fins a un milió.
def count_primes(limit: int) -> int:
count = 0
for n in range(2, limit):
is_prime = True
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
is_prime = False
break
if is_prime:
count += 1
return count
# A la meva màquina: ~4.2 segons per a limit=1_000_000func countPrimes(limit int) int {
count := 0
for n := 2; n < limit; n++ {
isPrime := true
for i := 2; i*i <= n; i++ {
if n%i == 0 {
isPrime = false
break
}
}
if isPrime {
count++
}
}
return count
}
// A la meva màquina: ~0.15 segons per a limit=1_000_000Això és ~28x més ràpid. I Go ni tan sols està usant goroutines aquí. Amb paral·lelisme, la diferència seria major.
Però no tot és CPU
La majoria d’aplicacions backend passen més temps esperant I/O (base de dades, APIs externes, disc) que processant CPU. En aquests casos la diferència de rendiment és molt menor perquè el coll d’ampolla no és el llenguatge, sinó la xarxa o el disc.
| Escenari | Diferència Go vs Python | Importa? |
|---|---|---|
| Càlculs CPU intensius | 10x-40x | Molt |
| Processament de dades en memòria | 5x-20x | Bastant |
| API REST típica (CRUD + DB) | 2x-5x | Depèn del volum |
| Script que crida APIs externes | Marginal | Poc |
| Automatització I/O bound | Marginal | No |
Si el teu servei gestiona 50 peticions per minut, Python va sobrat. Si gestiona 5.000 per segon amb requisits de latència baixa, Go et dóna marge que a Python hauries de compensar amb més instàncies, més infraestructura i més complexitat operativa.
Desplegament: el binari únic canvia les regles
Això és alguna cosa que no s’aprecia fins que ho vius en producció. Desplegar Python i desplegar Go són experiències radicalment diferents.
Desplegar Python
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]La imatge resultant: entre 200MB i 500MB depenent de les dependències. Necessites gestionar requirements.txt o pyproject.toml, entorns virtuals, versions de Python. Si uses biblioteques amb extensions C (numpy, pandas, lxml), la imatge es complica amb dependències de sistema operatiu. Gestionar versions de Python a l’equip és un altre maldecap: pyenv, venv, poetry, uv, cada projecte amb el seu propi ritual.
Desplegar Go
FROM golang:1.23 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]La imatge resultant: entre 5MB i 20MB. Un binari estàtic sense dependències de runtime. Pots usar scratch o distroless com a imatge base perquè no necessites ni sistema operatiu. No hi ha versió de Go que gestionar en producció, no hi ha dependències de sistema, no hi ha entorn virtual.
Un binari Go compilat és un fitxer que copies i executes. Sense runtime, sense intèrpret, sense virtualenv. La simplicitat operativa és brutal.
| Aspecte | Python | Go |
|---|---|---|
| Mida d’imatge Docker | 200-500 MB | 5-20 MB |
| Dependències de runtime | Python + pip + libs | Cap |
| Temps d’arrencada | 1-3 segons | Mil·lisegons |
| Gestió de versions | pyenv/venv/poetry/uv | go.mod (inclòs al llenguatge) |
| Cross-compilation | Complex | GOOS=linux GOARCH=amd64 go build |
Per a APIs i microserveis en producció, la diferència operativa és significativa. Menys superfície d’atac, menys coses que poden fallar, desplegaments més ràpids.
Dades, IA i Machine Learning: Python guanya per golejada
Aquí no hi ha debat. Si treballes amb dades, machine learning o intel·ligència artificial, Python és l’estàndard de la indústria i Go no competeix.
L’ecosistema de Python per a dades és immens:
- Anàlisi de dades: pandas, polars, NumPy
- Machine learning: scikit-learn, XGBoost, LightGBM
- Deep learning: PyTorch, TensorFlow, JAX
- NLP: spaCy, Hugging Face Transformers
- Visualització: matplotlib, seaborn, plotly
- Notebooks: Jupyter, que és insustituïble per a exploració
Go té algunes biblioteques per a machine learning, però són marginals comparades amb l’ecosistema Python. No té res comparable a pandas per a manipulació de dades. No té frameworks de deep learning seriosos. I els notebooks no existeixen a Go.
Si la teva feina implica entrenar models, analitzar datasets, construir pipelines de dades o qualsevol cosa relacionada amb IA, Python és l’opció correcta sense discussió. Go pot complementar com a servei que serveix el model entrenat (inferència en producció), però el desenvolupament del model sempre serà en Python.
Scripting i automatització: Python és més pràctic
Per a scripts puntuals, automatitzacions i eines ràpides, Python segueix sent la meva primera opció. La raó és simple: la fricció d’escriure un script en Python és mínima.
# Reanomenar fitxers en un directori amb un patró
from pathlib import Path
source = Path("./exports")
for f in source.glob("*.csv"):
new_name = f.stem.replace(" ", "_").lower() + f.suffix
f.rename(f.parent / new_name)
print(f"Renamed: {f.name} -> {new_name}")L’equivalent en Go:
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func main() {
source := "./exports"
entries, err := os.ReadDir(source)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
for _, entry := range entries {
if entry.IsDir() || filepath.Ext(entry.Name()) != ".csv" {
continue
}
oldPath := filepath.Join(source, entry.Name())
name := strings.TrimSuffix(entry.Name(), ".csv")
newName := strings.ToLower(strings.ReplaceAll(name, " ", "_")) + ".csv"
newPath := filepath.Join(source, newName)
if err := os.Rename(oldPath, newPath); err != nil {
fmt.Fprintf(os.Stderr, "Error renaming %s: %v\n", entry.Name(), err)
continue
}
fmt.Printf("Renamed: %s -> %s\n", entry.Name(), newName)
}
}Funcional, correcte, robust. Però per a un script puntual, la versió Python és més ràpida d’escriure i més fàcil de modificar. No necessites compilar, no necessites declarar tipus, no necessites gestionar errors explícitament si no t’importa que falli sorollosament.
Go té sentit per a eines CLI que distribuiràs o mantindràs. Per al script que executes una vegada i llences, Python és més eficient en el teu temps.
Gestió d’errors: filosofies que condicionen el codi
Un punt que mereix menció a part és com cada llenguatge gestiona els errors, perquè afecta directament a l’experiència de desenvolupament.
Python usa excepcions. Pots ignorar errors fins que exploten en producció:
def get_user_email(user_id: int) -> str:
user = db.get_user(user_id) # pot llançar ConnectionError
return user["email"] # pot llançar KeyErrorFunciona. Però si db.get_user falla o l’usuari no té email, tens una excepció no gestionada. Pots posar try/except, però el llenguatge no t’obliga.
Go t’obliga a gestionar cada error explícitament:
func getUserEmail(userID int) (string, error) {
user, err := db.GetUser(userID)
if err != nil {
return "", fmt.Errorf("fetching user %d: %w", userID, err)
}
if user.Email == "" {
return "", fmt.Errorf("user %d has no email", userID)
}
return user.Email, nil
}Sí, és més verbós. I sí, el patró if err != nil es repeteix constantment. Però cada camí d’error està documentat al codi. No hi ha sorpreses en producció per una excepció que ningú va capturar. Quan mantens un servei que processa milions de peticions, aquesta predictibilitat val el seu pes en or.
A Python confies que algú va posar el try/except on calia. A Go, el compilador no et deixa ignorar un error. Són dos contractes diferents amb el desenvolupador.
Quan triar cada un: matriu de decisió
Després de treballar amb tots dos, el meu criteri es redueix a això:
Tria Python quan:
- Prototipat ràpid: necessites validar una idea en hores, no en dies
- Data science i ML: no hi ha alternativa real
- Automatitzacions i scripts: la fricció és mínima
- APIs internes: poques peticions, equip que ja sap Python
- Integració amb ecosistema de dades: pandas, notebooks, pipelines
- L’equip és de Python: la productivitat de l’equip pesa més que el rendiment teòric
Tria Go quan:
- Serveis d’alta concurrència: moltes connexions simultànies, websockets, streaming
- Microserveis en producció: on la latència i el consum de recursos importen
- CLIs i eines distribuïbles: un binari que funciona a qualsevol lloc
- Infraestructura i cloud native: Kubernetes, Docker, Terraform estan escrits en Go per alguna raó
- El desplegament importa: imatges petites, arrencada ràpida, sense dependències de runtime
- Processament CPU intensiu: compilat sempre guanya a interpretat
I la zona grisa
Hi ha casos on tots dos serveixen. Una API REST estàndard amb base de dades, un servei de processament de cues, un worker que consumeix de Kafka. En aquests casos, el meu consell: tria el que domini millor el teu equip. Un servei ben escrit en Python funciona millor que un de mal escrit en Go.
| Cas d’ús | Recomanació | Motiu |
|---|---|---|
| API interna / prototip | Python (FastAPI) | Velocitat de desenvolupament |
| API producció alt tràfic | Go | Rendiment + desplegament |
| Scripts i automatització | Python | Menys fricció |
| Data pipeline / ETL | Python | Ecosistema |
| Machine learning | Python | Sense alternativa |
| CLI distribuïble | Go | Binari únic |
| Microservei cloud native | Go | Imatge lleugera, arrencada ràpida |
| Worker/processador de cues | Tots dos | Depèn de l’equip |
| WebSocket / streaming | Go | Goroutines |
Conclusió: es complementen, no es reemplacen
Després de mesos explorant Go venint de Python, la meva conclusió és que no són llenguatges que competeixen. Ocupen nínxols diferents i ho fan bé.
Python segueix sent la meva eina principal per a automatitzacions, scripts, dades i qualsevol cosa que necessiti iterar ràpid. No deixaré d’usar-lo. El seu ecosistema per a data science i machine learning és insustituïble. La seva velocitat de desenvolupament per a prototips i eines internes segueix sense rival.
Go ha passat a ser la meva opció preferida per a serveis backend que van a producció amb requisits de rendiment, serveis que necessiten gestionar concurrència de forma predictible i eines que vull distribuir com un binari sense dependències. La simplicitat del desplegament i la predictibilitat del codi compilat amb tipus estàtics em donen confiança en producció.
La clau no és triar un i descartar l’altre. És saber quin problema tens davant i triar l’eina que millor el resol. Si el teu equip és fort en Python i el servei no té requisits extrems de rendiment, Python va sobrat. Si necessites un servei concurrent, lleuger i fàcil de desplegar, Go mereix que li dediquis temps.
Si estàs considerant fer el salt, comença per aprendre Go amb un projecte petit. Una CLI, un worker, una API senzilla. No intentis reescriure el teu monòlit de Python en Go el primer dia. Ves provant, ves formant criteri. I queda’t amb el que funcioni per al teu context.


