Skip to content
This page has been auto-translated and may contain errors.View in English

Módulos e a biblioteca padrão

docs.scrimba.com

O Python vem com uma enorme coleção de ferramentas prontas para usar: aleatoriedade, matemática, datas, caminhos de arquivo e muito mais. Essas ferramentas vivem em módulos, e você as traz para seu código com import. Você já usou import json no capítulo Arquivos e exceções. Este capítulo cobre imports completamente e introduz as partes mais úteis da biblioteca padrão.

A biblioteca padrão do Python fornece soluções testadas e documentadas para problemas comuns. Módulos são a unidade de organização de código: cada arquivo é um módulo, cada diretório com __init__.py é um pacote. O sistema de import encontra módulos, compila-os se necessário e os armazena em cache em sys.modules para que sejam carregados apenas uma vez.

Um import faz trabalho real: Python localiza o módulo, o executa uma vez e depois o armazena em cache em sys.modules (o dicionário de cada módulo já carregado), então um segundo import do mesmo nome é uma busca rápida, não uma re-execução. import requests vincula o objeto do módulo ao nome requests no seu namespace atual (a tabela de nomes visíveis naquele ponto do código). from requests import get executa o mesmo carregamento mas vincula apenas get. As partes que compensam em projetos reais são onde o Python procura (sys.path), como diretórios se tornam pacotes importáveis (__init__.py), e como evitar imports circulares, tudo coberto abaixo.

Importando módulos

A importação mais simples traz um módulo inteiro e deixa você usar seu conteúdo com notação de ponto. Você também pode importar nomes específicos de um módulo para usá-los diretamente sem o prefixo. Aliases encurtam nomes longos.

import module vincula o objeto do módulo ao nome module no escopo atual. from module import name vincula apenas name. Aliases (import module as alias) são comuns com bibliotecas de terceiros. Evite from module import *: ela polui o namespace e torna pouco claro de onde os nomes vieram.

from module import name não ignora nenhum trabalho: Python ainda carrega e executa o módulo inteiro, depois vincula apenas o nome que você pediu. A armadilha a conhecer é o import circular, onde dois módulos tentam importar um ao outro enquanto ainda estão carregando, então um deles vê um módulo semi-construído e falta um nome. As correções usuais são mover o offending import para dentro da função que precisa dele (para que execute depois que ambos os módulos terminarem de carregar) ou extrair o código compartilhado para um terceiro módulo que ambos podem importar. Quando você precisa importar um módulo cujo nome você só conhece em tempo de execução, importlib.import_module("name") aceita o nome como string.

python
import math

math.sqrt(16)     # 4.0
math.pi           # 3.141592653589793
math.floor(3.9)   # 3
math.ceil(3.1)    # 4

Importe nomes específicos de um módulo para usá-los diretamente:

python
from math import sqrt, pi

sqrt(16)    # 4.0 (sem o prefixo "math.")
pi          # 3.141592653589793

Dê a um módulo ou nome um alias para encurtá-lo:

python
import math as m

m.sqrt(16)    # 4.0

from math import sqrt as square_root
square_root(25)    # 5.0

Aliases são comuns com bibliotecas de terceiros populares (import numpy as np, import pandas as pd). Para módulos da biblioteca padrão, prefira usar o nome completo; torna o código mais legível.

JunoImportando módulosimport math traz o módulo inteiro, depois você acessa com um ponto: math.sqrt(16). from math import sqrt pega um nome para que você possa dispensar o prefixo. A palavra-chave as renomeia um import longo, mas para coisas da biblioteca padrão o nome completo fica mais claro.
JunoImportando módulosimport module vincula o objeto do módulo, from module import name vincula um nome, import module as alias renomeia. Pule from module import *: ela despeja todos os nomes no seu escopo e esconde de onde cada um veio.
JunoImportando módulosfrom module import name ainda carrega o módulo inteiro, apenas mantém um nome. O problema que você vai encontrar eventualmente é o import circular, onde dois módulos importam um ao outro durante o carregamento: mova esse `import` para dentro da função, ou divida o código compartilhado em um terceiro módulo. importlib.import_module() está lá para quando o nome é conhecido apenas em tempo de execução.

random

O módulo random gera números aleatórios e faz escolhas aleatórias. Use para jogos, simulações, amostragem aleatória e qualquer outra coisa que precisa de imprevisibilidade. Definir uma seed torna os resultados reproduzíveis: a mesma seed produz a mesma sequência toda vez.

