20. Parâmetros e Argumentos de Funções
No Capítulo 19, aprendemos como definir e chamar funções com parâmetros básicos. Agora vamos explorar em profundidade o sistema flexível de parâmetros e argumentos do Python. Entender esses mecanismos permite que você escreva funções que são ao mesmo tempo poderosas e fáceis de usar.
20.1) Argumentos Posicionais e Nomeados
Quando você chama uma função, você pode passar argumentos de duas formas fundamentais: por posição ou por nome (keyword).
20.1.1) Argumentos Posicionais
Argumentos posicionais são associados aos parâmetros com base na ordem. O primeiro argumento vai para o primeiro parâmetro, o segundo para o segundo parâmetro e assim por diante.
def calculate_discount(price, discount_percent):
"""Calculate the final price after applying a discount."""
discount_amount = price * (discount_percent / 100)
final_price = price - discount_amount
return final_price
# Passando argumentos por posição
result = calculate_discount(100, 20)
print(result)Output:
80.0Neste exemplo, 100 é atribuído a price e 20 a discount_percent puramente com base nas posições deles na chamada da função.
A ordem é criticamente importante com argumentos posicionais:
# Exemplo: queremos calcular um item de $100 com 20% de desconto
# Ordem correta: price primeiro, depois discount
print(calculate_discount(100, 20))
# Ordem errada: discount primeiro, depois price
print(calculate_discount(20, 100))Output:
80.0
-16.0Quando você troca os argumentos, o Python não sabe que você cometeu um erro — ele simplesmente os atribui em ordem. Isso produz um resultado matematicamente válido, mas logicamente incorreto (um preço negativo!).
20.1.2) Argumentos Nomeados
Argumentos nomeados especificam explicitamente qual parâmetro recebe qual valor usando o nome do parâmetro seguido de um sinal de igual e o valor. Isso torna seu código mais legível e protege contra erros de ordem.
def create_user_profile(username, email, age):
"""Create a user profile with the given information."""
profile = f"User: {username}\nEmail: {email}\nAge: {age}"
return profile
# Usando argumentos nomeados
profile = create_user_profile(username="alice_smith", email="alice@example.com", age=28)
print(profile)Output:
User: alice_smith
Email: alice@example.com
Age: 28Com argumentos nomeados, a ordem não importa:
# Mesmo resultado, ordem diferente
profile1 = create_user_profile(username="bob", email="bob@example.com", age=35)
profile2 = create_user_profile(age=35, username="bob", email="bob@example.com")
profile3 = create_user_profile(email="bob@example.com", age=35, username="bob")
# Os três produzem resultados idênticos
print(profile1 == profile2 == profile3)Output:
TrueEssa flexibilidade é particularmente valiosa quando uma função tem muitos parâmetros, pois torna fácil ver qual valor corresponde a qual parâmetro.
20.1.3) Misturando Argumentos Posicionais e Nomeados
Você pode combinar os dois estilos em uma única chamada de função, mas há uma regra importante: argumentos posicionais devem vir antes de argumentos nomeados.
def format_address(street, city, state, zip_code):
"""Format a mailing address."""
return f"{street}\n{city}, {state} {zip_code}"
# Válido: argumentos posicionais primeiro, depois argumentos nomeados
address = format_address("123 Main St", "Springfield", state="IL", zip_code="62701")
print(address)Output:
123 Main St
Springfield, IL 62701Aqui, "123 Main St" e "Springfield" são posicionais (atribuídos a street e city), enquanto state e zip_code são especificados pelo nome.
Tentar colocar argumentos posicionais depois de argumentos nomeados causa um erro:
# Inválido: argumento posicional após argumento nomeado
# address = format_address(street="123 Main St", "Springfield", state="IL", zip_code="62701")
# SyntaxError: positional argument follows keyword argumentO Python impõe essa regra porque, uma vez que você começa a usar argumentos nomeados, fica ambíguo qual parâmetro posicional um argumento sem nome subsequente deveria preencher.
20.1.4) Quando Usar Cada Estilo
Use argumentos posicionais quando:
- A função tem poucos parâmetros (tipicamente 1-3)
- A ordem dos parâmetros é óbvia e intuitiva
- A função é usada com frequência e a ordem é bem conhecida
# Óbvio e conciso
print(len("hello"))
result = max(10, 20, 5)Use argumentos nomeados quando:
- A função tem muitos parâmetros
- Os significados dos parâmetros não são imediatamente óbvios
- Você quer pular alguns parâmetros que têm valores padrão (coberto a seguir)
- Você quer tornar seu código autoexplicativo
# Claro e explícito
user = create_user_profile(username="charlie", email="charlie@example.com", age=42)20.2) Valores Padrão de Parâmetros
Funções podem especificar valores padrão para parâmetros. Quando quem chama não fornece um argumento para um parâmetro com padrão, o Python usa o valor padrão no lugar.
20.2.1) Definindo Parâmetros com Padrões
Valores padrão são especificados na definição da função usando o operador de atribuição:
def greet_user(name, greeting="Hello"):
"""Greet a user with a customizable greeting."""
return f"{greeting}, {name}!"
# Usando a saudação padrão
print(greet_user("Alice"))
# Fornecendo uma saudação personalizada
print(greet_user("Bob", "Good morning"))
print(greet_user("Carol", greeting="Hi"))Output:
Hello, Alice!
Good morning, Bob!
Hi, Carol!O parâmetro greeting tem um valor padrão de "Hello". Quando você chama greet_user("Alice"), o Python usa esse padrão. Quando você fornece um segundo argumento, ele sobrescreve o padrão.
20.2.2) Parâmetros com Padrões Devem Vir Depois de Parâmetros Obrigatórios
O Python exige que parâmetros com valores padrão apareçam depois de todos os parâmetros sem padrão. Essa regra evita ambiguidade sobre quais argumentos correspondem a quais parâmetros.
# Correto: parâmetros obrigatórios primeiro, depois padrões
def create_product(name, price, category="General", in_stock=True):
"""Create a product record."""
return {
"name": name,
"price": price,
"category": category,
"in_stock": in_stock
}
product = create_product("Laptop", 999.99)
print(product)Output:
{'name': 'Laptop', 'price': 999.99, 'category': 'General', 'in_stock': True}Tentar colocar um parâmetro obrigatório depois de um com padrão causa um erro de sintaxe:
# Inválido: parâmetro obrigatório após parâmetro padrão
# def invalid_function(name="Unknown", age):
# return f"{name} is {age} years old"
# SyntaxError: non-default argument follows default argumentIsso faz sentido: se name tem um padrão mas age não tem, como o Python saberia se invalid_function(25) significa name=25 com age faltando, ou age=25 com name usando o padrão? A regra elimina essa ambiguidade.
20.2.3) Usos Práticos de Parâmetros Padrão
Parâmetros padrão são excelentes para funções em que certos argumentos raramente mudam:
def calculate_shipping(weight, distance, express=False):
"""Calculate shipping cost based on weight and distance."""
base_rate = 0.50 * weight + 0.10 * distance
if express:
base_rate *= 2 # Frete expresso custa o dobro
return round(base_rate, 2)
# A maioria dos envios é padrão
standard_cost = calculate_shipping(5, 100)
print(f"Standard: ${standard_cost}")
# Ocasionalmente, alguém precisa de expresso
express_cost = calculate_shipping(5, 100, express=True)
print(f"Express: ${express_cost}")Output:
Standard: $12.5
Express: $25.0Esse design deixa o caso mais comum (frete padrão) simples de chamar, enquanto ainda suporta o caso menos comum (expresso) quando necessário.
20.2.4) Múltiplos Padrões e Sobrescrita Seletiva
Quando uma função tem múltiplos parâmetros com padrões, você pode sobrescrever qualquer combinação deles usando argumentos nomeados:
def format_currency(amount, currency="USD", show_symbol=True, decimal_places=2):
"""Format a number as currency."""
symbols = {"USD": "$", "EUR": "€", "GBP": "£", "JPY": "¥"}
formatted = f"{amount:.{decimal_places}f}"
if show_symbol and currency in symbols:
formatted = f"{symbols[currency]}{formatted}"
return formatted
# Usando todos os padrões
print(format_currency(42.5))
# Sobrescrever apenas a moeda
print(format_currency(42.5, currency="EUR"))
# Sobrescrever múltiplos padrões
print(format_currency(42.5, currency="JPY", decimal_places=0))Output:
$42.50
€42.50
¥42Essa flexibilidade permite que quem chama personalize exatamente o que precisa, mantendo a chamada da função concisa.
20.3) Listas de Argumentos de Comprimento Variável com *args
Às vezes você quer que uma função aceite qualquer número de argumentos sem saber de antemão quantos serão. O Python fornece *args para esse propósito.
20.3.1) Entendendo *args
A sintaxe *args em uma lista de parâmetros coleta todos os argumentos posicionais extras em uma tupla. O nome args é uma convenção (abreviação de "arguments"), mas você pode usar qualquer nome de parâmetro válido após o asterisco.
def calculate_total(*numbers):
"""Calculate the sum of any number of values."""
total = 0
for num in numbers:
total += num
return total
# Funciona com qualquer número de argumentos
print(calculate_total(10))
print(calculate_total(10, 20))
print(calculate_total(10, 20, 30, 40))
print(calculate_total())Output:
10
30
100
0Dentro da função, numbers é uma tupla contendo todos os argumentos posicionais passados para a função. Quando nenhum argumento é fornecido, é uma tupla vazia.
20.3.2) Combinando Parâmetros Regulares com *args
Você pode ter parâmetros regulares antes de *args. Os parâmetros regulares consomem os primeiros argumentos, e *args coleta o restante:
def create_team(team_name, *members):
"""Create a team with a name and any number of members."""
member_list = ", ".join(members)
return f"Team {team_name}: {member_list}"
# Primeiro argumento vai para team_name, o resto vai para members
print(create_team("Alpha", "Alice", "Bob"))
print(create_team("Beta", "Carol"))
print(create_team("Gamma", "Dave", "Eve", "Frank", "Grace"))Output:
Team Alpha: Alice, Bob
Team Beta: Carol
Team Gamma: Dave, Eve, Frank, GraceO primeiro argumento ("Alpha", "Beta" ou "Gamma") é atribuído a team_name, e todos os argumentos restantes são coletados na tupla members.
20.4) Parâmetros Somente-Nomeados e **kwargs
O Python fornece dois mecanismos adicionais para lidar com argumentos: parâmetros somente-nomeados e **kwargs para coletar argumentos nomeados arbitrários.
20.4.1) Parâmetros Somente-Nomeados
Parâmetros somente-nomeados devem ser especificados usando argumentos nomeados — eles não podem ser passados por posição. Você os cria colocando-os depois de um * ou depois de *args na lista de parâmetros.
def create_account(username, *, email, age):
"""Create an account. Email and age must be specified by name."""
return {
"username": username,
"email": email,
"age": age
}
# Correto: email e age especificados por nome
account = create_account("alice", email="alice@example.com", age=28)
print(account)
# Inválido: tentando passar email e age por posição
# account = create_account("bob", "bob@example.com", 30)
# TypeError: create_account() takes 1 positional argument but 3 were givenOutput:
{'username': 'alice', 'email': 'alice@example.com', 'age': 28}O * na lista de parâmetros atua como um separador. Tudo após ele deve ser passado como argumento nomeado. Isso é útil quando você quer forçar quem chama a ser explícito sobre certos parâmetros, tornando o código mais legível e menos propenso a erros.
Você também pode combinar parâmetros regulares, *args e parâmetros somente-nomeados:
def log_event(event_type, *details, severity="INFO", timestamp=None):
"""Log an event with optional details and metadata."""
# Vamos aprender sobre o módulo datetime em detalhes no Capítulo 39,
# mas por enquanto, apenas saiba que estas linhas pegam a hora atual
# e a formatam como uma string de timestamp
from datetime import datetime
if timestamp is None:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
details_str = " | ".join(details)
return f"[{timestamp}] {severity}: {event_type} - {details_str}"
# event_type é posicional, details são coletados por *details,
# severity e timestamp são somente-nomeados
print(log_event("Login", "User: alice", "IP: 192.168.1.1"))
print(log_event("Error", "Database connection failed", severity="ERROR"))Output (timestamp will vary based on when you run the code):
[2025-12-18 19:29:16] INFO: Login - User: alice | IP: 192.168.1.1
[2025-12-18 19:29:16] ERROR: Error - Database connection failed20.4.2) Entendendo **kwargs
A sintaxe **kwargs coleta todos os argumentos nomeados extras em um dicionário. Assim como args, o nome kwargs é convencional (abreviação de "keyword arguments"), mas você pode usar qualquer nome válido após o asterisco duplo.
def create_product(**attributes):
"""Create a product with any number of attributes."""
product = {}
for key, value in attributes.items():
product[key] = value
return product
# Passe quaisquer argumentos nomeados que você quiser
laptop = create_product(name="Laptop", price=999.99, brand="TechCorp", in_stock=True)
print(laptop)
phone = create_product(name="Phone", price=699.99, color="Black")
print(phone)Output:
{'name': 'Laptop', 'price': 999.99, 'brand': 'TechCorp', 'in_stock': True}
{'name': 'Phone', 'price': 699.99, 'color': 'Black'}Dentro da função, attributes é um dicionário em que as chaves são os nomes dos parâmetros e os valores são os argumentos passados.
20.4.3) Combinando Parâmetros Regulares, *args e **kwargs
Você pode usar todos esses mecanismos juntos, mas eles devem aparecer em uma ordem específica:
- Parâmetros posicionais regulares
*args(se presente)- Parâmetros somente-nomeados (se presentes)
**kwargs(se presente)
def complex_function(required, *args, keyword_only, **kwargs):
"""Demonstrate all parameter types together."""
print(f"Required: {required}")
print(f"Args: {args}")
print(f"Keyword-only: {keyword_only}")
print(f"Kwargs: {kwargs}")
complex_function(
"value1", # required
"value2", "value3", # args
keyword_only="kw", # keyword_only
extra1="e1", # kwargs
extra2="e2" # kwargs
)Output:
Required: value1
Args: ('value2', 'value3')
Keyword-only: kw
Kwargs: {'extra1': 'e1', 'extra2': 'e2'}Essa flexibilidade é poderosa, mas deve ser usada com critério. A maioria das funções não precisa de todos esses mecanismos.
20.4.4) Caso de Uso Prático: Funções de Configuração
Um uso comum para **kwargs é criar funções que aceitam opções de configuração:
def connect_to_database(host, port, **options):
"""Connect to a database with flexible configuration options."""
connection_string = f"Connecting to {host}:{port}"
# Processa quaisquer opções adicionais
if options.get("ssl"):
connection_string += " with SSL"
if options.get("timeout"):
connection_string += f" (timeout: {options['timeout']}s)"
if options.get("pool_size"):
connection_string += f" (pool size: {options['pool_size']})"
return connection_string
# Conexão básica
print(connect_to_database("localhost", 5432))
# Com SSL
print(connect_to_database("db.example.com", 5432, ssl=True))
# Com múltiplas opções
print(connect_to_database("db.example.com", 5432, ssl=True, timeout=30, pool_size=10))Output:
Connecting to localhost:5432
Connecting to db.example.com:5432 with SSL
Connecting to db.example.com:5432 with SSL (timeout: 30s) (pool size: 10)Esse padrão permite que a função aceite qualquer número de parâmetros opcionais de configuração sem defini-los todos explicitamente na lista de parâmetros.
20.5) Desempacotamento de Argumentos ao Chamar Funções
Assim como *args e **kwargs coletam argumentos ao definir funções, você pode usar * e ** para desempacotar coleções ao chamar funções.
20.5.1) Desempacotando Sequências com *
O operador * desempacota uma sequência (list, tuple, etc.) em argumentos posicionais separados:
def calculate_rectangle_area(width, height):
"""Calculate the area of a rectangle."""
return width * height
# Em vez de passar argumentos individualmente
dimensions = [5, 10]
area = calculate_rectangle_area(dimensions[0], dimensions[1])
print(area)
# Desempacote a lista diretamente
area = calculate_rectangle_area(*dimensions)
print(area)Output:
50
50Quando você escreve *dimensions, o Python desempacota a lista [5, 10] em dois argumentos separados, como se você tivesse escrito calculate_rectangle_area(5, 10).
Isso funciona com qualquer iterável:
def format_name(first, middle, last):
"""Format a full name."""
return f"{first} {middle} {last}"
# Desempacotando uma tupla
name_tuple = ("John", "Q", "Public")
print(format_name(*name_tuple))
# Desempacotando uma lista
name_list = ["Jane", "M", "Doe"]
print(format_name(*name_list))
# Até desempacotar uma string (cada caractere vira um argumento)
# Isso só funciona se a função espera o número certo de argumentos
def show_first_three(a, b, c):
return f"{a}, {b}, {c}"
print(show_first_three(*"ABC"))Output:
John Q Public
Jane M Doe
A, B, C20.5.2) Desempacotando Dicionários com **
O operador ** desempacota um dicionário em argumentos nomeados:
def create_user(username, email, age):
"""Create a user profile."""
return f"User: {username}, Email: {email}, Age: {age}"
# Dicionário com chaves correspondendo aos nomes dos parâmetros
user_data = {
"username": "alice",
"email": "alice@example.com",
"age": 28
}
# Desempacote o dicionário
profile = create_user(**user_data)
print(profile)Output:
User: alice, Email: alice@example.com, Age: 28Quando você escreve **user_data, o Python desempacota o dicionário em argumentos nomeados, equivalente a:
create_user(username="alice", email="alice@example.com", age=28)As chaves do dicionário devem corresponder aos nomes dos parâmetros da função, ou você vai receber um erro:
# Inválido: a chave do dicionário não corresponde ao nome do parâmetro
invalid_data = {"name": "bob", "email": "bob@example.com", "age": 30}
# profile = create_user(**invalid_data)
# TypeError: create_user() got an unexpected keyword argument 'name'20.5.3) Combinando Desempacotamento com Argumentos Regulares
Você pode misturar argumentos desempacotados com argumentos regulares:
def calculate_total(base_price, tax_rate, discount):
"""Calculate total price after tax and discount."""
subtotal = base_price * (1 + tax_rate)
total = subtotal * (1 - discount)
return round(total, 2)
# Alguns argumentos regulares, outros desempacotados
pricing = [0.08, 0.10] # tax_rate e discount
total = calculate_total(100, *pricing)
print(total)Output:
97.2Você também pode desempacotar múltiplas coleções em uma única chamada:
def create_full_address(street, city, state, zip_code, country):
"""Create a complete address."""
return f"{street}, {city}, {state} {zip_code}, {country}"
street_address = ["123 Main St", "Springfield"]
location_details = ["IL", "62701", "USA"]
address = create_full_address(*street_address, *location_details)
print(address)Output:
123 Main St, Springfield, IL 62701, USA20.5.4) Exemplo Prático: Chamadas de Função Flexíveis
O desempacotamento é particularmente útil ao trabalhar com dados vindos de fontes externas:
def send_email(recipient, subject, body, cc=None, bcc=None):
"""Send an email with optional CC and BCC."""
message = f"To: {recipient}\nSubject: {subject}\n\n{body}"
if cc:
message += f"\nCC: {cc}"
if bcc:
message += f"\nBCC: {bcc}"
return message
# Dados de email vindos de um arquivo de configuração ou banco de dados
email_config = {
"recipient": "user@example.com",
"subject": "Welcome",
"body": "Thank you for signing up!",
"cc": "manager@example.com"
}
# Desempacote a configuração diretamente
result = send_email(**email_config)
print(result)Output:
To: user@example.com
Subject: Welcome
Thank you for signing up!
CC: manager@example.comEsse padrão facilita passar argumentos de funções como estruturas de dados, o que é comum ao construir APIs ou processar arquivos de configuração.
20.6) A Armadilha de Argumentos Padrão Mutáveis (Por Que Padrões de Lista Persistem)
Uma das armadilhas mais notórias do Python envolve usar objetos mutáveis (como listas ou dicionários) como valores padrão de parâmetros. Entender esse problema é crucial para escrever funções corretas.
20.6.1) O Problema: Padrões Mutáveis Compartilhados
Considere esta função aparentemente inocente:
def add_student(name, grades=[]):
"""Add a student with their grades."""
grades.append(name)
return grades
# Primeira chamada
students1 = add_student("Alice")
print(students1)
# Segunda chamada - esperando uma lista nova
students2 = add_student("Bob")
print(students2)
# Terceira chamada
students3 = add_student("Carol")
print(students3)Output:
['Alice']
['Alice', 'Bob']
['Alice', 'Bob', 'Carol']Esse comportamento surpreende muitos programadores. Cada chamada de add_student() sem fornecer um argumento grades usa a mesma lista, não uma nova. A lista persiste entre chamadas de função, acumulando valores.
20.6.2) Por Que Isso Acontece: Valores Padrão São Criados Uma Vez
A chave para entender esse comportamento é saber quando valores padrão são criados. O Python avalia valores padrão de parâmetros uma vez, quando a função é definida, não a cada vez que a função é chamada.
def demonstrate_default_creation():
"""Show when defaults are created."""
print("Function defined!")
def use_default(value=demonstrate_default_creation()):
"""Use a default that calls a function."""
return value
# A mensagem imprime quando a função é DEFINIDA, não chamadaOutput:
Function defined!Quando o Python encontra a linha def use_default, ele avalia o parâmetro padrão value=demonstrate_default_creation(). Isso chama demonstrate_default_creation(), que imprime "Function defined!" imediatamente. Chamadas posteriores de use_default() não avaliam o padrão novamente, então nada adicional é impresso.
Quando o Python encontra def add_student(name, grades=[]):, ele cria uma lista vazia e a armazena como o valor padrão de grades. Cada chamada subsequente que não fornece um argumento grades usa essa mesma lista.
Aqui vai uma demonstração mais clara usando identidade de objeto:
def show_list_identity(items=[]):
"""Show that the same list object is reused."""
print(f"List ID: {id(items)}")
items.append("item")
return items
# Cada chamada usa o mesmo objeto de lista (mesmo ID)
show_list_identity()
show_list_identity()
show_list_identity()Output:
List ID: 140234567890123
List ID: 140234567890123
List ID: 140234567890123Os números exatos do ID vão variar no seu sistema, mas repare que todas as três chamadas mostram o mesmo valor de ID, provando que elas estão usando o mesmo objeto de lista. A função id() retorna um identificador único para cada objeto na memória — quando os IDs correspondem, é o mesmo objeto.
20.6.3) O Padrão Correto: Use None como Padrão
A solução padrão é usar None como padrão e criar um novo objeto mutável dentro da função:
def add_student_correct(name, grades=None):
"""Add a student with their grades (correct version)."""
if grades is None:
grades = [] # Cria uma NOVA lista a cada vez
grades.append(name)
return grades
# Agora cada chamada recebe sua própria lista
students1 = add_student_correct("Alice")
print(students1)
students2 = add_student_correct("Bob")
print(students2)
students3 = add_student_correct("Carol")
print(students3)Output:
['Alice']
['Bob']
['Carol']Esse padrão funciona porque None é imutável e uma nova lista é criada dentro do corpo da função a cada vez que grades é None.
20.6.4) O Mesmo Problema com Dicionários
Esse problema afeta todos os tipos mutáveis, não apenas listas:
# WRONG: Dictionary default
def create_config_wrong(key, value, config={}):
"""Create a configuration (BUGGY VERSION)."""
config[key] = value
return config
config1 = create_config_wrong("theme", "dark")
print(config1)
config2 = create_config_wrong("language", "en")
print(config2)
print("---")
# CORRECT: None as default
def create_config_correct(key, value, config=None):
"""Create a configuration (CORRECT VERSION)."""
if config is None:
config = {}
config[key] = value
return config
config1 = create_config_correct("theme", "dark")
print(config1)
config2 = create_config_correct("language", "en")
print(config2)Output:
{'theme': 'dark'}
{'theme': 'dark', 'language': 'en'}
---
{'theme': 'dark'}
{'language': 'en'}20.6.5) Resumo: A Regra de Ouro
Nunca use objetos mutáveis (listas, dicionários, conjuntos) como valores padrão de parâmetros. Sempre use None e crie o objeto mutável dentro da função:
# ❌ WRONG
def function(items=[]):
pass
# ✅ CORRECT
def function(items=None):
if items is None:
items = []
# Agora use items com segurançaEsse padrão garante que cada chamada de função receba seu próprio objeto mutável independente, evitando bugs misteriosos em que dados vazam entre chamadas.
Neste capítulo, exploramos em profundidade o sistema flexível de parâmetros e argumentos do Python. Você aprendeu a usar argumentos posicionais e nomeados, fornecer valores padrão, lidar com números variáveis de argumentos com *args e **kwargs, desempacotar coleções ao chamar funções e evitar a armadilha de argumentos padrão mutáveis.
Esses mecanismos te dão ferramentas poderosas para desenhar interfaces de funções que sejam ao mesmo tempo flexíveis e fáceis de usar. À medida que você escrever mais funções, você vai desenvolver intuição sobre quais padrões de parâmetros se encaixam melhor em situações diferentes. O segredo é equilibrar flexibilidade com clareza — torne suas funções fáceis de chamar corretamente e difíceis de chamar incorretamente.