Python *args e **kwargs Explicados: O Guia Completo
Updated on
Todo desenvolvedor Python encontra essa barreira em algum momento. Você abre o código-fonte de uma biblioteca, o pull request de um colega ou um projeto open-source, e vê assinaturas de funções repletas de *args e **kwargs. O que os asteriscos significam? Por que há um e dois deles? Quando você deve usar qual? Errar eles produz mensagens confusas de TypeError como "takes 2 positional arguments but 5 were given" ou "got an unexpected keyword argument", e de repente uma chamada de função simples se transforma em uma sessão de debugging.
O problema piora quando você precisa escrever funções flexíveis você mesmo. Codificar cada parâmetro torna sua API rígida. Aceitar muitos parâmetros nomeados torna a assinatura da função ilegível. Você precisa de uma maneira de escrever funções que aceitem um número variável de argumentos sem sacrificar clareza.
Python resolve isso com duas características especiais de sintaxe: *args para argumentos posicionais variáveis e **kwargs para argumentos de palavra-chave variáveis. Este guia explica ambos desde o básico, cobre os operadores de desempacotamento, passa por padrões do mundo real e ajuda você a evitar os erros mais comuns.
O Que São *args e **kwargs?
Em Python, *args e **kwargs são convenções para aceitar um número variável de argumentos em definições de função.
*argscoleta argumentos posicionais extras em uma tupla.**kwargscoleta argumentos de palavra-chave extras em um dicionário.
Os nomes args e kwargs são convenções, não requisitos. A mágica vem dos prefixos * e **, não dos nomes em si. Você poderia escrever *values e **options e eles funcionariam de forma idêntica.
Aqui está a demonstração mais simples:
def show_args(*args, **kwargs):
print(f"args = {args}")
print(f"kwargs = {kwargs}")
show_args(1, 2, 3, name="Alice", age=30)
# args = (1, 2, 3)
# kwargs = {'name': 'Alice', 'age': 30}Argumentos posicionais (valores passados sem nomes) caem em args como uma tupla. Argumentos de palavra-chave (valores passados com sintaxe key=value) caem em kwargs como um dicionário. Esse é todo o conceito central.
Entendendo *args: Argumentos Posicionais Variáveis
O asterisco simples * antes de um nome de parâmetro diz ao Python para empacotar todos os argumentos posicionais restantes em uma tupla. Isso permite que sua função aceite qualquer número de valores posicionais.
Sintaxe Básica e Uso
def add_all(*args):
"""Soma qualquer número de valores."""
total = 0
for num in args:
total += num
return total
print(add_all(1, 2)) # 3
print(add_all(1, 2, 3, 4, 5)) # 15
print(add_all(10)) # 10
print(add_all()) # 0Dentro da função, args é uma tupla Python regular. Você pode iterar sobre ela, indexar nela, verificar seu comprimento e passá-la para outras funções.
def describe_args(*args):
print(f"Type: {type(args)}")
print(f"Length: {len(args)}")
print(f"First element: {args[0] if args else 'N/A'}")
print(f"Contents: {args}")
describe_args("hello", 42, True)
# Type: <class 'tuple'>
# Length: 3
# First element: hello
# Contents: ('hello', 42, True)Combinando Parâmetros Regulares com *args
Você pode misturar parâmetros padrão com *args. Todos os parâmetros posicionais regulares são preenchidos primeiro, e quaisquer argumentos posicionais restantes vão para *args:
def log_message(level, *args):
"""Registra uma mensagem com um nível de severidade."""
message = " ".join(str(a) for a in args)
print(f"[{level.upper()}] {message}")
log_message("info", "Server started on port", 8080)
# [INFO] Server started on port 8080
log_message("error", "Connection failed:", "timeout after", 30, "seconds")
# [ERROR] Connection failed: timeout after 30 secondsExemplo Prático: Uma Função de Média Flexível
def average(*values):
"""Calcula a média de qualquer número de valores."""
if not values:
raise ValueError("average() requires at least one argument")
return sum(values) / len(values)
print(average(85, 90, 78)) # 84.33333333333333
print(average(100)) # 100.0
print(average(72, 88, 95, 67, 91)) # 82.6Exemplo Prático: Auxiliar de Formatação de String
def build_path(*segments):
"""Junta segmentos de caminho com barras, removendo extras."""
cleaned = [seg.strip("/") for seg in segments if seg]
return "/" + "/".join(cleaned)
print(build_path("api", "v2", "users", "123"))
# /api/v2/users/123
print(build_path("/data/", "/reports/", "2026/", "sales.csv"))
# /data/reports/2026/sales.csvEntendendo **kwargs: Argumentos de Palavra-Chave Variáveis
O asterisco duplo ** antes de um nome de parâmetro diz ao Python para empacotar todos os argumentos de palavra-chave restantes em um dicionário. Isso permite que sua função aceite qualquer número de valores nomeados.
Sintaxe Básica e Uso
def print_info(**kwargs):
"""Imprime pares chave-valor de forma formatada."""
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Alice", age=30, city="Seattle")
# name: Alice
# age: 30
# city: SeattleDentro da função, kwargs é um dicionário Python padrão. Você pode usar .get(), .keys(), .values(), .items() e qualquer outro método de dict.
def describe_kwargs(**kwargs):
print(f"Type: {type(kwargs)}")
print(f"Keys: {list(kwargs.keys())}")
print(f"Values: {list(kwargs.values())}")
describe_kwargs(x=10, y=20, z=30)
# Type: <class 'dict'>
# Keys: ['x', 'y', 'z']
# Values: [10, 20, 30]Exemplo Prático: Construtor de Configuração
def create_connection(host, port, **kwargs):
"""Cria uma conexão de banco de dados com configurações opcionais."""
config = {
"host": host,
"port": port,
"timeout": kwargs.get("timeout", 30),
"retries": kwargs.get("retries", 3),
"ssl": kwargs.get("ssl", True),
"pool_size": kwargs.get("pool_size", 5),
}
# Adiciona quaisquer configurações extras fornecidas pelo chamador
for key, value in kwargs.items():
if key not in config:
config[key] = value
return config
# Uso básico
basic = create_connection("localhost", 5432)
print(basic)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'retries': 3, 'ssl': True, 'pool_size': 5}
# Com opções personalizadas
custom = create_connection(
"db.example.com", 5432,
timeout=60,
ssl=False,
application_name="my_app"
)
print(custom)
# {'host': 'db.example.com', 'port': 5432, 'timeout': 60, 'retries': 3, 'ssl': False, 'pool_size': 5, 'application_name': 'my_app'}Exemplo Prático: Construtor de Tags HTML
def html_tag(tag, content="", **attributes):
"""Gera uma tag HTML com atributos opcionais."""
attr_str = ""
for key, value in attributes.items():
# Converte nomenclatura Python para HTML (class_ -> class)
html_key = key.rstrip("_")
attr_str += f' {html_key}="{value}"'
if content:
return f"<{tag}{attr_str}>{content}</{tag}>"
return f"<{tag}{attr_str} />"
print(html_tag("a", "Click here", href="https://example.com", class_="btn"))
# <a href="https://example.com" class="btn">Click here</a>
print(html_tag("img", src="photo.jpg", alt="A photo", width="200"))
# <img src="photo.jpg" alt="A photo" width="200" />
print(html_tag("p", "Hello world", id="intro", style="color: blue"))
# <p id="intro" style="color: blue">Hello world</p>Usando *args e **kwargs Juntos
Você pode usar ambos na mesma definição de função para aceitar qualquer combinação de argumentos posicionais e de palavra-chave. A ordem dos parâmetros segue regras estritas.
Regras de Ordenação de Parâmetros
Python impõe exatamente esta ordem em assinaturas de função:
- Parâmetros posicionais regulares
*args(variável posicional)- Parâmetros apenas de palavra-chave (após
*args) **kwargs(variável de palavra-chave)
def example(a, b, *args, option=True, **kwargs):
print(f"a = {a}")
print(f"b = {b}")
print(f"args = {args}")
print(f"option = {option}")
print(f"kwargs = {kwargs}")
example(1, 2, 3, 4, 5, option=False, color="red", size=10)
# a = 1
# b = 2
# args = (3, 4, 5)
# option = False
# kwargs = {'color': 'red', 'size': 10}Aqui está um resumo dos tipos de parâmetros e sua ordem:
| Posição | Tipo | Sintaxe | Exemplo | Descrição |
|---|---|---|---|---|
| 1º | Posicional | param | a, b | Obrigatório, preenchido por posição |
| 2º | Padrão | param=value | c=10 | Opcional, preenchido por posição ou nome |
| 3º | Posicional variável | *args | *args | Coleta argumentos posicionais extras |
| 4º | Apenas palavra-chave | param (após *) | option=True | Deve ser passado por nome |
| 5º | Palavra-chave variável | **kwargs | **kwargs | Coleta argumentos de palavra-chave extras |
Padrão Comum: Função de Passagem (Pass-Through)
Um dos padrões mais úteis com *args e **kwargs é criar funções que passam todos os argumentos para outra função:
def timed_call(func, *args, **kwargs):
"""Chama uma função e mede seu tempo de execução."""
import time
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
def expensive_sum(a, b, c):
import time
time.sleep(0.1)
return a + b + c
result = timed_call(expensive_sum, 10, 20, c=30)
# expensive_sum took 0.1003s
print(result)
# 60Desempacotamento com * e **
Os operadores * e ** funcionam em ambas as direções: eles empacotam argumentos em definições de função e desempacotam argumentos em chamadas de função e outros contextos.
Desempacotando Listas e Tuplas com *
Use * para desempacotar um iterável em argumentos posicionais individuais:
def add(a, b, c):
return a + b + c
numbers = [10, 20, 30]
# Sem desempacotamento - isso causa TypeError
# add(numbers) # TypeError: add() missing 2 required positional arguments
# Com desempacotamento - espalha a lista em argumentos separados
result = add(*numbers)
print(result) # 60
# Funciona com tuplas, sets e qualquer iterável
coords = (5, 10, 15)
print(add(*coords)) # 30Você também pode usar * para desempacotamento em atribuições e construção de listas (Python 3.5+):
# Desempacotamento estendido em atribuições
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# Desempacotamento em construção de listas/tuplas
list_a = [1, 2, 3]
list_b = [4, 5, 6]
combined = [*list_a, *list_b]
print(combined) # [1, 2, 3, 4, 5, 6]
# Desempacotamento com elementos adicionais
extended = [0, *list_a, 99, *list_b, 100]
print(extended) # [0, 1, 2, 3, 99, 4, 5, 6, 100]Desempacotando Dicionários com **
Use ** para desempacotar um dicionário em argumentos de palavra-chave:
def create_user(name, email, role="viewer"):
return {"name": name, "email": email, "role": role}
user_data = {"name": "Alice", "email": "alice@example.com", "role": "admin"}
# Desempacota dict em argumentos de palavra-chave
user = create_user(**user_data)
print(user)
# {'name': 'Alice', 'email': 'alice@example.com', 'role': 'admin'}Mesclando Dicionários com **
Um dos usos mais práticos de ** é mesclar dicionários:
defaults = {"color": "blue", "size": 12, "font": "Arial"}
user_prefs = {"color": "red", "size": 16}
# Mesclar: user_prefs sobrescreve defaults
merged = {**defaults, **user_prefs}
print(merged)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
# Python 3.9+ também suporta o operador |
merged_new = defaults | user_prefs
print(merged_new)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
# Adicionando chaves extras durante a mesclagem
final = {**defaults, **user_prefs, "theme": "dark"}
print(final)
# {'color': 'red', 'size': 16, 'font': 'Arial', 'theme': 'dark'}Combinando Desempacotamento com * e **
Você pode usar ambos os operadores juntos ao chamar uma função:
def report(title, *items, separator="---", **metadata):
print(f"== {title} ==")
for item in items:
print(f" - {item}")
print(separator)
for key, value in metadata.items():
print(f" {key}: {value}")
positional = ["Task A", "Task B", "Task C"]
options = {"author": "Alice", "date": "2026-02-14"}
report("Sprint Review", *positional, separator="===", **options)
# == Sprint Review ==
# - Task A
# - Task B
# - Task C
# ===
# author: Alice
# date: 2026-02-14Padrões Práticos
Padrão 1: Funções Decoradoras
O uso mais comum de *args e **kwargs em Python de produção é escrever decoradores. Um decorador envolve uma função com outra. Como você não sabe a assinatura da função envolvida antecipadamente, deve usar *args e **kwargs para encaminhar todos os argumentos:
import functools
import time
def retry(max_attempts=3, delay=1.0):
"""Tenta uma função até max_attempts vezes em caso de falha."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"Attempt {attempt}/{max_attempts} failed: {e}")
if attempt < max_attempts:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def fetch_data(url, timeout=10):
"""Simula busca de dados que pode falhar."""
import random
if random.random() < 0.6:
raise ConnectionError(f"Failed to connect to {url}")
return f"Data from {url}"
# O decorador encaminha url e timeout através de *args/**kwargs
result = fetch_data("https://api.example.com", timeout=5)
print(result)Padrão 2: Encaminhamento de init em Subclasses
Ao criar subclasses, você frequentemente precisa encaminhar argumentos do construtor para a classe pai. *args e **kwargs tornam isso limpo:
class Animal:
def __init__(self, name, species, sound="..."):
self.name = name
self.species = species
self.sound = sound
def speak(self):
return f"{self.name} says {self.sound}"
class Dog(Animal):
def __init__(self, *args, breed="Unknown", **kwargs):
super().__init__(*args, **kwargs)
self.breed = breed
def info(self):
return f"{self.name} ({self.breed}) - {self.species}"
# Todos os parâmetros de Animal passam perfeitamente
dog = Dog("Rex", "Canine", sound="Woof!", breed="German Shepherd")
print(dog.speak()) # Rex says Woof!
print(dog.info()) # Rex (German Shepherd) - CanineEste padrão é essencial ao trabalhar com hierarquias de classes complexas, especialmente em frameworks como Django, Flask ou SQLAlchemy onde você estende classes base.
Padrão 3: Funções Wrapper e Proxy
Quando você precisa interceptar ou modificar chamadas de função sem alterar a função original:
def log_call(func):
"""Registra cada chamada a uma função com seus argumentos."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
print(f"Calling {func.__name__}({signature})")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result!r}")
return result
return wrapper
@log_call
def calculate_discount(price, discount_pct, tax_rate=0.08):
discounted = price * (1 - discount_pct / 100)
return round(discounted * (1 + tax_rate), 2)
calculate_discount(100, 20, tax_rate=0.1)
# Calling calculate_discount(100, 20, tax_rate=0.1)
# calculate_discount returned 88.0Padrão 4: Construtores de Clientes de API
Construir wrappers de API flexíveis é um caso de uso clássico:
import json
class APIClient:
def __init__(self, base_url, **default_headers):
self.base_url = base_url.rstrip("/")
self.default_headers = {
"Content-Type": "application/json",
"Accept": "application/json",
**default_headers,
}
def request(self, method, endpoint, *args, **kwargs):
"""Constrói uma requisição com headers e parâmetros mesclados."""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
headers = {**self.default_headers, **kwargs.pop("headers", {})}
request_info = {
"method": method,
"url": url,
"headers": headers,
**kwargs,
}
print(json.dumps(request_info, indent=2, default=str))
return request_info
def get(self, endpoint, **kwargs):
return self.request("GET", endpoint, **kwargs)
def post(self, endpoint, **kwargs):
return self.request("POST", endpoint, **kwargs)
# Uso
client = APIClient(
"https://api.example.com",
Authorization="Bearer token123"
)
client.get("/users", params={"page": 1, "limit": 50})
client.post("/users", data={"name": "Alice"}, headers={"X-Request-ID": "abc123"})Padrão 5: Ciência de Dados -- Parâmetros de Plotagem Dinâmicos
Ao construir funções de análise de dados, **kwargs permite passar configuração para bibliotecas subjacentes:
import pandas as pd
def analyze_column(df, column, **plot_kwargs):
"""Analisa uma coluna do dataframe e gera estatísticas resumidas."""
stats = {
"count": df[column].count(),
"mean": df[column].mean(),
"std": df[column].std(),
"min": df[column].min(),
"max": df[column].max(),
}
print(f"\nAnalysis of '{column}':")
for stat, value in stats.items():
print(f" {stat}: {value:.2f}")
# Encaminha quaisquer kwargs extras para a função de plot
plot_defaults = {"kind": "hist", "bins": 20, "title": f"Distribution of {column}"}
plot_config = {**plot_defaults, **plot_kwargs}
# df[column].plot(**plot_config) # Descomente com matplotlib instalado
print(f" Plot config: {plot_config}")
return stats
# Cria dados de exemplo
df = pd.DataFrame({
"revenue": [100, 250, 180, 320, 275, 410, 195, 360],
"quantity": [5, 12, 8, 15, 13, 20, 9, 17],
})
# Análise padrão
analyze_column(df, "revenue")
# Configurações de plot personalizadas passadas através de **kwargs
analyze_column(df, "revenue", kind="box", color="steelblue", figsize=(10, 6))Se você trabalha em notebooks Jupyter e quer experimentar com assinaturas de funções interativamente, RunCell (opens in a new tab) fornece um ambiente de notebook powered by AI onde você pode testar padrões de *args e **kwargs, obter sugestões em tempo real para manipulação de parâmetros e debugar problemas de passagem de argumentos sem sair do seu workflow.
Erros Comuns e Como Corrigir
Aqui estão os erros mais frequentes que desenvolvedores Python encontram com *args e **kwargs, junto com suas soluções:
| Erro | Mensagem de Erro | Causa | Correção |
|---|---|---|---|
| Ordem errada de parâmetros | SyntaxError: invalid syntax | Colocar **kwargs antes de *args | Sempre use a ordem: regular, *args, apenas palavra-chave, **kwargs |
| Passar uma lista em vez de desempacotar | TypeError: func() missing required arguments | Passar [1,2,3] em vez de *[1,2,3] | Use * para desempacotar: func(*my_list) |
| Argumento de palavra-chave duplicado | TypeError: got multiple values for argument 'x' | Mesma chave tanto posicional quanto em **kwargs | Garanta que não haja sobreposição entre argumentos posicionais e chaves de dict |
| Modificar kwargs diretamente | Efeitos colaterais inesperados | Mutar o dict kwargs | Use kwargs.copy() ou {**kwargs, ...} |
Esquecer *args em super().init | TypeError: __init__() missing arguments | Não encaminhar args para classe pai | Use super().__init__(*args, **kwargs) |
| Usar padrão mutável com kwargs | Estado compartilhado entre chamadas | def func(data={}) | Use None como padrão: def func(data=None) |
Exemplo: Argumento de Palavra-Chave Duplicado
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
data = {"name": "Alice", "greeting": "Hi"}
# ERRADO: name é passado tanto como posicional QUANTO em **data
# greet("Alice", **data)
# TypeError: greet() got multiple values for argument 'name'
# CORRETO: passar apenas através do desempacotamento
print(greet(**data))
# Hi, Alice!
# OU: remover a chave duplicada
print(greet("Alice", **{"greeting": "Hi"}))
# Hi, Alice!Exemplo: Modificando kwargs de Forma Segura
def process(name, **kwargs):
# ERRADO: modificar kwargs diretamente afeta o dict do chamador
# kwargs["processed"] = True
# CORRETO: criar um novo dict
config = {**kwargs, "processed": True}
return {"name": name, **config}
settings = {"timeout": 30, "retries": 3}
result = process("task1", **settings)
print(result)
# {'name': 'task1', 'timeout': 30, 'retries': 3, 'processed': True}
# Dict original permanece inalterado
print(settings)
# {'timeout': 30, 'retries': 3}*args/**kwargs vs Outras Abordagens
Quando você deve usar *args e **kwargs em vez de alternativas? Aqui está uma comparação:
| Abordagem | Sintaxe | Melhor Para | Desvantagens |
|---|---|---|---|
*args | def f(*args) | Número desconhecidos de valores posicionais do mesmo tipo | Sem type hints por argumento, sem acesso nomeado |
**kwargs | def f(**kwargs) | Opções flexíveis, passagem para outras funções | Sem auto-complete em IDEs, sem verificação de tipo estática |
| Parâmetros explícitos | def f(a, b, c) | Conjunto conhecido e fixo de argumentos | Rígido; adicionar parâmetros quebra chamadas existentes |
| Parâmetros padrão | def f(a, b=10) | Parâmetros opcionais com defaults sensatos | Ainda requer conhecer todas as opções antecipadamente |
| Parâmetro lista/tupla | def f(items: list) | Coleção ordenada de valores | Chamador deve construir a lista explicitamente |
| Parâmetro dict | def f(options: dict) | Configuração estruturada | Chamador deve construir o dict explicitamente |
| TypedDict/dataclass | def f(config: Config) | Configuração estruturada e type-safe | Mais boilerplate, requer definição de classe |
Diretrizes gerais:
- Use parâmetros explícitos quando o conjunto de argumentos é conhecido e estável.
- Use
*argsquando sua função naturalmente opera sobre um número variável de valores do mesmo tipo (comoprint(),max(),min()). - Use
**kwargsquando você precisa encaminhar opções para outra função, aceitar configuração flexível ou construir APIs extensíveis. - Use ambos ao escrever decoradores, funções proxy ou hierarquias de classes que precisam de encaminhamento completo de argumentos.
- Use TypedDict ou dataclass quando você quer auto-complete de IDE e verificação de tipo estática para configuração estruturada.
Adicionando Type Hints com *args e **kwargs
Python 3.11+ permite adicionar type hints usando *args e **kwargs com Unpack e TypedDict:
# Type hints básicos (todos os args do mesmo tipo)
def add_numbers(*args: float) -> float:
return sum(args)
def set_options(**kwargs: str) -> dict[str, str]:
return kwargs
# Python 3.11+: tipagem precisa de kwargs com TypedDict
from typing import TypedDict, Unpack
class ConnectionOptions(TypedDict, total=False):
timeout: int
retries: int
ssl: bool
def connect(host: str, port: int, **kwargs: Unpack[ConnectionOptions]) -> dict:
return {"host": host, "port": port, **kwargs}
# IDE agora conhece os argumentos de palavra-chave válidos
result = connect("localhost", 5432, timeout=30, ssl=True)
print(result)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'ssl': True}Perguntas Frequentes
O que *args e **kwargs significam?
Os nomes args e kwargs são abreviações de "arguments" e "keyword arguments" respectivamente. Eles são nomes puramente convencionais -- a mágica real vem dos operadores de prefixo * e **. O asterisco simples * diz ao Python para empacotar argumentos posicionais extras em uma tupla, enquanto o asterisco duplo ** empacota argumentos de palavra-chave extras em um dicionário. Você verá esses nomes usados em praticamente toda base de código Python, tutorial e biblioteca porque são a convenção universalmente reconhecida, mas a linguagem não requer esses nomes específicos.
Posso usar nomes diferentes de args e kwargs?
Sim. Os nomes args e kwargs são convenções, não requisitos de sintaxe. O comportamento de desempacotamento vem inteiramente dos prefixos * e **. Você pode escrever *values, *items, *numbers, **options, **config, **params ou qualquer outro identificador Python válido. No entanto, manter *args e **kwargs é fortemente recomendado na maioria dos casos porque todo desenvolvedor Python os reconhece imediatamente. Use nomes personalizados apenas quando um nome mais descritivo genuinamente melhora a legibilidade, como *paths em uma função de manipulação de arquivos ou **headers em um cliente HTTP.
Qual é a ordem correta de parâmetros em uma função Python?
Python impõe uma ordenação estrita: parâmetros posicionais regulares vêm primeiro, depois *args, depois parâmetros apenas de palavra-chave (com valores padrão) e finalmente **kwargs. A ordem completa é: def func(pos1, pos2, default1=val, *args, kw_only1, kw_only2=val, **kwargs). Violando esta ordem produz um SyntaxError. Um mnemônico útil é "posicional, star-args, apenas-palavra-chave, double-star-kwargs" -- o número de asteriscos aumenta da esquerda para a direita.
Quando devo usar *args vs um parâmetro lista?
Use *args quando cada argumento é um valor separado e independente e o chamador deve passá-los naturalmente sem construir um container: print("a", "b", "c") é mais natural que print(["a", "b", "c"]). Use um parâmetro lista quando os valores logicamente formam uma coleção que o chamador já tem em uma variável, ou quando você precisa distinguir entre a coleção e outros parâmetros. Funções built-in como max(), min() e print() usam *args porque a convenção de chamada parece natural, enquanto funções como sorted(iterable) recebem um único iterável porque a entrada é inerentemente uma sequência.
*args e **kwargs são lentos?
A sobrecarga de *args e **kwargs é mínima. Python cria uma tupla para *args e um dicionário para **kwargs em cada chamada, o que envolve pequenas alocações de memória. Em benchmarks, a diferença comparada a parâmetros explícitos é tipicamente de algumas centenas de nanosegundos por chamada -- irrelevante para praticamente todo código do mundo real. Você precisaria de milhões de chamadas em um loop apertado antes que essa sobrecarga se tornasse mensurável. Foque em eficiência algorítmica e otimização de I/O em vez de evitar *args/**kwargs. A flexibilidade e manutenibilidade de código que eles proporcionam superam em muito qualquer custo de micro-performance.
Conclusão
Os *args e **kwargs do Python são duas das características mais práticas da linguagem. Eles resolvem um problema fundamental: como escrever funções que são flexíveis o suficiente para aceitar números variáveis de argumentos sem sacrificar legibilidade.
Os pontos-chave:
*argscoleta argumentos posicionais extras em uma tupla. Use-o quando sua função deve aceitar qualquer número de valores.**kwargscoleta argumentos de palavra-chave extras em um dicionário. Use-o para opções flexíveis, passagem de configuração e APIs extensíveis.- Ordem de parâmetros é sempre: regular,
*args, apenas palavra-chave,**kwargs. - Desempacotamento com
*e**funciona em chamadas de função, construção de listas e mesclagem de dicionários. - Decoradores são o caso de uso do mundo real mais importante -- eles dependem de
*argse**kwargspara envolver qualquer função independentemente de sua assinatura.
Comece com parâmetros explícitos para funções com assinaturas conhecidas e estáveis. Recorra a *args e **kwargs quando precisar de flexibilidade: decoradores, encaminhamento de subclasses, funções wrapper e construtores de API. Uma vez que você internalizar as mecânicas de empacotamento e desempacotamento, você se encontrará escrevendo código Python mais limpo e reutilizável.