random usa um gerador de números pseudo-aleatórios Mersenne Twister. A seed determina a sequência completa; a mesma seed sempre produz o mesmo resultado. .choice() escolhe um item, .choices() escolhe com reposição, .sample() escolhe sem. .shuffle() modifica a lista no local e retorna None.

random produz números pseudo-aleatórios: eles parecem aleatórios mas vêm de uma fórmula fixa dirigida por um valor inicial (a seed), então a mesma seed dá a mesma sequência em cada máquina, o que é o que torna um teste reproduzível. A regra que importa em produção: nunca use random para nada que uma pessoa possa explorar, como senhas, tokens de sessão ou códigos de reset de senha. É previsível por design, então um atacante que vê saída suficiente pode adivinhar o resto. Para esses, recorra ao módulo secrets, que usa a aleatoriedade segura do sistema operacional. random.SystemRandom() oferece a mesma fonte segura por trás dos nomes de método random familiares se você preferir não trocar APIs.

python
import random

random.random()              # float entre 0 e 1 (exclusivo)
random.randint(1, 10)        # inteiro de 1 a 10 (ambos inclusivos)
random.uniform(1.0, 10.0)    # float entre 1.0 e 10.0

cores = ["vermelho", "verde", "azul"]
random.choice(cores)       # escolhe um item
random.choices(cores, k=3) # escolhe k itens (com reposição)
random.sample(cores, k=2)  # escolhe k itens (sem reposição)

numeros = [1, 2, 3, 4, 5]
random.shuffle(numeros)      # embaralha no local, retorna None

Para resultados reproduzíveis (útil em testes e ciência de dados), defina uma seed antes de gerar:

python
random.seed(42)
random.randint(1, 100)   # sempre o mesmo valor para seed 42

A mesma seed produz a mesma sequência toda vez, em qualquer máquina.

Junorandomrandom.choice() escolhe um item de uma lista, random.randint(1, 10) dá um número inteiro em um intervalo, fim incluído. Quer os mesmos resultados a cada execução, digamos para um teste? Chame random.seed() primeiro e a sequência fica travada.
Junorandom.choice() escolhe um, .choices() escolhe com reposição, .sample() sem. .shuffle() reordena a lista no local e retorna None, então nunca escreva nums = random.shuffle(nums). A seed fixa a sequência inteira, o que é como você torna um teste aleatório repetível.
Junorandomrandom é pseudo-aleatório e previsível por design, então é ok para jogos e testes mas errado para qualquer coisa que um atacante pudesse explorar. Use secrets para tokens, senhas e códigos de reset. Mantenha random.seed() para os casos reproduzíveis onde você realmente quer que a sequência se repita.

math

O módulo math adiciona operações matemáticas mais avançadas além dos operadores aritméticos básicos que você conheceu no capítulo Números. Raízes quadradas, potências, logaritmos, trigonometria e valores especiais como pi e infinito estão tudo aqui.

math fornece implementações em nível C de funções matemáticas padrão. Note que math.pow() sempre retorna um float, enquanto o operador ** do Python retorna int para bases e expoentes inteiros. math.log(x, base) computa logaritmo em qualquer base; math.log(x) computa o logaritmo natural.

O detalhe que morde em código real é nan (abreviação de "not a number", o float que você obtém de resultados indefinidos como 0/0 ou um parse falhado) e inf (infinidade). Um nan nunca é igual a nada, nem mesmo a si mesmo, então x == float("nan") é sempre False. Isso significa que você não pode testá-lo com ==; use math.isnan(x) e math.isinf(x) para infinidade. O outro hábito que vale a pena manter: prefira math.isclose(a, b) a a == b ao comparar dois floats, já que arredondamento em ponto flutuante torna igualdade exata pouco confiável. Para matemática sobre arrays inteiros de números em vez de um por vez, numpy é a ferramenta padrão, embora esteja fora deste guia.

python
import math

math.sqrt(25)        # 5.0
math.pow(2, 10)      # 1024.0 (igual a 2 ** 10 mas sempre retorna float)
math.log(100, 10)    # 2.0 (log base 10)
math.log(math.e)     # 1.0 (log natural)

math.sin(math.pi / 2)   # 1.0
math.cos(0)             # 1.0

math.ceil(3.2)    # 4
math.floor(3.9)   # 3
math.trunc(3.9)   # 3 (igual a int() para positivos)

