Python Interview Study Guide — Factored AI
Python Interview
Study Guide
─────────────────────────────────────────────
Preparação para Entrevista Técnica — Factored AI
Tópicos cobertos:
1. OOP em Python • 2. Estruturas de Dados & Algoritmos
3. Python Idiomático • 4. Backend & APIs REST
5. Debug & Troubleshooting
SEÇÃO 1 — Orientação a Objetos (OOP) em Python
OOP é o tópico mais cobrado pela Factored. O foco é demonstrar que você entende os conceitos,
sabe aplicá-los em código Python real, e consegue explicar suas decisões em voz alta durante a
entrevista.
1.1 Classes, Instâncias e o método __init__
Uma classe é um molde; uma instância é o objeto criado a partir desse molde. O método __init__ é o
construtor — é chamado automaticamente quando você cria uma nova instância.
class BankAccount:
# Atributo de CLASSE — compartilhado por todas as instâncias
bank_name = 'MyBank'
def __init__(self, owner: str, balance: float = 0.0):
# Atributos de INSTÂNCIA — únicos por objeto
[Link] = owner
self._balance = balance # convenção: _ = 'protegido'
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError('Deposit must be positive')
self._balance += amount
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def get_balance(self) -> float:
return self._balance
def __str__(self) -> str:
return f'BankAccount({[Link]}, ${self._balance:.2f})'
def __repr__(self) -> str:
return f'BankAccount(owner={[Link]!r}, balance={self._balance!r})'
# Uso
acc = BankAccount('Alice', 1000)
[Link](500)
print(acc) # BankAccount(Alice, $1500.00) — usa __str__
print(repr(acc)) # BankAccount(owner='Alice', balance=1500) — usa __repr__
print(BankAccount.bank_name) # MyBank — atributo de classe
📌 NOTA
self é a referência ao próprio objeto. É sempre o primeiro parâmetro de métodos de instância, mas
você não passa esse argumento na chamada — Python injeta automaticamente.
1.2 Herança e Polimorfismo
Herança permite que uma classe filha reutilize e estenda o comportamento da classe pai. Polimorfismo
significa que objetos de classes diferentes respondem ao mesmo método de formas distintas.
class Animal:
def __init__(self, name: str):
[Link] = name
def speak(self) -> str:
raise NotImplementedError('Subclasses must implement speak()')
def __str__(self) -> str:
return f'{self.__class__.__name__}({[Link]})'
class Dog(Animal):
def speak(self) -> str:
return f'{[Link]} says: Woof!'
class Cat(Animal):
def speak(self) -> str:
return f'{[Link]} says: Meow!'
class Duck(Animal):
def speak(self) -> str:
return f'{[Link]} says: Quack!'
# Polimorfismo em ação — mesmo código, comportamentos diferentes
animals = [Dog('Rex'), Cat('Luna'), Duck('Donald')]
for animal in animals:
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
print([Link]())
# Rex says: Woof!
# Luna says: Meow!
# Donald says: Quack!
# super() — chamando o construtor / método do pai
class ElectricCar(Car):
def __init__(self, make: str, model: str, battery_kwh: float):
super().__init__(make, model) # chama Car.__init__
self.battery_kwh = battery_kwh
def describe(self) -> str:
base = super().describe() # reutiliza método do pai
return f'{base} | Battery: {self.battery_kwh} kWh'
1.3 Encapsulamento — público, protegido e privado
Python usa convenções de nomenclatura para indicar visibilidade. Não há enforcement rígido como em
Java, mas é uma convenção amplamente seguida e cobrada em entrevistas.
public_attr Acessível de qualquer lugar. Uso normal.
_protected Convenção: uso interno da classe ou subclasses. Não use de fora.
__private Name mangling: Python renomeia para _ClassName__attr. Dificulta
acesso externo.
__dunder__ Métodos especiais do Python (magic methods). Nunca crie os seus
próprios.
class Person:
def __init__(self, name: str, age: int):
[Link] = name # público
self._age = age # protegido
self.__ssn = '000-00-00' # privado (name mangling)
@property
def age(self) -> int:
return self._age
@[Link]
def age(self, value: int) -> None:
if value < 0:
raise ValueError('Age cannot be negative')
self._age = value
p = Person('Bob', 30)
print([Link]) # Bob — OK
print([Link]) # 30 — usa @property
[Link] = 31 # usa @setter
# print(p.__ssn) # AttributeError!
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
print(p._Person__ssn) # 000-00-00 — name mangling (não faça isso)
1.4 Magic Methods (Dunder Methods)
Os magic methods permitem que suas classes se integrem com a sintaxe nativa do Python:
operadores, funções built-in, iteração, comparação, etc. São muito cobrados em entrevistas.
__init__(self, ...) Construtor — inicializa o objeto
__str__(self) str(obj) e print(obj) — representação legível para humanos
__repr__(self) repr(obj) — representação técnica/debug; idealmente eval-able
__len__(self) len(obj) — retorna o tamanho
__eq__(self, other) obj1 == obj2 — comparação de igualdade
__lt__(self, other) obj1 < obj2 — menor que (use @functools.total_ordering para o resto)
__add__(self, other) obj1 + obj2 — operador de adição
__contains__(self, item) 'x' in obj — operador in
__iter__(self) for x in obj — torna o objeto iterável
__next__(self) next(obj) — retorna o próximo elemento
__enter__ / __exit__ with obj — context manager protocol
__getitem__(self, key) obj[key] — acesso por índice/chave
__call__(self, ...) obj() — torna a instância chamável como função
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __add__(self, other: 'Vector') -> 'Vector':
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar: float) -> 'Vector':
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
def __len__(self) -> int:
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
return 2 # sempre 2 dimensões
def __str__(self) -> str:
return f'Vector({self.x}, {self.y})'
def __repr__(self) -> str:
return f'Vector(x={self.x}, y={self.y})'
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
print(v1 == Vector(1, 2)) # True
print(len(v1)) # 2
1.5 @classmethod vs @staticmethod
Uma diferença clássica de entrevista. Ambos podem ser chamados na classe sem criar uma instância,
mas têm propósitos muito diferentes.
@classmethod @staticmethod
Recebe cls como 1º parâmetro Não recebe cls nem self
Acessa/modifica a classe Função utilitária relacionada à classe
Usado como factory method Usado para helpers sem estado
@classmethod @staticmethod
def from_string(cls, s): def validate(x, y):
x, y = [Link](',') return x >= 0 and y >= 0
return cls(float(x), float(y))
class Date:
def __init__(self, year: int, month: int, day: int):
[Link], [Link], [Link] = year, month, day
@classmethod
def from_string(cls, date_str: str) -> 'Date':
"""Factory method — cria Date a partir de string '2024-01-15'"""
year, month, day = map(int, date_str.split('-'))
return cls(year, month, day) # cls = Date (ou subclasse)
@classmethod
def today(cls) -> 'Date':
import datetime
d = [Link]()
return cls([Link], [Link], [Link])
@staticmethod
def is_leap_year(year: int) -> bool:
"""Não precisa de instância nem da classe"""
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def __str__(self) -> str:
return f'{[Link]:04d}-{[Link]:02d}-{[Link]:02d}'
d1 = Date.from_string('2024-03-15') # @classmethod
d2 = [Link]() # @classmethod
print(Date.is_leap_year(2024)) # True — @staticmethod
1.6 Decorators
Decorators são funções que recebem outra função como argumento e retornam uma versão
modificada. São fundamentais em Python — @property, @classmethod, @staticmethod são todos
decorators built-in.
import functools
import time
# Decorator simples — adiciona comportamento sem modificar a função original
def timer(func):
@[Link](func) # preserva __name__, __doc__, etc.
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f'{func.__name__} took {elapsed:.4f}s')
return result
return wrapper
@timer
def slow_function(n: int) -> int:
return sum(range(n))
slow_function(1_000_000) # slow_function took 0.0312s
# Decorator com parâmetros (factory de decorator)
def retry(max_attempts: int = 3, exceptions=(Exception,)):
def decorator(func):
@[Link](func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts:
raise
print(f'Attempt {attempt} failed: {e}. Retrying...')
return wrapper
return decorator
@retry(max_attempts=3, exceptions=(ConnectionError,))
def fetch_data(url: str) -> dict:
# simula chamada HTTP que pode falhar
...
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
💡 TIP
Na entrevista, se pedirem para implementar um decorator, sempre use @[Link] para
preservar os metadados da função original. Isso demonstra conhecimento profissional.
1.7 Classes Abstratas (ABC)
Classes abstratas definem uma interface que subclasses são obrigadas a implementar. São a forma
mais rigorosa de polimorfismo em Python.
from abc import ABC, abstractmethod
class Shape(ABC):
"""Interface para formas geométricas"""
@abstractmethod
def area(self) -> float:
"""Todas as shapes devem implementar area()"""
...
@abstractmethod
def perimeter(self) -> float: ...
def describe(self) -> str:
# Método concreto — disponível para todas as subclasses
return f'{self.__class__.__name__}: area={[Link]():.2f},
perimeter={[Link]():.2f}'
class Circle(Shape):
def __init__(self, radius: float):
[Link] = radius
def area(self) -> float:
import math
return [Link] * [Link] ** 2
def perimeter(self) -> float:
import math
return 2 * [Link] * [Link]
class Rectangle(Shape):
def __init__(self, w: float, h: float):
self.w, self.h = w, h
def area(self) -> float: return self.w * self.h
def perimeter(self) -> float: return 2 * (self.w + self.h)
# Shape() # TypeError: Can't instantiate abstract class
c = Circle(5)
print([Link]()) # Circle: area=78.54, perimeter=31.42
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
SEÇÃO 2 — Estruturas de Dados & Algoritmos
A Factored cobra algoritmos e estruturas de dados em contextos práticos, não puzzles de LeetCode
Hard. O foco é mostrar que você pensa em complexidade e escolhe a estrutura certa para cada
problema.
2.1 Complexidade Big O — O que você precisa saber
O(1) Acesso a elemento por índice/chave. dict lookup, list[i]
O(log n) Busca binária, árvores balanceadas. Divide o problema pela metade a
cada passo.
O(n) Percorrer uma lista, busca linear, sum(), max(), min()
O(n log n) Sort eficiente (Timsort do Python). Mergesort, Heapsort.
O(n²) Loops aninhados ingênuos. Bubble sort, seleção.
O(2^n) Problemas de combinação sem memoização. Fibonacci recursivo
puro.
💡 TIP
Na entrevista: sempre mencione a complexidade da sua solução antes de codificar. 'Minha
abordagem é O(n) em tempo e O(1) em espaço porque...' — isso impressiona muito.
2.2 Listas (list) — operações e complexidades
# Operações essenciais e suas complexidades
lst = [3, 1, 4, 1, 5, 9, 2, 6]
[Link](7) # O(1) amortizado
[Link](0, 99) # O(n) — desloca todos os elementos
[Link]() # O(1) — remove do final
[Link](0) # O(n) — remove do início, desloca tudo
lst[3] # O(1) — acesso por índice
3 in lst # O(n) — busca linear
[Link]() # O(n log n) — Timsort in-place
sorted(lst) # O(n log n) — retorna nova lista
# Slicing — cria nova lista, O(k) onde k = tamanho do slice
lst[1:4] # [1, 4, 1]
lst[::-1] # lista invertida
lst[::2] # elementos em índice par
# List comprehension — mais pythônico e geralmente mais rápido que loop
squares = [x**2 for x in range(10)]
evens = [x for x in lst if x % 2 == 0]
flat = [item for sublist in [[1,2],[3,4]] for item in sublist]
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
2.3 Dicionários (dict) — o mais importante
Dicionários são provavelmente a estrutura mais usada em entrevistas Python. Lookup em O(1),
inserção em O(1), extremamente versáteis.
# Operações básicas
d = {'name': 'Alice', 'age': 30, 'city': 'São Paulo'}
d['name'] # 'Alice' — O(1), KeyError se não existir
[Link]('name', 'N/A') # 'Alice' — O(1), retorna default se ausente
[Link]('email', 'N/A') # 'N/A' — sem KeyError
'age' in d # True — O(1) verificação de chave
d['email'] = 'a@[Link]' # inserção/atualização — O(1)
del d['city'] # remoção — O(1)
[Link]('age', None) # remove e retorna, sem KeyError se ausente
# Iteração
for key in d: # itera sobre chaves
print(key)
for key, value in [Link](): # itera sobre pares
print(f'{key}: {value}')
# Dict comprehension
squared = {x: x**2 for x in range(5)} # {0:0, 1:1, 2:4, 3:9, 4:16}
inverted = {v: k for k, v in [Link]()}
# Merge de dicts (Python 3.9+)
merged = d1 | d2
d1 |= d2 # update in-place
# Padrão muito comum: contar frequência
from collections import Counter, defaultdict
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
count = Counter(words) # Counter({'apple': 3, 'banana': 2, ...})
count.most_common(2) # [('apple', 3), ('banana', 2)]
# defaultdict — evita KeyError ao acessar chave inexistente
groups = defaultdict(list)
for word in words:
groups[word[0]].append(word) # agrupa por primeira letra
2.4 Sets — para unicidade e operações de conjunto
s = {1, 2, 3, 4, 5}
[Link](6) # O(1)
[Link](3) # O(1), KeyError se não existir
[Link](99) # O(1), sem KeyError
3 in s # O(1) — muito mais rápido que list para busca
# Operações de conjunto
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
a | b # união: {1, 2, 3, 4, 5, 6}
a & b # interseção: {3, 4}
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
a - b # diferença: {1, 2}
a ^ b # diferença simétrica: {1, 2, 5, 6}
# Caso de uso clássico: remover duplicatas mantendo unicidade
lst = [1, 2, 2, 3, 3, 3, 4]
unique = list(set(lst)) # [1, 2, 3, 4] — ordem não garantida
# Verificar se dois grupos têm elementos em comum
has_overlap = bool(set_a & set_b) # O(min(|a|, |b|))
2.5 Filas e Pilhas
from collections import deque
# ── PILHA (Stack) — LIFO: Last In, First Out ──────────────────
# Use list para pilha — append/pop no final são O(1)
stack = []
[Link]('a') # push
[Link]('b')
[Link]('c')
top = [Link]() # pop — 'c' — O(1)
# Casos de uso: histórico de navegação, undo/redo, DFS, validação de parênteses
# ── FILA (Queue) — FIFO: First In, First Out ──────────────────
# Use deque — appendleft/popleft são O(1) ([Link](0) é O(n)!)
queue = deque()
[Link]('a') # enqueue — adiciona no final
[Link]('b')
[Link]('c')
first = [Link]() # dequeue — 'a' — O(1)
# deque também funciona como double-ended queue
[Link]('z') # adiciona no início — O(1)
[Link]('y')
# Casos de uso: BFS, processamento em ordem, rate limiting
# ── HEAP (Priority Queue) ─────────────────────────────────────
import heapq
heap = [5, 3, 8, 1, 9, 2]
[Link](heap) # O(n) — transforma lista em min-heap
smallest = [Link](heap) # O(log n) — remove e retorna o menor
[Link](heap, 4) # O(log n) — insere mantendo heap
# Para max-heap: insira valores negativados
max_heap = []
for val in [5, 3, 8, 1]:
[Link](max_heap, -val)
largest = -[Link](max_heap) # 8
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
2.6 Árvores Binárias — tópico cobrado na Factored
A Factored já cobrou problemas com árvores binárias. Você precisa saber implementar um nó, inserir
elementos e fazer os 3 tipos de travessia.
from typing import Optional
class TreeNode:
def __init__(self, val: int):
[Link] = val
[Link]: Optional['TreeNode'] = None
[Link]: Optional['TreeNode'] = None
def __repr__(self) -> str:
return f'TreeNode({[Link]})'
class BinarySearchTree:
def __init__(self):
[Link]: Optional[TreeNode] = None
def insert(self, val: int) -> None:
if not [Link]:
[Link] = TreeNode(val)
else:
self._insert([Link], val)
def _insert(self, node: TreeNode, val: int) -> None:
if val < [Link]:
if [Link] is None:
[Link] = TreeNode(val)
else:
self._insert([Link], val)
else:
if [Link] is None:
[Link] = TreeNode(val)
else:
self._insert([Link], val)
def search(self, val: int) -> bool:
return self._search([Link], val)
def _search(self, node: Optional[TreeNode], val: int) -> bool:
if node is None: return False
if [Link] == val: return True
if val < [Link]: return self._search([Link], val)
return self._search([Link], val)
# In-order (esquerda → raiz → direita) — retorna valores ORDENADOS em BST
def inorder(self, node: Optional[TreeNode] = None) -> list:
if node is None: node = [Link]
result = []
def _traverse(n):
if n:
_traverse([Link])
[Link]([Link])
_traverse([Link])
_traverse(node)
return result
# Pre-order (raiz → esquerda → direita) — útil para copiar árvore
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def preorder(self, node: Optional[TreeNode] = None) -> list:
if node is None: node = [Link]
result = []
def _traverse(n):
if n:
[Link]([Link])
_traverse([Link])
_traverse([Link])
_traverse(node)
return result
bst = BinarySearchTree()
for val in [5, 3, 7, 1, 4, 6, 8]:
[Link](val)
print([Link]()) # [1, 3, 4, 5, 6, 7, 8] — ordenado!
print([Link]()) # [5, 3, 1, 4, 7, 6, 8]
print([Link](4)) # True
print([Link](9)) # False
2.7 Algoritmos de Busca e Ordenação
# ── BUSCA BINÁRIA — O(log n) ────────────────────────────────
def binary_search(arr: list, target: int) -> int:
"""Retorna o índice do target ou -1 se não encontrado."""
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target: return mid
elif arr[mid] < target: left = mid + 1
else: right = mid - 1
return -1
arr = [1, 3, 5, 7, 9, 11, 13, 15]
print(binary_search(arr, 7)) # 3
print(binary_search(arr, 6)) # -1
# ── MERGE SORT — O(n log n), estável ────────────────────────
def merge_sort(arr: list) -> list:
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left: list, right: list) -> list:
result, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
[Link](left[i]); i += 1
else:
[Link](right[j]); j += 1
[Link](left[i:])
[Link](right[j:])
return result
print(merge_sort([5, 2, 8, 1, 9, 3])) # [1, 2, 3, 5, 8, 9]
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
# ── TWO POINTERS — padrão muito cobrado ─────────────────────
def two_sum_sorted(arr: list, target: int):
"""Encontra par que soma target em lista ORDENADA — O(n)"""
left, right = 0, len(arr) - 1
while left < right:
s = arr[left] + arr[right]
if s == target: return (left, right)
elif s < target: left += 1
else: right -= 1
return None
SEÇÃO 3 — Python Idiomático
Código idiomático é código que segue as convenções e padrões da comunidade Python. Em
entrevistas, demonstra senioridade e familiaridade com a linguagem além do básico.
3.1 Comprehensions
# ── List Comprehension ──────────────────────────────────────
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
flat = [item for row in matrix for item in row]
strings = [str(x) for x in range(5)]
# ── Dict Comprehension ──────────────────────────────────────
word_len = {word: len(word) for word in ['hello', 'world', 'python']}
inverted = {v: k for k, v in original_dict.items()}
filtered = {k: v for k, v in [Link]() if v > 0}
# ── Set Comprehension ───────────────────────────────────────
unique_len = {len(word) for word in words}
# ── Generator Expression — lazy, usa menos memória ──────────
total = sum(x**2 for x in range(1_000_000)) # não cria lista!
has_negative = any(x < 0 for x in numbers) # para no primeiro True
all_positive = all(x > 0 for x in numbers) # para no primeiro False
# Quando usar generator vs list:
# - Vai iterar uma única vez: generator
# - Vai indexar ou iterar múltiplas vezes: list
3.2 Generators e yield
# Função geradora — usa yield em vez de return
def fibonacci():
a, b = 0, 1
while True: # infinito! mas lazy
yield a
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
a, b = b, a + b
gen = fibonacci()
first_10 = [next(gen) for _ in range(10)] # [0,1,1,2,3,5,8,13,21,34]
# Generator com pipeline — muito eficiente para grandes volumes
def read_large_file(filepath: str):
with open(filepath) as f:
for line in f: # lê uma linha por vez
yield [Link]()
def filter_lines(lines, keyword: str):
for line in lines:
if keyword in line:
yield line
def count_words(lines):
for line in lines:
yield len([Link]())
# Composição de generators — processa arquivo enorme sem carregá-lo todo
lines = read_large_file('[Link]')
errors = filter_lines(lines, 'ERROR')
wc = count_words(errors)
total_wc = sum(wc) # só aqui o arquivo é realmente lido
3.3 *args e **kwargs
# *args — recebe qualquer número de argumentos posicionais
def sum_all(*args: float) -> float:
return sum(args)
sum_all(1, 2, 3) # 6
sum_all(1, 2, 3, 4, 5) # 15
# **kwargs — recebe qualquer número de argumentos nomeados
def print_info(**kwargs) -> None:
for key, value in [Link]():
print(f'{key}: {value}')
print_info(name='Alice', age=30, city='SP')
# Combinando tudo
def create_user(name: str, *roles: str, **attrs) -> dict:
"""
name — argumento obrigatório posicional
roles — 0 ou mais roles (posicionais extras)
attrs — 0 ou mais atributos nomeados
"""
return {'name': name, 'roles': list(roles), **attrs}
create_user('Alice', 'admin', 'dev', age=30, active=True)
# {'name': 'Alice', 'roles': ['admin', 'dev'], 'age': 30, 'active': True}
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
# Unpacking ao chamar funções
def add(a, b, c): return a + b + c
args = (1, 2, 3)
kwargs = {'a': 1, 'b': 2, 'c': 3}
add(*args) # equivale a add(1, 2, 3)
add(**kwargs) # equivale a add(a=1, b=2, c=3)
3.4 Context Managers (with)
# ── Uso básico ───────────────────────────────────────────────
with open('[Link]', 'r') as f: # __enter__ chamado
content = [Link]()
# __exit__ chamado automaticamente — arquivo fechado mesmo se há exceção
# ── Implementando seu próprio context manager ─────────────────
class DatabaseConnection:
def __init__(self, host: str, db: str):
[Link] = host
[Link] = db
[Link] = None
def __enter__(self):
print(f'Connecting to {[Link]}/{[Link]}...')
[Link] = {'host': [Link], 'db': [Link]} # simulado
return [Link]
def __exit__(self, exc_type, exc_val, exc_tb):
print('Closing connection')
[Link] = None
# return True ← suprimiria a exceção (raramente desejado)
with DatabaseConnection('localhost', 'mydb') as conn:
print(f'Using: {conn}')
# Conectando... Using: {...} Fechando...
# ── Usando contextlib — forma mais simples ────────────────────
from contextlib import contextmanager
@contextmanager
def timer_ctx(label: str):
import time
start = time.perf_counter()
try:
yield # execução do bloco 'with' acontece aqui
finally:
elapsed = time.perf_counter() - start
print(f'{label}: {elapsed:.4f}s')
with timer_ctx('data processing'):
result = [x**2 for x in range(100_000)]
# data processing: 0.0089s
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
3.5 Tratamento de Exceções
# ── Estrutura completa ───────────────────────────────────────
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f'Math error: {e}')
except (TypeError, ValueError) as e:
print(f'Type/Value error: {e}')
except Exception as e:
print(f'Unexpected: {e}')
raise # re-raise — nunca engula exceções silenciosamente!
else:
print('No exception — this runs only on success')
finally:
print('Always runs — cleanup here')
# ── Exceções customizadas ────────────────────────────────────
class AppError(Exception):
"""Base para todas as exceções da aplicação"""
pass
class ValidationError(AppError):
def __init__(self, field: str, message: str):
[Link] = field
[Link] = message
super().__init__(f'ValidationError on {field!r}: {message}')
class NotFoundError(AppError):
def __init__(self, resource: str, id):
super().__init__(f'{resource} with id={id!r} not found')
def get_user(user_id: int) -> dict:
users = {1: {'name': 'Alice'}}
if user_id not in users:
raise NotFoundError('User', user_id)
return users[user_id]
try:
user = get_user(999)
except NotFoundError as e:
print(e) # User with id=999 not found
3.6 Type Hints
from typing import Optional, Union, List, Dict, Tuple, Any, Callable
from typing import TypeVar, Generic
# Básico
def greet(name: str) -> str:
return f'Hello, {name}'
def divide(a: float, b: float) -> Optional[float]:
"""Retorna None se b == 0"""
return a / b if b != 0 else None
# Coleções
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def process(items: list[int]) -> dict[str, int]: # Python 3.9+
return {'sum': sum(items), 'count': len(items)}
# Union — aceita múltiplos tipos
def parse_id(value: str | int) -> int: # Python 3.10+
return int(value)
# Callable — tipo de função
def apply(func: Callable[[int], int], value: int) -> int:
return func(value)
apply(lambda x: x * 2, 5) # 10
# TypeVar — generics
T = TypeVar('T')
def first(lst: list[T]) -> Optional[T]:
return lst[0] if lst else None
first([1, 2, 3]) # int
first(['a', 'b', 'c']) # str
SEÇÃO 4 — Backend & APIs REST
Como a vaga é de backend engineering, você pode ser questionado sobre design de APIs, HTTP, e
como estruturaria um serviço simples em Python. O foco é mostrar pensamento sistêmico.
4.1 Princípios REST
GET Lê recurso. Idempotente. Sem body. Cacheável.
POST Cria recurso. Não idempotente. Com body. Retorna 201 Created.
PUT Atualiza recurso INTEIRO (substituição). Idempotente.
PATCH Atualiza recurso PARCIALMENTE. Envio apenas dos campos
alterados.
DELETE Remove recurso. Idempotente. Retorna 204 No Content.
200 OK Sucesso geral — GET, PUT, PATCH
201 Created Recurso criado com sucesso — POST
204 No Content Sucesso sem corpo de resposta — DELETE
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
400 Bad Request Erro do cliente — payload inválido, validação falhou
401 Unauthorized Não autenticado — precisa de credenciais
403 Forbidden Autenticado mas sem permissão
404 Not Found Recurso não encontrado
409 Conflict Conflito — ex: email já cadastrado
422 Unprocessable Dados semanticamente inválidos
429 Too Many Requests Rate limit atingido
500 Internal Error Erro no servidor — nunca exponha detalhes ao cliente
4.2 API REST com FastAPI (padrão da indústria em Python)
from fastapi import FastAPI, HTTPException, status, Depends
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
import uuid
app = FastAPI(title='User Service', version='1.0.0')
# ── Models ──────────────────────────────────────────────────
class UserCreate(BaseModel):
name: str
email: str
age: int
@validator('age')
def age_must_be_positive(cls, v):
if v <= 0:
raise ValueError('Age must be positive')
return v
class UserResponse(BaseModel):
id: str
name: str
email: str
age: int
# ── In-memory DB (simulado) ─────────────────────────────────
db: dict[str, dict] = {}
# ── Endpoints ───────────────────────────────────────────────
@[Link]('/users', response_model=UserResponse, status_code=201)
def create_user(user: UserCreate):
# Checar duplicata
if any(u['email'] == [Link] for u in [Link]()):
raise HTTPException(409, 'Email already registered')
user_id = str(uuid.uuid4())
db[user_id] = {'id': user_id, **[Link]()}
return db[user_id]
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
@[Link]('/users/{user_id}', response_model=UserResponse)
def get_user(user_id: str):
if user_id not in db:
raise HTTPException(404, f'User {user_id} not found')
return db[user_id]
@[Link]('/users', response_model=list[UserResponse])
def list_users(skip: int = 0, limit: int = 10):
users = list([Link]())
return users[skip : skip + limit]
@[Link]('/users/{user_id}', response_model=UserResponse)
def update_user(user_id: str, updates: dict):
if user_id not in db:
raise HTTPException(404, 'User not found')
db[user_id].update(updates)
return db[user_id]
@[Link]('/users/{user_id}', status_code=204)
def delete_user(user_id: str):
if user_id not in db:
raise HTTPException(404, 'User not found')
del db[user_id]
4.3 Padrões de Design importantes para Backend
Repository Pattern — separação de responsabilidades
from abc import ABC, abstractmethod
from typing import Optional
class UserRepository(ABC):
@abstractmethod
def find_by_id(self, user_id: str) -> Optional[dict]: ...
@abstractmethod
def save(self, user: dict) -> dict: ...
@abstractmethod
def delete(self, user_id: str) -> bool: ...
class InMemoryUserRepository(UserRepository):
def __init__(self):
self._store: dict = {}
def find_by_id(self, user_id: str) -> Optional[dict]:
return self._store.get(user_id)
def save(self, user: dict) -> dict:
self._store[user['id']] = user
return user
def delete(self, user_id: str) -> bool:
return self._store.pop(user_id, None) is not None
# Service layer usa o repositório via interface — não depende da implementação
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
class UserService:
def __init__(self, repo: UserRepository):
[Link] = repo
def get_user(self, user_id: str) -> dict:
user = [Link].find_by_id(user_id)
if not user:
raise ValueError(f'User {user_id} not found')
return user
4.4 Concorrência em Python — async/await
import asyncio
import aiohttp # pip install aiohttp
# Função assíncrona — não bloqueia a event loop enquanto aguarda I/O
async def fetch_user(session: [Link], user_id: int) -> dict:
async with [Link](f'[Link] as resp:
return await [Link]()
# Buscar múltiplos usuários em paralelo (I/O-bound)
async def fetch_all_users(user_ids: list[int]) -> list[dict]:
async with [Link]() as session:
tasks = [fetch_user(session, uid) for uid in user_ids]
results = await [Link](*tasks) # paralelo!
return list(results)
# Sem async (sequencial): 10 requests x 200ms = 2000ms
# Com async (paralelo): 10 requests x 200ms = ~200ms ← 10x mais rápido
# FastAPI é assíncrono nativamente
@[Link]('/users/{user_id}')
async def get_user(user_id: str):
user = await db.find_one({'_id': user_id}) # não bloqueia
if not user:
raise HTTPException(404, 'Not found')
return user
📌 NOTA
async/await é ideal para I/O-bound (chamadas HTTP, banco de dados, arquivos). Para CPU-bound
(processamento pesado), use multiprocessing ou [Link].
SEÇÃO 5 — Debug & Troubleshooting em Python
Saber debugar demonstra maturidade de engenharia. Na entrevista, se algo não funcionar, como você
aborda o problema é tão importante quanto a solução final.
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
5.1 O processo mental de debug
Nunca tente corrigir código aleatoriamente. Siga um processo:
1. Reproduza o problema de forma consistente. Sem reprodução, não há debug.
2. Isole — qual é a menor quantidade de código que ainda reproduz o bug?
3. Formule uma hipótese — o que você acha que está acontecendo e por quê?
4. Teste a hipótese — adicione logs, use o debugger, verifique valores intermediários.
5. Corrija e verifique — confirme que o bug sumiu e que nenhum novo apareceu.
💡 TIP
Na entrevista ao vivo: verbalize esse processo! 'Vou adicionar um print aqui para ver o valor de X
antes da chamada' demonstra método e experiência.
5.2 pdb — Python Debugger (essencial)
# ── Iniciando o debugger ────────────────────────────────────
import pdb
def buggy_function(data: list) -> int:
total = 0
for item in data:
pdb.set_trace() # execução para aqui — abre REPL interativo
total += item # você pode inspecionar variáveis em tempo real
return total
# Python 3.7+ — forma mais moderna
def buggy_function_v2(data: list) -> int:
total = 0
for item in data:
breakpoint() # equivalente a pdb.set_trace()
total += item
return total
# ── Comandos mais importantes no pdb ───────────────────────
# n (next) — executa a próxima linha (não entra em funções)
# s (step) — entra dentro de funções
# c (continue) — continua até o próximo breakpoint
# l (list) — mostra código ao redor da linha atual
# p expr — avalia e imprime expressão: p total, p len(data)
# pp expr — pretty-print: pp data
# w (where) — mostra call stack
# u / d — sobe/desce na call stack
# q (quit) — sai do debugger
# b 42 — adiciona breakpoint na linha 42
# h — help
# ── Post-mortem debugging — debug após exceção ──────────────
import pdb, sys
try:
resultado = buggy_function([1, 2, 'three', 4])
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
except Exception:
pdb.post_mortem() # abre debugger no ponto do crash
5.3 logging — nunca use só print() em produção
import logging
# ── Configuração básica ─────────────────────────────────────
[Link](
level=[Link],
format='%(asctime)s | %(levelname)-8s | %(name)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)
logger = [Link](__name__) # nome do módulo como identificador
# ── Níveis de log (do menor ao maior) ──────────────────────
# DEBUG — informação detalhada para desenvolvimento
# INFO — confirmação de que tudo funciona como esperado
# WARNING — algo inesperado mas não crítico
# ERROR — erro sério, a função falhou
# CRITICAL — erro grave, o sistema pode não continuar
[Link]('Processing item: %s', item) # lazy formatting
[Link]('User %s logged in', user_id)
[Link]('Config not found, using defaults')
[Link]('Failed to connect to DB: %s', exc)
[Link]('Out of disk space!')
[Link]('Unhandled error') # inclui traceback automaticamente
# ── Handler para arquivo ────────────────────────────────────
handler = [Link]('[Link]')
[Link]([Link]) # só erros vão para o arquivo
formatter = [Link]('%(asctime)s — %(levelname)s — %(message)s')
[Link](formatter)
[Link](handler)
# ── Padrão recomendado em cada módulo ─────────────────────
# Sempre no topo do arquivo, após os imports
logger = [Link](__name__)
class UserService:
def __init__(self, repo):
[Link] = repo
[Link] = [Link](self.__class__.__name__)
def get_user(self, user_id: str):
[Link]('Fetching user: %s', user_id)
user = [Link].find_by_id(user_id)
if not user:
[Link]('User not found: %s', user_id)
raise ValueError(f'User {user_id} not found')
[Link]('User retrieved successfully: %s', user_id)
return user
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
5.4 Erros comuns e como diagnosticá-los
NameError: name 'x' is not Variável não declarada ou fora de escopo. Verifique typos e escopo.
defined
AttributeError: 'NoneType' Objeto é None quando você não esperava. Adicione verificação: if obj
has no attr is not None.
TypeError: unsupported Tipos incompatíveis. Use type() ou isinstance() para inspecionar.
operand
KeyError: 'key' Chave não existe no dict. Use .get(key) ou 'key' in d antes.
IndexError: list index out Índice além do tamanho da lista. Verifique len() e off-by-one.
of range
RecursionError: max Recursão infinita ou muito profunda. Verifique caso base.
depth exceeded
ImportError / Módulo não instalado ou path errado. pip install ou verifique [Link].
ModuleNotFoundError
IndentationError Mistura de tabs e espaços, ou indentação incorreta.
StopIteration Iterator esgotado. Use list() para converter ou for loop.
5.5 Técnicas avançadas de diagnóstico
# ── traceback — analisando o stack trace ────────────────────
import traceback
try:
problematic_code()
except Exception as e:
traceback.print_exc() # imprime traceback completo
tb_str = traceback.format_exc() # captura como string para logging
[Link]('Error: %s\n%s', e, tb_str)
# ── inspect — inspecionando objetos em runtime ───────────────
import inspect
def debug_call():
frame = [Link]()
caller = [Link](frame)[1]
print(f'Called from {[Link]}:{[Link]}')
[Link](obj) # todos os atributos e métodos
[Link](some_func) # assinatura da função
[Link](some_func) # código fonte
# ── cProfile — identificando gargalos de performance ─────────
import cProfile, pstats, io
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def profile(func):
def wrapper(*args, **kwargs):
pr = [Link]()
[Link]()
result = func(*args, **kwargs)
[Link]()
s = [Link]()
ps = [Link](pr, stream=s).sort_stats('cumulative')
ps.print_stats(10) # top 10 funções mais lentas
print([Link]())
return result
return wrapper
@profile
def slow_operation():
return sum(x**2 for x in range(500_000))
# ── memory_profiler — identificando vazamentos de memória ────
# pip install memory-profiler
from memory_profiler import memory_usage
def memory_hungry():
return [x**2 for x in range(1_000_000)]
mem = memory_usage(memory_hungry)
print(f'Peak memory: {max(mem):.1f} MB')
# ── sys — informações de baixo nível ───────────────────────
import sys
[Link]([1, 2, 3]) # tamanho em bytes de um objeto
sys.exc_info() # (type, value, traceback) da exceção atual
[Link]() # limite de recursão (padrão: 1000)
[Link](2000) # aumenta limite (use com cuidado)
5.6 Testes — a melhor forma de prevenir bugs
import pytest
# ── Teste unitário básico ────────────────────────────────────
def add(a: int, b: int) -> int:
return a + b
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -1) == -2
def test_add_zero():
assert add(5, 0) == 5
# ── Parametrize — testa múltiplos casos de uma vez ──────────
@[Link]('a, b, expected', [
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0),
(100, -50, 50),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected
# ── Testando exceções ────────────────────────────────────────
def test_get_user_not_found():
service = UserService(InMemoryUserRepository())
with [Link](ValueError, match='not found'):
service.get_user('nonexistent-id')
# ── Fixtures — setup/teardown reutilizável ───────────────────
@[Link]
def user_service():
repo = InMemoryUserRepository()
service = UserService(repo)
# setup: adiciona dados de teste
[Link]({'id': '1', 'name': 'Alice', 'email': 'alice@[Link]'})
return service
def test_get_existing_user(user_service):
user = user_service.get_user('1')
assert user['name'] == 'Alice'
# ── Mock — simulando dependências externas ───────────────────
from [Link] import Mock, patch, MagicMock
def test_fetch_data_with_mock():
with patch('[Link]') as mock_get:
mock_get.return_value.json.return_value = {'id': 1, 'name': 'Test'}
mock_get.return_value.status_code = 200
result = fetch_data('[Link]
assert result['name'] == 'Test'
mock_get.assert_called_once_with('[Link]
⚠️ ATENÇÃO
Nunca faça chamadas HTTP reais em testes unitários. Use mocks. Testes que dependem de
serviços externos são lentos, frágeis e não determinísticos.
QUICK REFERENCE — Cheatsheet para a
Entrevista
Use essa seção para uma revisão rápida no dia anterior à entrevista.
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
OOP — Perguntas frequentes
Diferença class attr vs Class: compartilhado por todas as instâncias. Instance: único por
instance attr? objeto, definido no __init__ via self.
Quando usar Factory methods — criar instâncias de formas alternativas. Recebe cls.
@classmethod?
Quando usar Funções utilitárias relacionadas à classe mas sem acesso a cls ou self.
@staticmethod?
O que é name mangling Python renomeia para _ClassName__attr. Não é verdadeiramente
(__attr)? privado.
Para que serve __repr__? Representação técnica/debug. Idealmente eval()-able. Usado em logs.
Diferença herança vs Herança: 'é um'. Composição: 'tem um'. Prefira composição quando
composição? possível.
Estruturas de Dados — Quando usar cada uma
list Sequência ordenada, mutável. Acesso por índice O(1). Busca O(n).
Use quando ordem importa.
dict Mapeamento chave-valor. Lookup O(1). Use quando precisa acessar
por identificador.
set Coleção sem duplicatas. Lookup O(1). Use para unicidade e
operações de conjunto.
tuple Sequência imutável. Mais leve que list. Use para dados fixos, retorno
múltiplo de funções.
deque Fila ou pilha dupla. append/popleft O(1). Use quando faz pop/insert no
início frequentemente.
heapq Heap (fila de prioridade). Sempre acessa o menor elemento. O(log n)
para inserção/remoção.
Counter Dict especializado para contagem. most_common() muito útil.
defaultdict Dict que não lança KeyError. Útil para agrupar ou acumular.
Dicas comportamentais para a entrevista
• Pense em voz alta. Descreva seu raciocínio antes de codificar.
• Mencione complexidade. 'Minha solução é O(n) em tempo e O(1) em espaço'.
• Pergunte antes de codificar. Clarify requirements. 'Os dados já chegam ordenados?'
• Comece simples. Solução bruta primeiro, otimize depois se solicitado.
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
• Use type hints. Demonstra senioridade e cuidado com qualidade de código.
• Trate edge cases. Lista vazia, None, valores negativos, strings vazias.
• Não entre em pânico. Se travar, verbalize. 'Preciso de um momento para pensar no edge case aqui.'
📌 NOTA
A Factored valoriza o processo de raciocínio tanto quanto a resposta correta. Eles vão te treinar — o
que precisam confirmar é que você pensa como um engenheiro sênior.
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
SEÇÃO 6 — Testes Unitários em Python (Deep
Dive)
Testes são a maior prova de maturidade de um engenheiro. Código sem testes é código que você não
confia. Esta seção cobre pytest em profundidade, patterns de teste, mocking avançado e TDD.
6.1 Anatomia de um teste bem escrito — padrão AAA
Todo bom teste segue o padrão AAA: Arrange (preparar os dados), Act (executar a ação) e Assert
(verificar o resultado). Esse padrão torna os testes legíveis e fáceis de manter.
import pytest
from decimal import Decimal
# ── Classe sendo testada ─────────────────────────────────────
class ShoppingCart:
def __init__(self):
self._items: list[dict] = []
def add_item(self, name: str, price: Decimal, qty: int = 1) -> None:
if price <= 0:
raise ValueError(f'Price must be positive, got {price}')
if qty <= 0:
raise ValueError(f'Quantity must be positive, got {qty}')
self._items.append({'name': name, 'price': price, 'qty': qty})
def total(self) -> Decimal:
return sum(item['price'] * item['qty'] for item in self._items)
def item_count(self) -> int:
return sum(item['qty'] for item in self._items)
def clear(self) -> None:
self._items.clear()
# ── Testes seguindo o padrão AAA ────────────────────────────
class TestShoppingCart:
def test_empty_cart_has_zero_total(self):
# Arrange
cart = ShoppingCart()
# Act
result = [Link]()
# Assert
assert result == Decimal('0')
def test_add_single_item_updates_total(self):
# Arrange
cart = ShoppingCart()
# Act
cart.add_item('Apple', Decimal('1.50'), qty=3)
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
# Assert
assert [Link]() == Decimal('4.50')
assert cart.item_count() == 3
def test_add_multiple_items_accumulates_total(self):
# Arrange
cart = ShoppingCart()
# Act
cart.add_item('Apple', Decimal('1.50'), qty=2)
cart.add_item('Banana', Decimal('0.75'), qty=4)
# Assert
assert [Link]() == Decimal('6.00') # 2*1.5 + 4*0.75
def test_add_item_with_negative_price_raises(self):
# Arrange
cart = ShoppingCart()
# Act & Assert
with [Link](ValueError, match='Price must be positive'):
cart.add_item('Bad Item', Decimal('-1.00'))
def test_clear_empties_cart(self):
cart = ShoppingCart()
cart.add_item('Apple', Decimal('1.50'))
[Link]()
assert [Link]() == Decimal('0')
assert cart.item_count() == 0
6.2 Fixtures — setup reutilizável e isolado
Fixtures são funções que preparam o estado necessário para os testes. O pytest injeta
automaticamente fixtures pelo nome do parâmetro. Use-as para evitar repetição e garantir isolamento.
import pytest
from [Link] import MagicMock
# ── Fixtures simples ────────────────────────────────────────
@[Link]
def empty_cart() -> ShoppingCart:
return ShoppingCart()
@[Link]
def populated_cart() -> ShoppingCart:
cart = ShoppingCart()
cart.add_item('Apple', Decimal('1.50'), qty=2)
cart.add_item('Banana', Decimal('0.75'), qty=4)
return cart
# Usando fixtures nos testes
def test_clear_populated_cart(populated_cart):
populated_cart.clear()
assert populated_cart.total() == Decimal('0')
# ── Fixture com scope — controla ciclo de vida ───────────────
# scope='function' — padrão, cria nova instância por teste
# scope='class' — compartilhado por todos os testes da classe
# scope='module' — compartilhado por todos os testes do arquivo
# scope='session' — compartilhado por toda a execução do pytest
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
@[Link](scope='module')
def db_connection():
"""Conexão cara — criada UMA vez por arquivo de teste"""
conn = create_test_db_connection()
yield conn # yield em vez de return — permite cleanup
[Link]() # executado após TODOS os testes do módulo
@[Link]
def user_repo(db_connection):
"""Fixtures podem depender de outras fixtures"""
repo = UserRepository(db_connection)
yield repo
repo.clear_all() # cleanup após cada teste
# ── [Link] — fixtures compartilhadas entre arquivos ─────
# Crie um arquivo [Link] na raiz dos testes.
# Pytest o descobre automaticamente — sem necessidade de import.
# [Link]
@[Link](scope='session')
def app_config():
return {'env': 'test', 'db_url': 'sqlite:///:memory:'}
6.3 Parametrize — testando múltiplos cenários elegantemente
import pytest
def is_palindrome(s: str) -> bool:
cleaned = ''.join([Link]() for c in s if [Link]())
return cleaned == cleaned[::-1]
# ── Parametrize básico ───────────────────────────────────────
@[Link]('input_str, expected', [
('racecar', True),
('hello', False),
('A man a plan', False),
('Was it a car', False),
('', True),
('a', True),
('Racecar', True), # case-insensitive
('race car', True), # espaços ignorados
])
def test_is_palindrome(input_str, expected):
assert is_palindrome(input_str) == expected
# ── Parametrize com ids — nomes descritivos nos resultados ───
@[Link]('value, error_msg', [
(0, 'Price must be positive'),
(-1, 'Price must be positive'),
(-99, 'Price must be positive'),
], ids=['zero', 'negative_one', 'large_negative'])
def test_invalid_prices(value, error_msg):
cart = ShoppingCart()
with [Link](ValueError, match=error_msg):
cart.add_item('Item', Decimal(str(value)))
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
# ── Combinação de parametrize — produto cartesiano ───────────
@[Link]('qty', [1, 5, 100])
@[Link]('price', ['0.01', '9.99', '999.99'])
def test_valid_combinations(price, qty):
cart = ShoppingCart()
cart.add_item('Item', Decimal(price), qty=qty)
assert cart.item_count() == qty # 3 x 3 = 9 testes gerados
6.4 Mocking avançado — isolando dependências externas
Mocking é a técnica de substituir dependências reais (APIs, banco de dados, email) por objetos
controlados durante o teste. Isso torna os testes rápidos, determinísticos e sem efeitos colaterais.
from [Link] import Mock, MagicMock, patch, call, AsyncMock
import pytest
# ── Mock básico ──────────────────────────────────────────────
def send_welcome_email(user: dict, email_service) -> bool:
return email_service.send(
to=user['email'],
subject='Welcome!',
body=f"Hello {user['name']}!"
)
def test_send_welcome_email_calls_service():
# Arrange
user = {'name': 'Alice', 'email': 'alice@[Link]'}
mock_email = Mock() # cria mock genérico
mock_email.send.return_value = True # configura retorno
# Act
result = send_welcome_email(user, mock_email)
# Assert — verifica se foi chamado corretamente
assert result == True
mock_email.send.assert_called_once_with(
to='alice@[Link]',
subject='Welcome!',
body='Hello Alice!'
)
# ── patch — substituindo módulos/classes inteiras ─────────────
import requests
def fetch_github_user(username: str) -> dict:
resp = [Link](f'[Link]
resp.raise_for_status()
return [Link]()
@patch('[Link]') # intercepta [Link] no módulo atual
def test_fetch_github_user_success(mock_get):
# Arrange — configura resposta simulada
mock_response = Mock()
mock_response.json.return_value = {'login': 'alice', 'id': 123}
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
mock_response.raise_for_status.return_value = None
mock_get.return_value = mock_response
# Act
user = fetch_github_user('alice')
# Assert
assert user['login'] == 'alice'
mock_get.assert_called_once_with('[Link]
@patch('[Link]')
def test_fetch_github_user_not_found(mock_get):
import requests as req
mock_get.side_effect = [Link]('404 Not Found')
with [Link]([Link]):
fetch_github_user('nonexistent-user-xyz')
# ── MagicMock — suporte a magic methods ──────────────────────
def test_context_manager_mock():
mock_file = MagicMock()
mock_file.__enter__.return_value = mock_file
mock_file.__exit__.return_value = False
mock_file.read.return_value = 'file content'
with patch('[Link]', return_value=mock_file):
with open('any_file.txt') as f:
content = [Link]()
assert content == 'file content'
# ── AsyncMock — para funções async ───────────────────────────
@[Link]
async def test_async_fetch():
mock_client = AsyncMock()
mock_client.get.return_value.json.return_value = {'data': 'ok'}
with patch('[Link]', return_value=mock_client):
result = await async_fetch_data('/endpoint')
assert result == {'data': 'ok'}
6.5 pytest marks — organizando e controlando testes
import pytest
# ── Marks built-in ───────────────────────────────────────────
# @[Link] — pula sempre
@[Link](reason='Feature not implemented yet')
def test_future_feature():
...
# @[Link] — pula condicionalmente
import sys
@[Link]([Link] == 'win32', reason='Unix only')
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def test_unix_permissions():
...
# @[Link] — espera falha (teste de bug conhecido)
@[Link](reason='Known bug #123')
def test_known_bug():
assert broken_function() == 'expected'
# ── Custom marks — categorizando seus testes ─────────────────
# [Link] ou [Link]:
# [[Link].ini_options]
# markers = [
# 'unit: fast isolated tests',
# 'integration: tests with external dependencies',
# 'slow: tests that take more than 1 second',
# ]
@[Link]
def test_fast_unit_test():
assert 1 + 1 == 2
@[Link]
@[Link]
def test_database_integration():
...
# Executando apenas certos marks:
# pytest -m unit # só testes unitários
# pytest -m 'not slow' # exclui testes lentos
# pytest -m 'unit or integration'
# ── Comandos úteis do pytest ──────────────────────────────────
# pytest # roda todos os testes
# pytest -v # verbose — mostra cada teste
# pytest -x # para no primeiro erro
# pytest -k 'test_cart' # filtra por nome
# pytest --tb=short # traceback resumido
# pytest --cov=src --cov-report=html # cobertura de código
# pytest tests/test_cart.py::TestShoppingCart::test_clear # teste específico
6.6 TDD — Test Driven Development na prática
TDD é escrever o teste ANTES do código. O ciclo é Red (teste falha) → Green (código mínimo para
passar) → Refactor (melhora o código mantendo o teste verde). É uma das habilidades mais
valorizadas em empresas de produto.
# Exemplo de TDD: implementar uma classe Stack do zero
# ── PASSO 1: RED — escreva o teste que vai falhar ────────────
# (Stack ainda não existe)
def test_new_stack_is_empty():
stack = Stack()
assert stack.is_empty() == True
assert [Link]() == 0
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def test_push_adds_element():
stack = Stack()
[Link](42)
assert stack.is_empty() == False
assert [Link]() == 1
def test_pop_returns_last_pushed():
stack = Stack()
[Link](1)
[Link](2)
[Link](3)
assert [Link]() == 3
assert [Link]() == 2
assert [Link]() == 1
def test_pop_empty_stack_raises():
stack = Stack()
with [Link](IndexError, match='Stack is empty'):
[Link]()
def test_peek_does_not_remove():
stack = Stack()
[Link](99)
assert [Link]() == 99
assert [Link]() == 1 # ainda tem o elemento
# ── PASSO 2: GREEN — implementação mínima ────────────────────
class Stack:
def __init__(self):
self._data: list = []
def push(self, item) -> None:
self._data.append(item)
def pop(self):
if self.is_empty():
raise IndexError('Stack is empty')
return self._data.pop()
def peek(self):
if self.is_empty():
raise IndexError('Stack is empty')
return self._data[-1]
def is_empty(self) -> bool:
return len(self._data) == 0
def size(self) -> int:
return len(self._data)
# ── PASSO 3: REFACTOR — melhora sem quebrar testes ───────────
# Adicionar type hints, docstrings, __repr__, __len__...
# Os testes garantem que nada quebrou.
💡 TIP
Na entrevista: se pedirem para implementar algo ao vivo, pergunte 'posso começar pelos testes?'
Isso demonstra senioridade e dá tempo para pensar na interface antes de codificar.
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
SEÇÃO 7 — Performance Testing de APIs REST
Saber medir performance é essencial para um backend engineer. Esta seção cobre load testing,
benchmarking de endpoints, identificação de gargalos e ferramentas do mercado.
7.1 Conceitos de performance — o que medir
Latência (Latency) Tempo de resposta de uma requisição individual. Medido em ms. p50,
p90, p99 são as métricas padrão.
Throughput Número de requisições processadas por segundo (RPS/TPS).
Capacidade máxima do sistema.
p50 / p95 / p99 Percentis. p99 = 99% das requisições respondem abaixo desse tempo.
p99 é o mais crítico.
Concorrência Número de usuários simultâneos. A latência sobe com alta
concorrência.
Error rate % de requisições que retornam erro (5xx). Deve ser 0% sob carga
normal.
Saturation point O ponto onde aumentar carga não aumenta throughput — o sistema
está no limite.
📌 NOTA
A regra de ouro: p50 é a experiência 'mediana', mas p99 é a experiência do pior caso. Em sistemas
de produção, otimize para p99, não para p50. Um p50 de 20ms com p99 de 5000ms é um sistema
problemático.
7.2 pytest-benchmark — benchmarking de funções Python
# pip install pytest-benchmark
# benchmark é injetado automaticamente pelo pytest como fixture
def test_benchmark_sorting_algorithms(benchmark):
import random
data = [Link](range(100_000), 10_000)
# [Link]() para controle fino
result = benchmark(sorted, data) # forma simples
assert result == sorted(data)
# Comparando duas implementações
def sum_loop(n):
total = 0
for i in range(n): total += i
return total
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def sum_builtin(n):
return sum(range(n))
def test_sum_loop(benchmark):
result = benchmark(sum_loop, 10_000)
assert result == 49_995_000
def test_sum_builtin(benchmark):
result = benchmark(sum_builtin, 10_000)
assert result == 49_995_000
# Executar: pytest --benchmark-compare
# Saída mostra: min, max, mean, stddev, median, ops/s
# [Link] — mais controle
def test_pedantic(benchmark):
[Link](
target_function,
args=(arg1, arg2),
kwargs={'key': 'value'},
iterations=100, # repetições por rodada
rounds=5, # número de rodadas
warmup_rounds=2, # rodadas de aquecimento (descartadas)
)
7.3 httpx + pytest — testes de integração da API FastAPI
O FastAPI tem suporte nativo a testes via TestClient (baseado no httpx). Você testa os endpoints reais
sem precisar de um servidor rodando.
# pip install pytest httpx
from [Link] import TestClient
from fastapi import FastAPI
import pytest
# ── Setup ────────────────────────────────────────────────────
app = FastAPI()
client = TestClient(app)
@[Link](autouse=True)
def reset_db():
"""Limpa o banco antes de cada teste — garante isolamento"""
[Link]()
yield
# ── Testes de endpoints ───────────────────────────────────────
def test_create_user_returns_201():
response = [Link]('/users', json={
'name': 'Alice', 'email': 'alice@[Link]', 'age': 30
})
assert response.status_code == 201
data = [Link]()
assert data['name'] == 'Alice'
assert 'id' in data
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def test_create_user_duplicate_email_returns_409():
[Link]('/users', json={'name': 'A', 'email': 'dup@[Link]', 'age': 25})
response = [Link]('/users', json={'name': 'B', 'email': 'dup@[Link]',
'age': 30})
assert response.status_code == 409
def test_get_nonexistent_user_returns_404():
response = [Link]('/users/nonexistent-id')
assert response.status_code == 404
def test_list_users_pagination():
for i in range(15):
[Link]('/users', json={'name': f'User{i}', 'email': f'u{i}@[Link]',
'age': 20+i})
page1 = [Link]('/users?skip=0&limit=10').json()
page2 = [Link]('/users?skip=10&limit=10').json()
assert len(page1) == 10
assert len(page2) == 5
def test_update_user_partial():
r = [Link]('/users', json={'name': 'Alice', 'email': 'a@[Link]', 'age':
30})
user_id = [Link]()['id']
patch_r = [Link](f'/users/{user_id}', json={'age': 31})
assert patch_r.status_code == 200
assert patch_r.json()['age'] == 31
assert patch_r.json()['name'] == 'Alice' # não alterado
7.4 Locust — load testing de APIs em Python puro
Locust é a ferramenta de load testing mais popular no ecossistema Python. Os cenários de teste são
escritos em Python puro — sem YAML, sem XML.
# pip install locust
# arquivo: [Link]
from locust import HttpUser, task, between, events
import random
class APIUser(HttpUser):
# Simula um usuário que espera entre 1 e 3 segundos entre requests
wait_time = between(1, 3)
# Dados compartilhados entre tarefas
user_ids: list[str] = []
def on_start(self):
"""Executado uma vez quando o usuário virtual inicia"""
# Cria um usuário de teste para usar nos outros endpoints
resp = [Link]('/users', json={
'name': 'LoadTest User',
'email': f'load_{[Link](1,999999)}@[Link]',
'age': 25
})
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
if resp.status_code == 201:
self.user_id = [Link]()['id']
@task(3) # peso 3 — executado 3x mais que tarefas com peso 1
def get_user(self):
with [Link](
f'/users/{self.user_id}',
name='/users/[id]', # agrupa no relatório
catch_response=True
) as resp:
if resp.status_code == 200:
[Link]()
elif resp.status_code == 404:
[Link](f'User not found: {self.user_id}')
@task(2)
def list_users(self):
[Link]('/users?limit=10')
@task(1)
def create_and_delete_user(self):
r = [Link]('/users', json={
'name': 'Temp', 'email': f't_{[Link](1,9999999)}@[Link]',
'age': 20
})
if r.status_code == 201:
uid = [Link]()['id']
[Link](f'/users/{uid}', name='/users/[id]')
# Executar com interface web ([Link]
# locust -f [Link] --host=[Link]
# Executar headless (CI/CD):
# locust -f [Link] --host=[Link]
# --users 100 --spawn-rate 10 --run-time 60s --headless
# --csv=results
7.5 Analisando resultados e identificando gargalos
# ── Script Python para análise de resultados do Locust ──────
import pandas as pd
import statistics
# Locust gera CSV com --csv=results
# Arquivos: results_stats.csv, results_failures.csv, results_history.csv
def analyze_results(csv_path: str):
df = pd.read_csv(csv_path)
for _, row in [Link]():
if row['Name'] == 'Aggregated':
continue
print(f"Endpoint: {row['Name']}")
print(f" Requests: {row['Request Count']}")
print(f" Failures: {row['Failure Count']} ({row['Failure
Count']/row['Request Count']*100:.1f}%)")
print(f" p50: {row['50%']} ms")
print(f" p95: {row['95%']} ms")
print(f" p99: {row['99%']} ms")
print(f" Max: {row['Max Response Time']} ms")
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
print(f" RPS: {row['Requests/s']:.1f}")
print()
# ── O que procurar nos resultados ────────────────────────────
# p99 muito alto vs p50 → outliers — problema de timeout ou lock
# Error rate > 0% → bug ou saturação do sistema
# RPS cai com mais users → gargalo de concorrência (sem async?)
# Max response > 10s → provavelmente timeout de DB ou serviço externo
# ── Profiling do endpoint para identificar a causa ───────────
# No FastAPI, adicione middleware de timing:
import time
from fastapi import Request
@[Link]('http')
async def add_process_time_header(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
elapsed = time.perf_counter() - start
[Link]['X-Process-Time'] = f'{elapsed:.4f}'
if elapsed > 0.5: # log se > 500ms
[Link]('Slow request: %s %s took %.3fs',
[Link], [Link], elapsed)
return response
⚠️ ATENÇÃO
Nunca faça load test em ambiente de produção sem avisas o time e ter um plano de rollback. Use
sempre ambientes de staging ou dev para performance testing.
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
SEÇÃO 8 — FastAPI & Pydantic: Deep Dive
FastAPI é o framework backend Python mais adotado em 2025/2026. É a base de muitos produtos de
AI Engineering. Pydantic é a biblioteca de validação de dados que o FastAPI usa por baixo dos panos.
8.1 Pydantic — validação de dados com type hints
Pydantic usa type hints Python para definir schemas de dados e valida automaticamente os valores. É
muito mais poderoso que simples dataclasses.
from pydantic import BaseModel, Field, validator, root_validator, EmailStr
from pydantic import ValidationError
from typing import Optional, Annotated
from datetime import datetime
from enum import Enum
# ── Model básico ─────────────────────────────────────────────
class UserCreate(BaseModel):
name: str
email: str
age: int
# Pydantic valida AUTOMATICAMENTE na criação:
user = UserCreate(name='Alice', email='alice@[Link]', age=30) # OK
# UserCreate(name=123, email='a@[Link]', age='trinta') # Pydantic faz coerção:
# name='123', age lança ValidationError (string não vira int)
# ── Field — metadados e validação declarativa ────────────────
class Product(BaseModel):
id: int = Field(gt=0, description='Product ID')
name: str = Field(min_length=1, max_length=200)
price: float = Field(gt=0, le=99999.99)
stock: int = Field(ge=0, default=0)
description: Optional[str] = Field(default=None, max_length=2000)
tags: list[str] = Field(default_factory=list, max_length=10)
class Config:
str_strip_whitespace = True # remove espaços automaticamente
json_schema_extra = {
'example': {'id': 1, 'name': 'Widget', 'price': 9.99}
}
# ── Validators customizados ───────────────────────────────────
class UserSignup(BaseModel):
username: str
email: str
password: str
confirm_password: str
age: int
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
@validator('username')
def username_alphanumeric(cls, v):
if not [Link]():
raise ValueError('Username must be alphanumeric')
return [Link]() # normaliza
@validator('age')
def must_be_adult(cls, v):
if v < 18:
raise ValueError('Must be 18 or older')
return v
@root_validator # acessa múltiplos campos
def passwords_match(cls, values):
pw = [Link]('password')
cpw = [Link]('confirm_password')
if pw and cpw and pw != cpw:
raise ValueError('Passwords do not match')
return values
# ── Capturando erros de validação ────────────────────────────
try:
UserSignup(
username='alice 123', # espaço inválido
email='alice@[Link]',
password='secret',
confirm_password='different',
age=16
)
except ValidationError as e:
print([Link](indent=2))
# Mostra TODOS os erros de uma vez, campo a campo
for error in [Link]():
print(f" {error['loc']}: {error['msg']}")
8.2 Pydantic — serialização, nested models e herança
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
# ── Modelos aninhados ────────────────────────────────────────
class Address(BaseModel):
street: str
city: str
country: str
zip: Optional[str] = None
class Company(BaseModel):
name: str
website: Optional[str] = None
class Employee(BaseModel):
id: int
name: str
address: Address # nested model — validado automaticamente
company: Optional[Company] = None
hire_date: datetime
skills: list[str] = []
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
# Criação a partir de dict (comum ao receber JSON)
emp = Employee(**{
'id': 1, 'name': 'Alice',
'address': {'street': 'Rua X', 'city': 'SP', 'country': 'BR'},
'hire_date': '2024-01-15T09:00:00',
'skills': ['Python', 'FastAPI']
})
# Serialização
emp.model_dump() # dict Python
emp.model_dump_json() # string JSON
emp.model_dump(exclude={'id'}) # exclui campos
emp.model_dump(include={'name', 'skills'}) # só esses campos
# ── Herança de modelos — padrão request/response ─────────────
class UserBase(BaseModel):
"""Campos comuns a todos os modelos de User"""
name: str
email: str
class UserCreate(UserBase): # para POST /users
"""Campos extras apenas na criação"""
password: str
age: int
class UserUpdate(BaseModel): # para PATCH /users/{id}
"""Todos os campos opcionais — patch parcial"""
name: Optional[str] = None
email: Optional[str] = None
age: Optional[int] = None
class UserResponse(UserBase): # retornado pela API
"""Campos expostos — NUNCA inclua password aqui"""
id: int
created_at: datetime
class Config:
from_attributes = True # Pydantic V2: lê de objetos ORM
8.3 FastAPI — recursos avançados
from fastapi import FastAPI, Depends, HTTPException, status, Query, Path, Body
from [Link] import CORSMiddleware
from [Link] import JSONResponse
from pydantic import BaseModel
from typing import Annotated
app = FastAPI(
title='My API',
description='API de exemplo com FastAPI',
version='1.0.0',
docs_url='/docs', # Swagger UI automático
redoc_url='/redoc', # ReDoc automático
)
# ── CORS — essencial para frontend ───────────────────────────
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
app.add_middleware(
CORSMiddleware,
allow_origins=['[Link] '[Link]
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
# ── Path e Query parameters com validação ────────────────────
@[Link]('/users/{user_id}')
async def get_user(
user_id: Annotated[int, Path(gt=0, description='User ID')],
include_deleted: bool = Query(default=False),
):
...
@[Link]('/products')
async def search_products(
q: Annotated[str | None, Query(min_length=2)] = None,
min_price:Annotated[float, Query(ge=0)] = 0,
max_price:Annotated[float, Query(le=99999)] = 99999,
skip: int = 0,
limit: Annotated[int, Query(le=100)] = 20,
):
...
# ── Dependency Injection — o recurso mais poderoso do FastAPI ─
from [Link] import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
token = [Link]
# Validar JWT, buscar usuário no banco...
user = verify_token(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid token',
headers={'WWW-Authenticate': 'Bearer'},
)
return user
async def require_admin(
current_user: dict = Depends(get_current_user)
) -> dict:
if current_user.get('role') != 'admin':
raise HTTPException(status.HTTP_403_FORBIDDEN, 'Admin only')
return current_user
# Usando as dependências — composição limpa
@[Link]('/users/{user_id}', status_code=204)
async def delete_user(
user_id: int,
admin: dict = Depends(require_admin), # já valida token + role
):
...
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
# ── Background Tasks ─────────────────────────────────────────
from fastapi import BackgroundTasks
def send_email_background(to: str, subject: str, body: str):
"""Executada APÓS retornar a resposta — não bloqueia o cliente"""
email_service.send(to=to, subject=subject, body=body)
@[Link]('/users', status_code=201)
async def create_user(
user: UserCreate,
background_tasks: BackgroundTasks
):
new_user = save_user(user)
background_tasks.add_task( # não espera o email
send_email_background,
to=[Link],
subject='Welcome!',
body=f'Hi {[Link]}!'
)
return new_user # retorna imediatamente
8.4 FastAPI — tratamento de erros e exception handlers
from fastapi import FastAPI, Request, HTTPException
from [Link] import JSONResponse
from [Link] import RequestValidationError
import logging
logger = [Link](__name__)
# ── Exception handler global — captura erros não tratados ────
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
[Link]('Unhandled error on %s %s', [Link], [Link])
return JSONResponse(
status_code=500,
content={'detail': 'Internal server error', 'type': type(exc).__name__}
)
# ── Customizar erros de validação do Pydantic ────────────────
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc:
RequestValidationError):
errors = []
for error in [Link]():
field = ' -> '.join(str(loc) for loc in error['loc'])
[Link]({'field': field, 'message': error['msg']})
return JSONResponse(
status_code=422,
content={'detail': 'Validation error', 'errors': errors}
)
# ── Exceções de negócio customizadas ────────────────────────
class AppException(Exception):
def __init__(self, message: str, status_code: int = 400):
[Link] = message
self.status_code = status_code
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
class UserNotFoundError(AppException):
def __init__(self, user_id: int):
super().__init__(f'User {user_id} not found', status_code=404)
class DuplicateEmailError(AppException):
def __init__(self, email: str):
super().__init__(f'Email {email!r} already registered', status_code=409)
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
return JSONResponse(
status_code=exc.status_code,
content={'detail': [Link]}
)
# Uso nos endpoints — limpo, sem HTTPException espalhado
@[Link]('/users/{user_id}')
async def get_user(user_id: int):
user = [Link](user_id)
if not user:
raise UserNotFoundError(user_id) # limpo!
return user
8.5 FastAPI — estrutura de projeto real
Em projetos reais, o FastAPI é organizado em módulos. Essa estrutura é o que você verá em
empresas como a Factored.
# Estrutura recomendada para projetos FastAPI
# .
# ├── app/
# │ ├── [Link] # Cria o app, registra routers
# │ ├── [Link] # Settings via pydantic-settings
# │ ├── [Link] # Deps compartilhadas (auth, db)
# │ ├── [Link] # Exceções customizadas
# │ ├── api/
# │ │ ├── v1/
# │ │ │ ├── [Link] # Router de users
# │ │ │ ├── [Link] # Router de products
# │ │ │ └── __init__.py
# │ │ └── __init__.py
# │ ├── models/
# │ │ ├── [Link] # Pydantic schemas
# │ │ └── [Link]
# │ ├── services/
# │ │ ├── user_service.py # Lógica de negócio
# │ │ └── email_service.py
# │ └── repositories/
# │ └── user_repo.py # Acesso a dados
# ├── tests/
# │ ├── [Link]
# │ ├── test_users.py
# │ └── test_products.py
# ├── [Link]
# └── Dockerfile
# app/[Link]
from fastapi import FastAPI
from [Link].v1 import users, products
Boa sorte na entrevista! Página
Python Interview Study Guide — Factored AI
def create_app() -> FastAPI:
app = FastAPI(title='My API')
app.include_router([Link], prefix='/api/v1/users', tags=['users'])
app.include_router([Link], prefix='/api/v1/products',
tags=['products'])
return app
app = create_app()
# app/api/v1/[Link]
from fastapi import APIRouter, Depends
from [Link] import UserCreate, UserResponse
from [Link].user_service import UserService
from [Link] import get_user_service
router = APIRouter()
@[Link]('', response_model=UserResponse, status_code=201)
async def create_user(
payload: UserCreate,
service: UserService = Depends(get_user_service),
):
return await [Link](payload)
# app/[Link] — configuração via variáveis de ambiente
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = 'My API'
db_url: str = 'sqlite:///./[Link]'
secret_key: str
debug: bool = False
class Config:
env_file = '.env'
settings = Settings() # lê automaticamente do .env ou env vars
💡 TIP
Na entrevista: se perguntarem 'como você estruturaria uma API em Python?', mencione separação
em routers, services e repositories, uso de Pydantic para validação, dependency injection para auth
e conexões, e exception handlers globais. Isso demonstra experiência com projetos reais.
Boa sorte na entrevista! Página