Skip to content

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.

  • *args coleta argumentos posicionais extras em uma tupla.
  • **kwargs coleta 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())                 # 0

Dentro 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 seconds

Exemplo 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.6

Exemplo 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.csv

Entendendo **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: Seattle

Dentro 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:

  1. Parâmetros posicionais regulares
  2. *args (variável posicional)
  3. Parâmetros apenas de palavra-chave (após *args)
  4. **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çãoTipoSintaxeExemploDescrição
Posicionalparama, bObrigatório, preenchido por posição
Padrãoparam=valuec=10Opcional, preenchido por posição ou nome
Posicional variável*args*argsColeta argumentos posicionais extras
Apenas palavra-chaveparam (após *)option=TrueDeve ser passado por nome
Palavra-chave variável**kwargs**kwargsColeta 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)
# 60

Desempacotamento 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))  # 30

Você 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-14

Padrõ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) - Canine

Este 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.0

Padrã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:

ErroMensagem de ErroCausaCorreção
Ordem errada de parâmetrosSyntaxError: invalid syntaxColocar **kwargs antes de *argsSempre use a ordem: regular, *args, apenas palavra-chave, **kwargs
Passar uma lista em vez de desempacotarTypeError: func() missing required argumentsPassar [1,2,3] em vez de *[1,2,3]Use * para desempacotar: func(*my_list)
Argumento de palavra-chave duplicadoTypeError: got multiple values for argument 'x'Mesma chave tanto posicional quanto em **kwargsGaranta que não haja sobreposição entre argumentos posicionais e chaves de dict
Modificar kwargs diretamenteEfeitos colaterais inesperadosMutar o dict kwargsUse kwargs.copy() ou {**kwargs, ...}
Esquecer *args em super().initTypeError: __init__() missing argumentsNão encaminhar args para classe paiUse super().__init__(*args, **kwargs)
Usar padrão mutável com kwargsEstado compartilhado entre chamadasdef 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:

AbordagemSintaxeMelhor ParaDesvantagens
*argsdef f(*args)Número desconhecidos de valores posicionais do mesmo tipoSem type hints por argumento, sem acesso nomeado
**kwargsdef f(**kwargs)Opções flexíveis, passagem para outras funçõesSem auto-complete em IDEs, sem verificação de tipo estática
Parâmetros explícitosdef f(a, b, c)Conjunto conhecido e fixo de argumentosRígido; adicionar parâmetros quebra chamadas existentes
Parâmetros padrãodef f(a, b=10)Parâmetros opcionais com defaults sensatosAinda requer conhecer todas as opções antecipadamente
Parâmetro lista/tupladef f(items: list)Coleção ordenada de valoresChamador deve construir a lista explicitamente
Parâmetro dictdef f(options: dict)Configuração estruturadaChamador deve construir o dict explicitamente
TypedDict/dataclassdef f(config: Config)Configuração estruturada e type-safeMais boilerplate, requer definição de classe

Diretrizes gerais:

  • Use parâmetros explícitos quando o conjunto de argumentos é conhecido e estável.
  • Use *args quando sua função naturalmente opera sobre um número variável de valores do mesmo tipo (como print(), max(), min()).
  • Use **kwargs quando 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:

  • *args coleta argumentos posicionais extras em uma tupla. Use-o quando sua função deve aceitar qualquer número de valores.
  • **kwargs coleta 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 *args e **kwargs para 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.

📚