math.inf          # infinidade
math.isnan(float("nan"))   # True
math.isinf(math.inf)       # True
Junomathmath cobre tudo além de + - * /: raízes quadradas, potências, logs, trig, além de constantes como math.pi. Recorra a ela no momento em que um cálculo precisa de mais que os operadores básicos e você pula escrever a fórmula você mesmo.
Junomathmath tem raízes quadradas, logs e trig. Uma coisa para observar: math.pow(2, 10) sempre retorna um float, enquanto 2 ** 10 permanece um int para inputs inteiros. math.log(x, base) aceita qualquer base, math.log(x) é o log natural.
Junomath As armadilhas são nan e inf: nan nunca é igual a si mesmo, então teste com math.isnan(), não com ==. E compare floats com math.isclose() em vez de ==, já que arredondamento torna igualdade exata pouco confiável. Para arrays inteiros de números, esse é o território de numpy.

datetime

O módulo datetime lida com datas e horas. datetime.now() dá a você a data e hora atuais. strftime() formata como string. strptime() analisa uma string em um datetime. timedelta representa uma duração que você pode adicionar ou subtrair.

datetime, date e timedelta são as classes principais. strftime() formata um datetime como string usando códigos de formato. strptime() analisa uma string dado um padrão de formato. timedelta suporta aritmética: você pode adicionar ou subtrair durações de datas e comparar datetimes com <, >, -.

A armadilha em produção é que um simples datetime.now() é naive: não tem fuso horário anexado, então não sabe realmente qual parte do mundo representa. Dois datetimes naive de máquinas diferentes parecem comparáveis mas silenciosamente não são, e essa ambiguidade causa bugs de horas fora. A correção é trabalhar em datetimes aware (aqueles que carregam um fuso horário) e padronizar em UTC para qualquer coisa armazenada ou enviada entre sistemas: datetime.now(timezone.utc). Converta para hora local apenas na borda, quando você mostra para uma pessoa. Use o módulo zoneinfo para zonas nomeadas como "Europe/Oslo", que lida com mudanças de horário de verão para você. Uma mais regra: nunca meça tempo decorrido com diferenças de datetime.now(), porque o relógio do sistema pode pular (uma sincronização ou mudança manual); use time.perf_counter(), um contador que apenas se move adiante, para durações.

python
from datetime import datetime, date, timedelta

now = datetime.now()             # data e hora atuais
today = date.today()             # apenas data atual

print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)

# Formatação
print(now.strftime("%Y-%m-%d"))           # "2024-01-15"
print(now.strftime("%d %B %Y, %H:%M"))   # "15 de janeiro de 2024, 09:42"

# Análise
deadline = datetime.strptime("2024-12-31", "%Y-%m-%d")

# Aritmética
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
diff = deadline - now
print(f"{diff.days} dias até o prazo")

Códigos comuns de strftime:

CódigoSignificadoExemplo
%YAno com 4 dígitos2024
%mMês (com zero)01
%dDia (com zero)15
%HHora (24h)09
%MMinuto42
%BNome do mês completojaneiro
Junodatetimedatetime.now() dá a você a data e hora atuais. strftime() a transforma em string com códigos como %Y-%m-%d, e strptime() vai de outro jeito, analisando uma string de volta. timedelta é uma duração que você pode adicionar ou subtrair, então today + timedelta(days=1) é amanhã.
Junodatetimedatetime, date e timedelta são o núcleo. strftime() formata com códigos, strptime() analisa com padrão correspondente. Datas fazem aritmética: subtraia duas e você obtém um timedelta, adicione um timedelta para deslocar para frente ou trás.
Junodatetime Um simples datetime.now() é naive, sem fuso horário, o que silenciosamente quebra uma vez que duas máquinas estão envolvidas. Armazene e passe UTC com datetime.now(timezone.utc), converta para local apenas para exibição, e dependa de zoneinfo para zonas nomeadas. E meça tempo decorrido com time.perf_counter(), nunca diferenças de now(), já que o relógio de parede pode pular.

os e pathlib

pathlib é a forma moderna de trabalhar com caminhos de arquivo. Objetos Path deixam você construir, inspecionar e navegar caminhos usando o operador /. os dá acesso a variáveis de ambiente e operações de SO de nível inferior. Prefira pathlib para código novo.

