Python¶
Python é uma linguagem de propósito geral, interpretada (compilada para bytecode), orientada a objetos, de tipagem dinâmica e forte, famosa pela legibilidade. É a linguagem dominante em ciência de dados e machine learning (Machine Learning), muito usada em automação, scripts, backend (Django, FastAPI, Flask) e infraestrutura.
Sobre a versão
A apostila-fonte usa o Python 3.6. Esta página usa o Python 3 atual (3.10+): onde
algo mudou (f-strings, dataclass, dicionários ordenados), há uma nota "Hoje". Os trechos
marcados como complemento não estão na apostila. A teoria geral de orientação a objetos
(encapsulamento, herança, polimorfismo, SOLID) está em
Orientação a Objetos; aqui fica o que é específico
do Python.
História, interpretador e versões¶
Python foi criada em 1990 por Guido van Rossum no CWI (Holanda), como sucessora da linguagem ABC, com a ideia de ser simples, legível e expressiva (por isso a indentação é obrigatória: ela define os blocos). Desde 2001 é mantida pela Python Software Foundation (PSF) e todas as versões são de código aberto.
Interpretada ou compilada?¶
Os dois. O CPython compila o código-fonte para bytecode (arquivos .pyc na pasta
__pycache__) e a Python Virtual Machine (PVM) o executa. Não é preciso compilar
manualmente — acontece sozinho; o .pyc é reutilizado se o .py não mudou. É um processo parecido
com o do Java (Java), só que sem um passo separado de compilação.
| Implementação | O que é |
|---|---|
| CPython | A implementação de referência (a que se instala de python.org) |
| PyPy | Escrita em Python, com compilador JIT; costuma ser mais rápida |
| Jython / IronPython | Geram bytecode para a JVM / para o .NET |
Definição: PEP
PEP (Python Enhancement Proposal) é uma proposta de melhoria da linguagem, avaliada
pela comunidade. A PEP 0 lista todas; a PEP 8 é o guia de estilo e a PEP 20
("The Zen of Python", import this) resume a filosofia.
Python 2 x Python 3¶
Python 2 está descontinuado (fim de vida em 2020); use sempre Python 3. Diferenças que ainda aparecem em código legado:
| Tema | Python 2 | Python 3 |
|---|---|---|
print |
comando (print "oi") |
função (print("oi")) |
| Entrada | raw_input() |
input() |
| Divisão | 5 / 2 → 2 |
5 / 2 → 2.5 (use // para divisão inteira) |
| Classes | class A(object): |
class A: (herda de object implicitamente) |
| Strings | bytes por padrão | str é Unicode |
Ferramentas do dia a dia¶
python3 --version
python3 # modo interativo (REPL): testa trechos, prompt >>>
python3 programa.py # modo script: executa um arquivo .py
python3 -m venv .venv # complemento: cria um ambiente virtual isolado
pip install requests # complemento: instala pacotes
O modo interativo serve para testar ideias rapidamente; o modo script é o de
desenvolvimento. help(objeto) e dir(objeto) mostram a documentação e os membros de qualquer
coisa. Complemento: use ambiente virtual (venv) por projeto para isolar dependências.
Ao ler um erro, comece pelo traceback (rastro de pilha): a última linha diz o tipo e a
mensagem do erro (SyntaxError, NameError...) e as anteriores mostram a sequência de
chamadas, da mais antiga para a mais recente.
Sintaxe básica¶
Variáveis e tipos embutidos¶
Uma variável é um nome que referencia um valor (uma "etiqueta"), e o tipo pertence ao
valor, não à variável (tipagem dinâmica). O tipo é descoberto com type().
mensagem = 'oi, python' # str
numero = 5 # int
pi = 3.14 # float
z = 2 + 3j # complex (j é a unidade imaginária)
ativo = True # bool
nada = None # NoneType: ausência de valor
print(type(numero)) # <class 'int'>
- Nomes: letras, números e
_, sem começar por número e sem usar palavra reservada (class,if,for,None...; vejakeyword.kwlist). A convenção é snake_case (numero_de_cadastro). - Strings aceitam aspas simples ou duplas.
'2'e'3.2'são texto, não números. - Não confunda
=(atribuição) com==(comparação).
Operadores¶
| Operador | Significado | Exemplo |
|---|---|---|
+ - * |
soma, subtração, multiplicação (+ e * também funcionam em strings e listas) |
'ab' * 3 → 'ababab' |
/ |
divisão (sempre float) |
7 / 2 → 3.5 |
// |
divisão inteira | 7 // 2 → 3 |
% |
resto (útil para testar divisibilidade) | 7 % 3 → 1 |
** |
potência | 2 ** 10 → 1024 |
== != < > <= >= |
comparação | 2 > 1 → True |
and or not |
lógicos | a and not b |
in, not in |
pertencimento | 'a' in 'banana' → True |
is, is not |
identidade (mesmo objeto) | x is None |
== x is
== compara o conteúdo; is compara a identidade (se são o mesmo objeto na
memória). Duas listas iguais são ==, mas não is:
Use is para comparar com None (if valor is None).
Strings, input e formatação¶
texto = 'python'
texto.upper() # 'PYTHON'
texto.capitalize() # 'Python'
' banana\n'.strip() # 'banana' (remove espaços e quebras de linha nas pontas)
nome = input('Digite seu nome: ') # sempre devolve str!
idade = int(input('Idade: ')) # converta para número quando precisar
int('caelum') lança ValueError. Para montar mensagens:
print('Seu nome é {} e sua idade é {}'.format(nome, idade)) # .format (apostila)
print(f'Seu nome é {nome} e sua idade é {idade}') # Hoje: f-string (Python 3.6+)
print(f'{245.2346:.2f}') # 245.23 (duas casas decimais)
Valores "verdadeiros" (truthy) e None¶
bool(valor) converte qualquer valor: são falsos 0, 0.0, '' (string vazia), [],
{}, set(), (), None e False; o resto é verdadeiro. Por isso se escreve
if lista: em vez de if len(lista) > 0:. None representa "sem valor" e é um objeto de
verdade (não um ponteiro nulo). É o padrão para parâmetros opcionais.
Condicionais e laços¶
if chute == numero_secreto:
print('Você acertou!')
elif chute > numero_secreto:
print('Seu chute foi maior')
else:
print('Seu chute foi menor')
# Dica de legibilidade: dar nome às condições
acertou = chute == numero_secreto
maior = chute > numero_secreto
- Indentação obrigatória (4 espaços por convenção): ela é a sintaxe do bloco.
whilerepete enquanto a condição for verdadeira — cuidado com o laço infinito se a condição nunca mudar.breaksai do laço;continuepula para a próxima volta;passé um bloco vazio.forpercorre qualquer sequência/iterável.range(inicio, fim, passo)gera inteiros e o fim não é incluído:
for rodada in range(1, 4): # 1, 2, 3
print(rodada)
for n in range(1, 10, 2): # 1, 3, 5, 7, 9
print(n)
for indice, letra in enumerate('abc'): # complemento: índice + valor
print(indice, letra)
# compreensão de lista (list comprehension): forma concisa de criar listas
letras_acertadas = ['_' for _ in 'banana'] # ['_','_','_','_','_','_']
pares = [n for n in range(10) if n % 2 == 0]
Estruturas de dados¶
| Tipo | Sintaxe | Mutável? | Ordenado? | Notas |
|---|---|---|---|---|
list (lista) |
[1, 2, 3] |
sim | sim (por posição) | Pode misturar tipos |
tuple (tupla) |
(1, 2, 3) ou 1, 2, 3 |
não | sim | É a vírgula que cria a tupla; pode ser chave de dicionário |
range |
range(1, 10) |
não | sim | Guarda só início, fim e passo (não a lista inteira) |
str |
'texto' |
não | sim | Sequência de caracteres |
set (conjunto) |
{1, 2, 3} / set() |
sim | não | Sem repetidos; {} vazio é um dict! |
dict (dicionário) |
{'nome': 'João'} |
sim | por inserção* | Pares chave-valor |
*Hoje: a apostila trata o dict como sem ordem; desde o Python 3.7 ele preserva a ordem de
inserção (conjuntos continuam sem ordem garantida).
Sequências: índices, fatias e métodos de lista¶
As sequências (list, tuple, range, str) compartilham operações: x in s, s + t,
s * n, s[i], s[i:j], s[i:j:k], len, min, max, s.count(x).
lista = [2, 3, 5, 7, 11]
lista[0] # 2 (índices começam em 0)
lista[-1] # 11 (índice negativo conta do fim)
lista[:2] # [2, 3] (fatia: o fim não é incluído)
lista[2:] # [5, 7, 11]
lista[-3:] # [5, 7, 11]
lista[10] # IndexError
lista.append(13) # acrescenta um item no fim
lista.extend([17, 19]) # acrescenta vários (o mesmo que lista += [17, 19])
lista.pop() # remove e devolve o último (ou o da posição informada)
- Listas são mutáveis; tuplas e strings não (
dias[0] = 'dom'→TypeError). Prefira tupla para dados que não devem mudar. Uma tupla que contém uma lista ainda pode ter essa lista alterada por dentro. list('python')etuple(lista)convertem iteráveis.
Conjuntos¶
a = set('abacate')
b = set('abacaxi')
a - b # diferença a | b # união
a & b # interseção a ^ b # diferença simétrica
Servem para remover duplicados e testar pertencimento rapidamente.
Dicionários¶
pessoa = {'nome': 'João', 'idade': 25, 'cidade': 'São Paulo'}
pessoa['nome'] # 'João'
pessoa['pais'] = 'Brasil' # inserir ou atualizar
pessoa.keys() # chaves
pessoa.values() # valores
pessoa.get('profissao', '—') # complemento: valor padrão se a chave não existir
pessoa[0] # KeyError: não se acessa por posição, só por chave
- Acessa-se pela chave, e a chave precisa ser imutável/hashable (
str,int, tupla...).{[1, 2]: 'x'}dáTypeError: unhashable type: 'list'. dict(um=1, dois=2)também cria dicionários.
Funções¶
def velocidade(espaco, tempo):
return espaco / tempo # sem return, a função devolve None
def dados(nome, idade=None): # parâmetro opcional com valor padrão
if idade is not None:
return f'nome: {nome}, idade: {idade}'
return f'nome: {nome}, idade: não informada'
dados('joão') # só o nome
dados(idade=20, nome='ana') # argumentos nomeados (qualquer ordem)
- Parâmetros obrigatórios vêm antes dos opcionais; chamar sem um obrigatório →
TypeError. - Retornar vários valores devolve uma tupla (e dá para desempacotar):
Cuidado: valor padrão mutável
Complemento: nunca use lista/dicionário como valor padrão (def f(itens=[])): o objeto é
criado uma única vez e compartilhado entre as chamadas. Use None e crie dentro da
função.
*args e **kwargs¶
Permitem receber um número variável de argumentos. Os nomes são convenção — o que vale são os asteriscos.
def teste(arg, *args): # *args: argumentos posicionais extras, empacotados em tupla
for outro in args:
print(outro)
def minha_funcao(**kwargs): # **kwargs: argumentos nomeados extras, empacotados em dict
for chave, valor in kwargs.items():
print(f'{chave} = {valor}')
lista = ['é', 'muito', 'legal']
teste('python', *lista) # * desempacota uma sequência na chamada
minha_funcao(**{'nome': 'joao', 'idade': 25}) # ** desempacota um dicionário
Módulos e import¶
Um módulo é simplesmente um arquivo .py; um pacote é uma pasta de módulos.
import random
from conta import Conta # importa só uma classe do módulo conta.py
random.randrange(0, 4) # inteiro de 0 a 3 (o fim não é incluído)
if __name__ == '__main__': # só executa quando o arquivo é rodado diretamente
jogar() # (e não quando é importado por outro módulo)
Variáveis criadas dentro de uma função têm escopo local: só existem ali. Para a função usar
algo de fora, passe como parâmetro e devolva o resultado com return. Refatorar é dividir o
código em funções pequenas e de responsabilidade única
(Refatoração).
Arquivos¶
with open('palavras.txt', 'r', encoding='utf-8') as arquivo: # r = leitura (padrão)
palavras = [linha.strip() for linha in arquivo] # um arquivo é uma sequência de linhas
with open('palavras.txt', 'a', encoding='utf-8') as arquivo: # a = acrescentar ao final
arquivo.write('morango\n') # \n é a quebra de linha
| Modo | Efeito |
|---|---|
'r' |
Leitura; FileNotFoundError se o arquivo não existir |
'w' |
Escrita; apaga o conteúdo anterior (cria se não existir) |
'a' |
Acrescenta no fim (append) |
'b' (combinado: 'rb') |
Modo binário (imagens etc.) |
read()lê tudo (e o "cursor" fica no fim: ler de novo devolve''); iterar comforlê linha a linha;readline()lê uma.printewritenão se comportam igual:printacrescenta\n,writenão.- Sempre feche o arquivo. A forma segura é o
with: ele fecha o arquivo mesmo se ocorrer um erro dentro do bloco (a apostila trata isso como "para saber mais"; na prática, é o padrão). - O módulo
csvlê/escreve arquivos de valores separados por vírgula:
import csv
with open('funcionarios.txt', newline='', encoding='utf-8') as arquivo:
for nome, cpf, salario in csv.reader(arquivo):
print(nome, float(salario))
Orientação a objetos em Python¶
Classes, objetos e __init__¶
Uma classe é a "receita"; um objeto (instância) é o que se constrói a partir dela. O
procedural com dicionários (conta['saldo']) deixa os dados soltos e sem proteção; a classe
junta dados e comportamento.
class Conta:
def __init__(self, numero, titular, saldo, limite=1000.0):
self.numero = numero # atributos de instância
self.titular = titular
self.saldo = saldo
self.limite = limite
def deposita(self, valor):
self.saldo += valor
def saca(self, valor):
if valor > self.saldo + self.limite:
return False
self.saldo -= valor
return True
def transfere_para(self, destino, valor):
if self.saca(valor):
destino.deposita(valor)
return True
return False
conta = Conta('123-4', 'João', 120.0)
conta.deposita(20.0) # o Python passa a conta como "self" automaticamente
selfé a referência à própria instância: todo método de instância o recebe como primeiro parâmetro (por convenção chamadoself).__init__não cria o objeto — inicializa. Quem cria é o__new__, chamado antes; o__init__recebe a instância pronta. Chama-se "construtor" por abuso de linguagem.- Sem
__init__compatível,Conta()dáTypeError: missing 4 required positional arguments. - Python é dinâmico: nada impede
conta.nome = 'x'em tempo de execução (veja__slots__). - Tudo é objeto em Python: números, strings, funções, classes, módulos (
type(2)→int).
Objetos são acessados por referência¶
Uma variável guarda uma referência, não o objeto. c2 = c1 não copia — as duas variáveis
apontam para o mesmo objeto. A passagem de argumentos funciona como uma atribuição: passar
uma Conta a uma função não a clona.
c1 = Conta('123-4', 'João', 120.0)
c2 = c1
c2.deposita(30.0)
c1.saldo # 150.0 — é o mesmo objeto
id(c1) == id(c2) # True
Por padrão, == entre objetos compara a identidade: duas contas com os mesmos dados são
diferentes. Para comparar por conteúdo, implemente __eq__ (veja métodos mágicos).
Composição¶
Classes colaboram: um objeto guarda referências para outros.
class Cliente:
def __init__(self, nome, sobrenome, cpf):
self.nome, self.sobrenome, self.cpf = nome, sobrenome, cpf
class Historico:
def __init__(self):
self.data_abertura = datetime.datetime.today()
self.transacoes = []
class Conta:
def __init__(self, numero, cliente, saldo, limite=1000.0):
self.cliente = cliente # agregação: o Cliente existe sem a Conta
self.historico = Historico() # composição: o Histórico nasce e morre com a Conta
- Agregação: o objeto contido existe independentemente (um
Cliente). - Composição: o objeto contido depende do dono (o
HistoricodaConta). - Navega-se pelo ponto:
conta.cliente.nome.
Encapsulamento: convenções, não cadeados¶
Python não tem private de verdade.
| Convenção | Efeito |
|---|---|
atributo |
Público |
_atributo |
"Protegido": por convenção (PEP 8), não deve ser acessado de fora; o interpretador não impede |
__atributo |
Name mangling: o nome vira _Classe__atributo. Evita colisões em subclasses, mas não torna o atributo realmente privado (p._Pessoa__idade ainda funciona) |
Atribuir pessoa.__idade = 25 de fora apenas cria um atributo novo — outra fonte de
confusão. A maioria dos programadores Python prefere o _ simples.
Properties em vez de getters e setters¶
Em Java usa-se getSaldo()/setSaldo(); em Python o estilo "pitônico" é atributo público
simples e, se um dia for preciso validar, transformá-lo em property sem mudar quem usa a
classe:
class Conta:
def __init__(self, saldo=0.0):
self._saldo = saldo
@property
def saldo(self): # leitura: conta.saldo
return self._saldo
@saldo.setter
def saldo(self, valor): # escrita: conta.saldo = x (com validação)
if valor < 0:
raise ValueError('saldo não pode ser negativo')
self._saldo = valor
Definição: Decorator
Um decorator (@algo) é uma função que recebe outra função e devolve uma versão
alterada dela. @property é um decorator: transforma o método em um atributo calculado.
(Não confundir com o padrão de projeto Decorator, mas a ideia de "acrescentar
comportamento sem editar o original" é a mesma.)
Não crie property/setter para todo atributo "por hábito": o saldo, por exemplo, só deveria
mudar por saca() e deposita(); um setter público pode ser desnecessário.
Atributos de classe, @classmethod e @staticmethod¶
class Conta:
_total_contas = 0 # atributo de CLASSE: compartilhado por todas as instâncias
def __init__(self, saldo):
self._saldo = saldo
Conta._total_contas += 1 # acesse pela classe (self.x += 1 criaria um atributo de instância!)
@classmethod
def total_contas(cls): # recebe a CLASSE (cls); respeita herança
return cls._total_contas
@staticmethod
def codigo_banco(): # não recebe nada: é só uma função dentro da classe
return '001'
| Tipo de método | 1º parâmetro | Quando usar |
|---|---|---|
| De instância | self (a instância) |
Opera nos dados de um objeto |
@classmethod |
cls (a classe) |
Opera na classe/atributos de classe; fábricas alternativas; pode ser sobrescrito nas filhas |
@staticmethod |
nenhum | Função utilitária sem relação com a instância ou a classe (muitas vezes cabe melhor em uma função de módulo) |
__slots__¶
Por padrão, cada instância guarda seus atributos em um dicionário (__dict__), o que gasta
memória. __slots__ fixa a lista de atributos e elimina o __dict__:
Efeito colateral: não dá mais para criar atributos novos (AttributeError). O objetivo
principal é economizar memória quando há milhões de instâncias (a apostila cita 40–50%), e
não "proteger" a classe.
dir, vars e __dict__¶
dir(obj) lista os membros (incluindo os métodos mágicos herdados de object); vars(obj)
ou obj.__dict__ devolve um dicionário com os atributos da instância.
Complemento: dataclass¶
Para classes que só carregam dados, @dataclass gera __init__, __repr__ e __eq__:
from dataclasses import dataclass
@dataclass
class Cliente:
nome: str
sobrenome: str
cpf: str
Cliente('João', 'Oliveira', '111') == Cliente('João', 'Oliveira', '111') # True
Herança, polimorfismo e interfaces¶
Herança e super()¶
A subclasse recebe tudo da superclasse; para reaproveitar a inicialização, chame super():
class Funcionario:
def __init__(self, nome, cpf, salario):
self._nome, self._cpf, self._salario = nome, cpf, salario
def get_bonificacao(self):
return self._salario * 0.10
class Gerente(Funcionario):
def __init__(self, nome, cpf, salario, senha, qtd_gerenciados):
super().__init__(nome, cpf, salario) # reutiliza o __init__ da mãe
self._senha = senha
self._qtd_gerenciados = qtd_gerenciados
def get_bonificacao(self): # reescrita (override)
return super().get_bonificacao() + 1000 # chama a versão da mãe e acrescenta
- Toda classe herda de
object(a mãe de todas), de onde vêm os métodos mágicos. - Reescrever um método substitui o da mãe;
super().metodo()permite reaproveitá-lo (e evita duplicar regras que podem mudar). - Pergunta de modelagem: "o
Diretoré umGerente?" — só herde se a relação é um fizer sentido. Herança aumenta o acoplamento entre mãe e filha (Herança x composição). - Polimorfismo: código que usa
Funcionariofunciona com qualquer subclasse, e o método executado é sempre o do objeto real (ControleDeBonificacoes.registra(gerente)chamaGerente.get_bonificacao). Assim, criarSecretarianão exige mudar quem usaFuncionario.
Métodos mágicos (dunder methods)¶
Métodos com dois sublinhados dos dois lados (__nome__) são chamados pelo interpretador e
permitem que seus objetos se comportem como tipos nativos:
| Método | Quando é chamado |
|---|---|
__init__ / __new__ |
Inicialização / criação do objeto |
__str__ |
print(obj) e str(obj): texto amigável para o usuário |
__repr__ |
repr(obj): representação técnica, idealmente reconstruível (Ponto(1, 2)); usada no console e em logs |
__eq__, __lt__... |
==, <... |
__add__, __mul__, __truediv__, __mod__, __pow__ |
+, *, /, %, ** |
__len__, __getitem__, __iter__, __contains__ |
len(), obj[i], for, in |
class Ponto:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f'({self.x}, {self.y})'
def __repr__(self):
return f'Ponto({self.x}, {self.y})'
def __eq__(self, outro):
return isinstance(outro, Ponto) and (self.x, self.y) == (outro.x, outro.y)
print(Ponto(1, 2)) # (1, 2)
repr(Ponto(1, 2)) # 'Ponto(1, 2)'
Correção da apostila
O exemplo de __repr__ da fonte soma + 1 às coordenadas, o que contradiz o propósito do
método (reconstruir o mesmo objeto via eval(repr(obj))). O correto é o acima. Além
disso, a fonte cita __div__, que é do Python 2: no Python 3 é __truediv__.
Duck typing¶
"Se anda como um pato e grasna como um pato, então é um pato."
Em Python, o que importa é se o objeto responde às mensagens (tem os métodos) que o código
usa, não de qual classe ele é. Qualquer objeto com get_bonificacao() funciona em
registra(), mesmo sem herdar de Funcionario.
class Pato:
def grasna(self): return 'quack!'
class Ganso:
def grasna(self): return 'quack!'
for ave in (Pato(), Ganso()):
print(ave.grasna())
- O estilo pitônico é EAFP (Easier to Ask Forgiveness than Permission): assuma que o
atributo existe e trate a exceção (
try/except AttributeError), em vez de testar comhasattr()/isinstance()antes (LBYL, Look Before You Leap). hasattr(obj, 'metodo')verifica se o atributo existe (nas instâncias e na classe — a apostila simplifica ao dizer "no__dict__").- A tipagem é dinâmica, mas forte:
'2' + 2dáTypeError(não converte sozinho).
Classes abstratas (abc)¶
import abc
class Funcionario(abc.ABC):
@abc.abstractmethod
def get_bonificacao(self):
"""Cada tipo de funcionário define a sua bonificação."""
class Gerente(Funcionario):
def get_bonificacao(self):
return self._salario * 0.15
Funcionario() # TypeError: Can't instantiate abstract class ... with abstract methods get_bonificacao
Uma classe abstrata não pode ser instanciada e serve de molde: toda subclasse concreta é
obrigada a implementar os métodos @abstractmethod, senão também não é instanciável. Usa-se
quando a classe-mãe só existe para dar polimorfismo (Pessoa → PessoaFisica/PessoaJuridica).
Herança múltipla, MRO e mix-ins¶
Python permite herdar de várias classes: class Gerente(Funcionario, Autenticavel). O risco é
o problema do diamante: se B e C herdam de A e ambas sobrescrevem m2(), qual versão
D(B, C) usa? O Python resolve com a MRO (Method Resolution Order): uma ordem linear,
da esquerda para a direita, que se consulta com D.mro():
class A:
def m1(self): print('A')
class B(A):
def m2(self): print('B')
class C(A):
def m2(self): print('C')
class D(B, C): pass
D.mro() # [D, B, C, A, object] -> D().m2() usa o m2 de B
super() segue a MRO, então chama cada superclasse uma única vez, mesmo em hierarquias
complexas.
Definição: Mix-in
Classe não feita para ser usada sozinha, que só acrescenta uma funcionalidade opcional
(ex.: AutenticavelMixIn, HoraExtraMixIn) a outras por herança múltipla. Por convenção,
leva MixIn no nome e geralmente não tem __init__. É prático em frameworks pequenos, mas
pode causar colisões de nomes e hierarquias confusas em sistemas grandes (o Tkinter
é o exemplo clássico).
Interfaces: protocolos e subclasses virtuais¶
Python não tem a palavra interface: toda classe tem uma interface (seus membros
públicos). Um protocolo é um conjunto de métodos que formam um "contrato" informal (como
__iter__ + __next__ para iteráveis), definido por documentação e convenção.
Para formalizar o contrato sem herança múltipla, use uma ABC e a subclasse virtual:
class Autenticavel(abc.ABC):
@abc.abstractmethod
def autentica(self, senha): ...
Autenticavel.register(Gerente) # Gerente "promete" cumprir o contrato, sem herdar
isinstance(Gerente(...), Autenticavel) # True
O Python não verifica se Gerente realmente implementa autentica ao registrar; se não
implementar, o erro (AttributeError) só aparece na chamada. Complemento: hoje o jeito
preferido de declarar "estruturas que se comportam assim" é typing.Protocol (tipagem
estrutural, verificada por ferramentas como o mypy) — a versão estática do duck typing.
Exceções e erros¶
Uma exceção representa algo anormal durante a execução. Se ninguém trata, ela sobe pela
pilha de chamadas (de metodo2 para metodo1 e para o programa principal) e encerra o
programa, imprimindo o traceback. Se alguém trata (except), a execução volta ao normal a
partir dali.
try:
resultado = 10 / divisor
except ZeroDivisionError:
print('divisão por zero')
except (TypeError, ValueError) as erro: # vários tipos em uma tupla; "as" dá acesso ao objeto
print(f'entrada inválida: {erro}')
else:
print(f'resultado: {resultado}') # só roda se NENHUMA exceção ocorreu
finally:
print('sempre executa') # limpeza: roda com ou sem erro
raiselança uma exceção:raise ValueError('Valor negativo').raisesozinho, dentro de umexcept, relança a exceção atual.- Prefira exceções a códigos de retorno (
return False, "números mágicos"): quem chama é obrigado a notar o problema, e o programa não segue em estado inconsistente. - Exceções próprias derivam de
Exception(nunca deBaseException) e costumam terminar emError; crie uma base por módulo e especializações:
class ContaError(Exception):
"""Base das exceções do módulo de contas."""
class SaldoInsuficienteError(ContaError):
pass
def saca(self, valor):
if valor < 0:
raise ValueError('Valor de saque negativo.')
if valor > self._saldo:
raise SaldoInsuficienteError('Saldo insuficiente.')
self._saldo -= valor
- Evite
except:"pelado" (pega tudo e esconde bugs) e capture o tipo mais específico. - Complemento: use
raise NovoErro(...) from erro_originalpara encadear causas.
Tipos de erro¶
| Tipo | Quando ocorre |
|---|---|
SyntaxError / IndentationError |
Estrutura inválida; detectado antes de executar (na compilação para bytecode) |
NameError |
Variável que não existe |
TypeError |
Operação com tipo inadequado (lista['a'], '2' + 2) |
ValueError |
Tipo certo, valor inválido (int('abc')) |
KeyError / IndexError |
Chave de dicionário / índice de sequência inexistente (ambos são LookupError) |
AttributeError |
Atributo ou método inexistente no objeto |
ZeroDivisionError |
Divisão por zero (ArithmeticError) |
FileNotFoundError |
Arquivo inexistente (OSError) |
Erro semântico é quando o programa roda, mas faz a coisa errada (erro de regra de negócio):
não há exceção, então ajuda usar print, testes e o depurador pdb (import pdb;
pdb.set_trace() ou, hoje, breakpoint()).
Hierarquia resumida: BaseException → SystemExit, KeyboardInterrupt e Exception; e
Exception → ArithmeticError, LookupError, OSError, TypeError, ValueError,
RuntimeError... É por isso que except LookupError pega tanto KeyError quanto IndexError.
Compare com o modelo de exceções do Java.
O módulo collections¶
| Tipo | Para quê |
|---|---|
defaultdict(list) |
Dicionário com valor padrão: d[chave].append(v) sem checar se a chave existe (sem KeyError) |
Counter |
Conta ocorrências: Counter(['a','b','a']) → {'a': 2, 'b': 1}; .most_common(n) |
deque |
Fila de duas pontas com append/appendleft/pop/popleft em O(1) (Estrutura de Dados) |
namedtuple |
Tupla com campos nomeados e imutável: Conta = namedtuple('Conta', 'numero titular saldo') |
UserDict, UserList, UserString |
Bases para criar variações de dict/list/str sem os problemas de herdar direto do tipo nativo |
from collections import defaultdict, Counter, deque, namedtuple
cores = [('1', 'azul'), ('2', 'amarelo'), ('1', 'branco')]
agrupadas = defaultdict(list)
for chave, valor in cores:
agrupadas[chave].append(valor) # {'1': ['azul', 'branco'], '2': ['amarelo']}
fila = deque(['a', 'b'])
fila.appendleft('z') # deque(['z', 'a', 'b'])
Por que não herdar direto de dict/list
Em CPython, os métodos dos tipos nativos muitas vezes ignoram versões sobrescritas por
subclasses (ex.: dict.update não chama o seu __setitem__). Para variar o comportamento,
herde de UserDict/UserList (que guardam um dict/lista interno em .data) ou das ABCs.
Containers próprios com collections.abc¶
O módulo collections.abc oferece ABCs para as interfaces das coleções (Container, Sized,
Iterable, Sequence, MutableSequence, Mapping...). Ao herdar de uma delas, você é
obrigado a implementar os métodos abstratos e ganha vários de graça. Para uma lista que só
aceita um tipo de objeto, herde de MutableSequence e implemente __len__, __getitem__,
__setitem__, __delitem__ e insert (o append, a iteração e o in vêm prontos):
from collections.abc import MutableSequence
class Funcionarios(MutableSequence):
def __init__(self):
self._dados = [] # atributo de INSTÂNCIA (um por objeto)
def __len__(self): return len(self._dados)
def __getitem__(self, pos): return self._dados[pos]
def __delitem__(self, pos): del self._dados[pos]
def __setitem__(self, pos, valor):
self._validar(valor)
self._dados[pos] = valor
def insert(self, pos, valor):
self._validar(valor)
self._dados.insert(pos, valor)
@staticmethod
def _validar(valor):
if not isinstance(valor, Funcionario):
raise TypeError('Só são aceitos objetos Funcionario')
Correção da apostila
A fonte declara _dados = [] no corpo da classe, o que cria uma única lista
compartilhada por todas as instâncias (e usa self._dados.__contains__(self, item), que
está incorreto). O certo é criar a lista no __init__, como acima.
Na prática, o dia a dia raramente exige criar ABCs próprias: as ABCs são mais usadas em
frameworks e bibliotecas, e para isinstance(x, Iterable)/Mapping.
Boas práticas e idiomas de Python¶
- PEP 8: 4 espaços,
snake_casepara funções/variáveis,PascalCasepara classes,MAIÚSCULASpara constantes. Ferramentas:black,ruff/flake8. - Prefira compreensões e iteradores a laços com contador manual; use
enumerateezip. - Use
withpara qualquer recurso que precise ser fechado (arquivos, conexões, locks). - Atributos públicos +
@propertyquando precisar; evite getters/setters estilo Java. - Duck typing + EAFP, com ABCs/
Protocolquando for preciso formalizar contratos. - Não use valores padrão mutáveis; use
None. - Trate exceções específicas e lance as suas, derivadas de
Exception. - Ambientes virtuais e arquivo de dependências (
requirements.txt) em cada projeto. - Testes com
pytest(ouunittest), na linha de Qualidade.
Perguntas comuns de entrevista (resumo)¶
| Pergunta | Resposta curta |
|---|---|
| Python é compilada ou interpretada? | O CPython compila para bytecode (.pyc) e a PVM o interpreta; o passo de compilação é automático |
| Qual a diferença entre lista e tupla? | Lista é mutável; tupla é imutável (e pode ser chave de dicionário) |
== x is? |
== compara valores; is compara se é o mesmo objeto (identidade) |
O que é self? |
A referência à instância; primeiro parâmetro de todo método de instância |
__init__ é o construtor? |
Na verdade inicializa; quem cria o objeto é __new__ |
@classmethod x @staticmethod? |
O primeiro recebe a classe (cls) e respeita herança; o segundo não recebe nada |
O que é *args/**kwargs? |
Empacotam argumentos posicionais (tupla) e nomeados (dict) extras |
Existe private em Python? |
Não; só convenções (_x) e name mangling (__x) |
| O que é a MRO? | A ordem em que o Python procura métodos em hierarquias com herança múltipla (Classe.mro()) |
| O que é duck typing? | Importa o que o objeto sabe fazer, não a que classe pertence |
Para que serve o with? |
Garante a liberação do recurso (ex.: fechar o arquivo) mesmo com erros |
| Mutable default argument? | Valor padrão mutável é criado uma vez e compartilhado; use None |
| O que é um decorator? | Função que recebe outra função e devolve uma versão modificada (@property, @classmethod) |
Veja também: Java e Go (para comparar tipagem, concorrência e modelos de objetos), Orientação a Objetos (teoria), Estrutura de Dados e Machine Learning (Python aplicado a dados com scikit-learn e Pandas).