pathlib.Path representa caminhos do sistema de arquivos como objetos com métodos para consultar e navegar. O operador / une componentes de caminho facilmente, tratando separadores específicos do SO automaticamente. os.environ é um objeto dict-like para variáveis de ambiente; os.environ.get("KEY", "default") é seguro para variáveis faltantes.

Path se adapta ao sistema operacional automaticamente, então o mesmo código une caminhos com o separador certo em cada máquina: escreva Path("data") / "file.csv", nunca strings construídas manualmente "data/" + name que quebram no Windows. Duas notas práticas. Primeiro, .glob(), .rglob() e .iterdir() retornam geradores (eles cedem entradas uma por vez em vez de construir uma lista completa), então coloque-os em list() se precisar fazer loop duas vezes ou conhecer a contagem antecipadamente; para um diretório gigante, iterar preguiçosamente é o ponto. Segundo, algumas bibliotecas antigas ainda esperam uma string simples em vez de um objeto Path; quando uma rejeita um Path, passe str(path) e prossiga. Para ler uma variável de ambiente, sempre use os.environ.get("KEY", "default") em vez de os.environ["KEY"], porque a forma indexada levanta KeyError quando a variável não está definida, que é o caso comum em uma implantação fresca.

python
from pathlib import Path

p = Path("data/reports")

p.exists()           # True se o caminho existe
p.is_dir()           # True se é um diretório
p.is_file()          # True se é um arquivo

p.mkdir(parents=True, exist_ok=True)   # criar diretórios

for f in p.glob("*.csv"):              # todos os arquivos CSV no diretório
    print(f.name)                      # o nome do arquivo

report = p / "report_jan.csv"          # operador / une caminhos
report.stem       # "report_jan" (nome sem extensão)
report.suffix     # ".csv"
report.parent     # Path("data/reports")

content = report.read_text()           # ler conteúdo do arquivo diretamente
report.write_text("novo conteúdo\n")    # escrever diretamente

Para o módulo os:

python
import os

os.getcwd()                        # diretório de trabalho atual
os.listdir(".")                    # listar conteúdo do diretório
os.path.exists("data.txt")        # True se caminho existe
os.path.join("data", "file.txt")  # "data/file.txt" (multiplataforma)
os.environ.get("HOME")            # ler uma variável de ambiente

Prefira pathlib para código novo. Use os quando precisar de variáveis de ambiente ou trabalhar com APIs antigas que esperam strings.

Junoos e pathlibpathlib.Path trata um caminho de arquivo como um objeto que você pode investigar: .exists(), .read_text(), .write_text(), .glob(). O operador / une partes, então p / "report.csv" lê como um caminho real. Recorra a pathlib sobre o mais antigo os.path em código novo.
Junoos e pathlibPath une com / e trata o separador do SO para você, então caminhos permanecem portáveis. os.environ é dict-like para variáveis de ambiente, e os.environ.get("KEY", "default") é a leitura segura, não vai explodir em uma chave faltante.
Junoos e pathlib Construa caminhos com Path e /, nunca strings coladas que quebram no Windows. .glob() e similares devolvem geradores, então list() eles se precisar fazer loop duas vezes. E leia variáveis de env com os.environ.get(key, default): a forma de colchetes levanta KeyError no momento em que uma variável não está definida, que é exatamente o caso de implantação fresca.

timeit

timeit mede quanto tempo código leva para executar. É útil quando você quer comparar duas abordagens e escolher a mais rápida. Execute o código muitas vezes para obter uma medição estável.

timeit.timeit(stmt, setup, number) cronometra stmt executando-o number vezes e retornando o tempo decorrido total em segundos. A string setup executa uma vez antes do loop cronometrado. Divida o resultado por number para obter o tempo por chamada. Mais repetições reduzem ruído de agendamento do sistema.

timeit é a ferramenta certa para um microbenchmark: cronometrar um pequeno snippet em isolamento, executado muitas vezes para que o ruído de agendamento se distribua. Ela silencia esse ruído para você, mas uma única execução ainda é pouco confiável, então pegue o melhor de várias tentativas em vez de um número. O erro a evitar é usá-la para encontrar uma lentidão em um programa inteiro: ela apenas cronometra o snippet que você entrega, então não pode dizer qual função é realmente lenta. Para isso, use cProfile, que executa seu programa real e relata onde o tempo foi. Regra de ouro: timeit para comparar duas implementações, cProfile para encontrar o gargalo em primeiro lugar.

python
import timeit

# Cronometrar uma declaração única
timeit.timeit("sum(range(1000))", number=10000)

# Cronometrar um bloco mais complexo
setup = "data = list(range(1000))"
code = "[x * 2 for x in data]"
time = timeit.timeit(code, setup=setup, number=10000)
print(f"{time:.4f} segundos para 10.000 execuções")

number é quantas vezes repetir. Mais repetições dão uma medição mais estável.

Junotimeittimeit.timeit() cronometra um pedaço de código executando-o repetidamente, depois você divide pela contagem de execução para um número por chamada estável. É como você resolve "qual dessas duas é mais rápida" em vez de adivinhar. Uma execução é muito ruidosa para confiar, então deixe fazer loop.
Junotimeittimeit.timeit(stmt, setup, number) executa stmt esse número de vezes e retorna os segundos totais; a string setup executa uma vez antes do loop. Divida por number para tempo por chamada, e aumente as repetições para afogar ruído de agendamento.
Junotimeittimeit é para microbenchmarks: comparar dois pequenos snippets, melhor de vários em vez de uma única execução ruidosa. Não pode dizer qual função em um programa real é lenta, já que apenas cronometra o que você entrega. Use cProfile para encontrar o gargalo, depois timeit para comparar correções.

string

O módulo string fornece constantes de string pré-construídas para letras, dígitos e pontuação. Útil quando você precisa verificar caracteres ou gerar strings aleatórias de um alfabeto específico.

Constantes do módulo string (ascii_letters, digits, punctuation) são strings simples que você pode indexar, iterar ou usar com in. Combiná-las com random.choices() é a forma padrão de gerar tokens ou senhas aleatórias.

Essas constantes são strings ordinárias, então verificar char in string.ascii_letters escaneia a string inteira cada vez. Esse escanear é O(n): o trabalho cresce com o comprimento da string, ok uma vez, desperdiçador em um loop apertado. Se você estiver testando associação repetidamente, construa um set uma vez (letters = set(string.ascii_letters)) e teste contra aquilo em vez disso, já que uma busca de set permanece rápida não importa quantas entradas ele contém. A outra peça útil aqui é string.Template, que faz substituição simples de $name: quando um template vem de entrada do usuário ou um arquivo de configuração, é mais seguro que entregar texto não confiável a uma f-string ou str.format(), porque não pode alcançar suas variáveis ou chamar métodos.

python
import string

string.ascii_lowercase   # "abcdefghijklmnopqrstuvwxyz"
string.ascii_uppercase   # "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
string.ascii_letters     # ambos combinados
string.digits            # "0123456789"
string.punctuation       # todos os caracteres de pontuação

Útil quando você precisa verificar caracteres ou gerar strings aleatórias:

python
import string, random

chars = string.ascii_letters + string.digits
password = "".join(random.choices(chars, k=12))
Junostring O módulo string lhe dá conjuntos de caracteres prontos: string.ascii_letters, string.digits, string.punctuation. São strings simples, então você pode fazer loop sobre elas ou amostrar delas. Útil sempre que você precisa de um alfabeto conhecido para construir ou verificar contra.
Junostringstring.ascii_letters, string.digits e amigos são strings simples que você pode indexar, iterar, ou testar com in. Combine-os com random.choices() e você tem a receita padrão para um token aleatório ou senha. Note que random não é seguro para segredos reais.
Junostring As constantes são strings simples, então char in string.ascii_letters escaneia a coisa inteira a cada chamada: construa um set uma vez se você estiver verificando em um loop. E quando um template vem de entrada do usuário, string.Template com $name é mais seguro que uma f-string, já que não pode alcançar suas variáveis.

Criando seus próprios módulos

Qualquer arquivo Python é um módulo. Para usá-lo de outro arquivo, importe pelo nome do arquivo (sem .py). Você pode importar o módulo inteiro e usar seu conteúdo com notação de ponto, ou importar nomes específicos diretamente.

Quando Python importa um módulo, ele executa o arquivo de cima para baixo uma vez e armazena em cache o resultado em sys.modules. Importações posteriores do mesmo módulo retornam o objeto armazenado em cache sem re-executar o arquivo. Para projetos maiores, módulos são organizados em pacotes: diretórios com um arquivo __init__.py.

Quando você escreve import utils, Python procura os diretórios em sys.path em ordem, começando com a pasta do script que você executou. Esta é a fonte do erro de import mais comum "funciona na minha máquina": execute um arquivo de um diretório de trabalho diferente e a pasta que ele espera pode não estar no caminho, então o import falha. A correção confiável é executar o projeto como um pacote (python -m mypackage.main) em vez de apontar Python para um arquivo solto. Um pacote é um diretório de módulos com um arquivo __init__.py (pode estar vazio; sua presença é o que diz a Python que a pasta é importável). Dentro de um pacote, um import relativo como from . import helpers significa "do mesmo pacote", que mantém imports funcionando mesmo se o pacote for renomeado ou movido. Uma borda afiada: importlib.reload() re-executa um módulo, mas quaisquer nomes que você já importou ainda apontam para a versão antiga, então recarregar é pouco confiável para qualquer coisa além de um experimento rápido no REPL.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

PI = 3.14159
python
# main.py
import utils

utils.clamp(150, 0, 100)   # 100
utils.PI                    # 3.14159

from utils import clamp
clamp(50, 0, 100)           # 50

Python encontra o módulo procurando no mesmo diretório que o arquivo que faz a importação (e alguns outros lugares). Para projetos maiores, módulos são organizados em pacotes: diretórios com um arquivo __init__.py.

JunoCriando seus próprios módulos Qualquer arquivo .py é já um módulo: importe pelo nome do arquivo sem o .py, e suas funções e variáveis são suas para usar. import utils então utils.clamp(...), ou puxe um nome com from utils import clamp. É como você divide um programa crescente entre arquivos.
JunoCriando seus próprios módulos Importar um módulo o executa de cima para baixo uma vez, depois o armazena em cache em sys.modules, então importações posteriores retornam o mesmo objeto sem re-executar o arquivo. Mantenha código de nível superior leve por isso, qualquer coisa pesada executa no tempo de import. Agrupe módulos relacionados em um pacote: uma pasta com um __init__.py.
JunoCriando seus próprios módulos Imports resolvem através de sys.path, começando com a pasta do script, o que é por que executar um arquivo solto do diretório errado quebra o import. Execute como um pacote em vez disso: python -m pkg.main. E não confie em importlib.reload() além de pokes rápidos do REPL, já que nomes que você já importou continuam apontando para a versão antiga.

__name__ == "__main__"

Quando Python executa um arquivo diretamente, __name__ é definido como "__main__". Quando o mesmo arquivo é importado como um módulo, __name__ é o nome do módulo. Este padrão deixa você escrever código que executa quando você executa o arquivo diretamente mas é ignorado quando o arquivo é importado por outro módulo.

if __name__ == "__main__": é a proteção padrão para código de módulo executável. Deixa um módulo ser importável (expondo suas funções) e diretamente executável (com código de teste ou demo). Sem isso, importar o módulo executaria qualquer código de nível superior, o que quase nunca é desejado.

Python define __name__ para "__main__" para o arquivo que você realmente executou e para o nome próprio do módulo para qualquer coisa importada. A proteção existe para manter efeitos colaterais de importação (código que faz algo quando o módulo carrega, como análise de argumentos, abrindo uma conexão ou executando uma demo) de disparar no momento em que outro arquivo o importa. O padrão que se sustenta: coloque o trabalho real em uma função main() e a chame sob a proteção, então importadores obtêm as funções sem a inicialização, e o arquivo ainda executa limpo por si. Ignorar a proteção é a razão clássica de uma suite de teste misteriosamente executar código demo de alguém no instante em que importa o módulo.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

if __name__ == "__main__":
    # isto apenas executa quando você faz: python utils.py
    # não quando você faz: import utils
    print(clamp(150, 0, 100))   # 100

Este é um padrão padrão para qualquer módulo que também é útil como um script autônomo.

Juno__name__ == '__main__'if __name__ == "__main__": deixa um arquivo ser tanto um módulo importável quanto um script executável. Código sob a proteção executa quando você faz python utils.py, e é ignorado quando outro arquivo faz import utils. Útil para encolher uma demo rápida ou teste na parte inferior de um módulo.
Juno__name__ == '__main__'__name__ é "__main__" apenas no arquivo que você executou diretamente. A proteção mantém seu código de demo ou teste de executar quando o módulo é importado, o que caso contrário acontece silenciosamente. É a forma padrão de tornar um arquivo funcione tanto como uma biblioteca quanto um script.
Juno__name__ == '__main__' A proteção detém efeitos colaterais de importação, parse de arg, demos, conexões, de disparar quando alguém o importa. Coloque o trabalho em main() e a chame sob a proteção: importadores obtêm as funções, o arquivo ainda executa sozinho. Ignore-a e observe uma suite de teste executar sua demo no instante em que importa o módulo.

Destaques da biblioteca padrão

Alguns módulos a mais que valem a pena conhecer. Cada um resolve um problema comum que levaria trabalho significativo para implementar você mesmo.

A biblioteca padrão é extensa; os destaques abaixo são os que você encontrará mais frequentemente em código de produção. Para uma referência completa, docs.python.org/3/library é a fonte autoritária.

A biblioteca padrão é um conjunto curado de módulos bem testados e documentados. Antes de recorrer a um pacote de terceiros, verifique se a biblioteca padrão tem uma solução: functools, itertools, contextlib, dataclasses, typing e abc cada uma fornecem ferramentas que pacotes de terceiros frequentemente reinventam.

collections: tipos de contêiner especializados:

python
from collections import Counter, defaultdict, deque

Counter(["a", "b", "a", "c", "a"])   # Counter({'a': 3, 'b': 1, 'c': 1})
defaultdict(list)                      # dict que cria automaticamente chaves faltantes
deque([1, 2, 3], maxlen=5)            # append/pop rápido em ambas as extremidades

itertools: ferramentas para trabalhar com iteráveis:

python
import itertools

list(itertools.chain([1, 2], [3, 4]))          # [1, 2, 3, 4]
list(itertools.islice(range(100), 5))          # [0, 1, 2, 3, 4]
list(itertools.combinations([1, 2, 3], 2))     # [(1, 2), (1, 3), (2, 3)]
list(itertools.product([0, 1], repeat=2))      # [(0,0), (0,1), (1,0), (1,1)]

sys: acesso ao interpretador Python:

python
import sys

sys.argv        # lista de argumentos de linha de comando
sys.exit(1)     # sair com um código de status
sys.version     # string de versão do Python

Pacotes de terceiros: além da biblioteca padrão, pip instala pacotes da comunidade:

bash
pip install requests    # biblioteca HTTP
pip install pandas      # manipulação de dados
pip install numpy       # computação numérica

Pacotes de terceiros estão fora do escopo deste guia, mas o padrão é sempre o mesmo: pip install, depois import.

Na prática

Combinando random, string e datetime para gerar IDs de jogo únicos com timestamps:

python
import random
import string
from datetime import datetime

def generate_game_id(length: int = 8) -> str:
    chars = string.ascii_uppercase + string.digits
    return "".join(random.choices(chars, k=length))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

game_id = generate_game_id()
print(f"[{timestamp()}] Iniciando jogo {game_id}")

scores = [random.randint(50, 100) for _ in range(5)]
print(f"Pontuações da rodada: {scores}")
print(f"Melhor: {max(scores)}")

Usando pathlib e datetime para encontrar arquivos em um diretório e relatar seus tamanhos:

python
from pathlib import Path
from datetime import datetime

def find_files(directory: str, pattern: str = "*.csv") -> list[Path]:
    return sorted(Path(directory).glob(pattern))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

files = find_files(".", "*.md")[:3]
print(f"[{timestamp()}] Encontrados {len(files)} arquivo(s)")
for f in files:
    size = f.stat().st_size if f.exists() else 0
    print(f"  {f.name} ({size} bytes)")

Lendo configuração de aplicativo de variáveis de ambiente com defaults tipados, e escrevendo entradas de log de acesso estruturadas como JSON delimitado por quebra de linha:

python
import os
import json
from datetime import datetime
from pathlib import Path

def load_env_config() -> dict:
    return {
        "debug": os.environ.get("DEBUG", "false").lower() == "true",
        "port": int(os.environ.get("PORT", "8080")),
        "log_level": os.environ.get("LOG_LEVEL", "INFO"),
    }

def write_access_log(method: str, path: str, status: int) -> None:
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)
    entry = {
        "ts": datetime.now().isoformat(),
        "method": method,
        "path": path,
        "status": status,
    }
    with open(log_dir / "access.jsonl", "a") as f:
        f.write(json.dumps(entry) + "\n")

config = load_env_config()
print(f"Iniciando na porta {config['port']}, debug={config['debug']}")
write_access_log("GET", "/users", 200)

JSON delimitado por quebra de linha (.jsonl) é um formato de log comum: cada linha é um objeto JSON válido, o que torna direto fazer stream, acrescentar e analisar linha por linha sem carregar o arquivo inteiro.