Concatenate no Python: altere parâmetros

Publicado em: 28/08/2026
Tempo de leitura: 5 minutos
Macro shot capturing detailed patterns of a python in its natural surroundings.

ParamSpec preserva a assinatura completa de uma função, mas alguns decoradores não mantêm exatamente os mesmos parâmetros. Eles podem injetar um contexto, remover uma dependência do chamador, adicionar um lock ou adaptar uma callable para um framework. typing.Concatenate descreve essa transformação no início da lista de parâmetros.

Neste guia, você aprenderá a combinar Concatenate com ParamSpec e TypeVar, criar decoradores de injeção, autenticação e sincronização, lidar com métodos, callbacks e wrappers assíncronos, além de reconhecer os limites da ferramenta.

O problema de um decorador que injeta contexto

from collections.abc import Callable

class Contexto:
    usuario: str

def injetar_contexto(funcao: Callable):
    def wrapper(*args, **kwargs):
        contexto = Contexto()
        contexto.usuario = "sistema"
        return funcao(contexto, *args, **kwargs)
    return wrapper

A função original exige um Contexto como primeiro parâmetro, mas a função decorada não deve expor esse argumento. Sem uma anotação precisa, autocomplete e validação de chamadas são perdidos.

Concatenate com ParamSpec

from collections.abc import Callable
from typing import Concatenate, ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")

def injetar_contexto(
    funcao: Callable[Concatenate[Contexto, P], R],
) -> Callable[P, R]:
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        contexto = Contexto()
        contexto.usuario = "sistema"
        return funcao(contexto, *args, **kwargs)
    return wrapper

Concatenate[Contexto, P] significa que a callable original recebe primeiro um Contexto e, em seguida, todos os parâmetros capturados por P. O wrapper público expõe apenas P.

Por que Concatenate aparece dentro de Callable

Concatenate é uma construção voltada à lista de parâmetros de callables. Ele normalmente aparece como primeiro argumento de Callable e termina com um ParamSpec.

Callable[Concatenate[Contexto, P], R]

Os tipos explícitos vêm antes de P. A ferramenta não serve para inserir parâmetros no meio ou no fim da assinatura capturada.

Injetando uma conexão

class Conexao:
    def executar(self, sql: str) -> list[tuple[object, ...]]:
        ...

def com_conexao(
    funcao: Callable[Concatenate[Conexao, P], R],
) -> Callable[P, R]:
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        conexao = abrir_conexao()
        try:
            return funcao(conexao, *args, **kwargs)
        finally:
            conexao.fechar()
    return wrapper

O chamador não fornece a conexão. O decorador controla criação e encerramento, enquanto a função interna recebe uma dependência explicitamente tipada.

Preservando metadados

from functools import wraps

def com_conexao(
    funcao: Callable[Concatenate[Conexao, P], R],
) -> Callable[P, R]:
    @wraps(funcao)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        ...
    return wrapper

wraps preserva nome, docstring e referência à função original em runtime. Concatenate, ParamSpec e TypeVar preservam a relação estática. Use ambos.

Adicionando um lock

from threading import Lock


def com_lock(
    funcao: Callable[Concatenate[Lock, P], R],
) -> Callable[P, R]:
    lock = Lock()

    @wraps(funcao)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        with lock:
            return funcao(lock, *args, **kwargs)
    return wrapper

O lock pode ser compartilhado por todas as chamadas da função decorada. Documente esse comportamento, pois a anotação não descreve granularidade, reentrância ou desempenho.

Injetando usuário autenticado

class Usuario:
    id: int
    nome: str

def requer_usuario(
    funcao: Callable[Concatenate[Usuario, P], R],
) -> Callable[P, R]:
    @wraps(funcao)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        usuario = usuario_atual()
        if usuario is None:
            raise PermissionError("autenticação necessária")
        return funcao(usuario, *args, **kwargs)
    return wrapper

A função de negócio declara explicitamente que depende de Usuario, enquanto o ponto de entrada público obtém essa informação do ambiente.

Mais de um parâmetro injetado

def injetar_servicos(
    funcao: Callable[
        Concatenate[Usuario, Conexao, P],
        R,
    ],
) -> Callable[P, R]:
    @wraps(funcao)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        usuario = obter_usuario()
        conexao = abrir_conexao()
        try:
            return funcao(usuario, conexao, *args, **kwargs)
        finally:
            conexao.fechar()
    return wrapper

Vários tipos podem aparecer antes de P. A ordem deve corresponder exatamente à função original.

Transformação inversa

Um wrapper também pode adicionar um parâmetro para o chamador e ocultá-lo da função interna. Nesse caso, a callable recebida usa P e a callable retornada usa Concatenate.

def exigir_token(
    funcao: Callable[P, R],
) -> Callable[Concatenate[str, P], R]:
    @wraps(funcao)
    def wrapper(
        token: str,
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> R:
        validar_token(token)
        return funcao(*args, **kwargs)
    return wrapper

Agora o chamador precisa fornecer um token antes dos parâmetros originais.

Wrappers assíncronos

from collections.abc import Awaitable

class ContextoAsync:
    trace_id: str

def com_trace(
    funcao: Callable[
        Concatenate[ContextoAsync, P],
        Awaitable[R],
    ],
) -> Callable[P, Awaitable[R]]:
    @wraps(funcao)
    async def wrapper(
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> R:
        contexto = ContextoAsync()
        contexto.trace_id = criar_trace_id()
        return await funcao(contexto, *args, **kwargs)
    return wrapper

A relação dos parâmetros é igual à versão síncrona; o retorno é um Awaitable.

Concatenate em callbacks

def registrar_handler(
    handler: Callable[Concatenate[Evento, P], None],
    *args: P.args,
    **kwargs: P.kwargs,
) -> None:
    evento = aguardar_evento()
    handler(evento, *args, **kwargs)

A função de registro controla o Evento e o consumidor fornece os demais argumentos compatíveis com P.

Métodos e self

Em métodos, self ou cls já faz parte da assinatura. Um decorador genérico que apenas encaminha parâmetros normalmente precisa somente de ParamSpec. Concatenate é necessário quando você altera explicitamente o início visível da assinatura.

class Servico:
    @requer_usuario
    def executar(
        usuario: Usuario,
        self,
        tarefa: str,
    ) -> None:
        ...

Esse formato é pouco natural porque o descritor de método espera self primeiro. Ao decorar métodos, planeje cuidadosamente a ordem e teste o comportamento do binding. Muitas vezes é melhor injetar a dependência depois de self com um decorador específico para métodos, algo que Concatenate não modela diretamente de forma simples.

Limite: apenas o início da assinatura

Concatenate adiciona tipos antes de P. Ele não insere um parâmetro depois do primeiro item capturado, nem representa facilmente a adição de um keyword-only no final. Para transformações mais complexas, overloads explícitos, Protocols específicos ou plugins de analisador podem ser necessários.

Parâmetros nomeados

Os tipos adicionados por Concatenate são tratados como parâmetros posicionais na relação da callable. Nome, default e natureza keyword-only podem não ser expressos da forma desejada. Se a API pública depende fortemente de nomes, considere declarar um Protocol com __call__ explícito.

Concatenate versus overload

Concatenate descreve uma transformação genérica de assinatura. Overload descreve um conjunto finito de assinaturas alternativas. Use Concatenate quando qualquer P é preservado após adicionar ou remover parâmetros iniciais. Use overload quando existem alguns formatos concretos com retornos diferentes.

Concatenate versus Protocol

Protocol é mais detalhado para callables com parâmetros nomeados específicos, atributos adicionais ou métodos. Concatenate é compacto para decoradores genéricos. As duas ferramentas podem ser combinadas.

Concatenate versus functools.partial

functools.partial fixa argumentos em runtime. Concatenate descreve estaticamente a mudança entre callables. Um helper tipado que cria partials pode usar ParamSpec e Concatenate, mas a inferência de argumentos nomeados complexos pode variar entre verificadores.

Retorno transformado

def com_contexto_opcional(
    funcao: Callable[Concatenate[Contexto, P], R],
) -> Callable[P, R | None]:
    @wraps(funcao)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None:
        contexto = tentar_contexto()
        if contexto is None:
            return None
        return funcao(contexto, *args, **kwargs)
    return wrapper

Concatenate cuida dos parâmetros; TypeVar e a anotação do retorno devem mostrar qualquer mudança no resultado.

Erros comuns

  • Usar Concatenate sem ParamSpec no final: a construção fica incompleta.
  • Inserir parâmetros no meio: Concatenate modela apenas prefixos.
  • Trocar a ordem: os tipos devem acompanhar a chamada real.
  • Esquecer P.args e P.kwargs: o wrapper não preserva os parâmetros restantes.
  • Ignorar binding de métodos: self e descritores podem mudar o comportamento.
  • Presumir que a tipagem valida runtime: o wrapper ainda precisa construir e encaminhar objetos corretos.

Exemplo completo: unidade de trabalho

from collections.abc import Callable
from functools import wraps
from typing import Concatenate, ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")

class UnidadeTrabalho:
    def __enter__(self):
        return self

    def confirmar(self) -> None:
        ...

    def __exit__(self, exc_type, exc, tb) -> None:
        if exc is not None:
            self.reverter()

    def reverter(self) -> None:
        ...

def transacional(
    funcao: Callable[Concatenate[UnidadeTrabalho, P], R],
) -> Callable[P, R]:
    @wraps(funcao)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        with UnidadeTrabalho() as uow:
            resultado = funcao(uow, *args, **kwargs)
            uow.confirmar()
            return resultado
    return wrapper

@transacional
def criar_pedido(
    uow: UnidadeTrabalho,
    cliente_id: int,
    itens: list[int],
) -> int:
    ...

Para o chamador, criar_pedido() recebe apenas cliente_id e itens. A função interna continua declarando sua dependência de forma explícita e testável.

Testando a assinatura

Use reveal_type(), testes de mypy ou pyright e chamadas intencionalmente inválidas. Teste também runtime para verificar ordem, binding e ciclo de vida das dependências.

Conclusão

typing.Concatenate complementa ParamSpec ao modelar callables que adicionam ou removem parâmetros no começo da assinatura. Ele é valioso para injeção de contexto, autenticação, locks, conexões e adaptadores.

A documentação oficial de Concatenate no Python detalha a construção. Consulte também o guia de ParamSpec no Python e use Protocol ou overloads quando a transformação não puder ser representada como um prefixo genérico.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    A close-up view of a person's hand signing a business contract on a desk with a pen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ParamSpec no Python: preserve assinaturas

    Aprenda ParamSpec no Python para preservar assinaturas em decoradores, callbacks, wrappers async e funções de ordem superior.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A detailed image of a reticulated python showcasing its patterned scales and intricate skin texture.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeIs no Python: refine os dois ramos

    Aprenda TypeIs no Python para refinar tipos nos ramos verdadeiro e falso, comparar com TypeGuard e criar predicados seguros.

    Ler mais

    Tempo de leitura: 6 minutos
    28/08/2026
    Close-up of hands typing on a laptop keyboard, Python book in sight, coding in progress.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeGuard no Python: refine tipos com segurança

    Aprenda TypeGuard no Python para refinar tipos, validar coleções, TypedDict e Protocol com segurança estática e checagem real.

    Ler mais

    Tempo de leitura: 6 minutos
    28/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    typing.Self no Python: retornos fluentes

    Aprenda typing.Self no Python para métodos fluentes, classmethods, builders, clones, Protocol, context managers e retornos que preservam subclasses.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    Yellow block letters spelling 'error' on a vibrant pink background, capturing a playful message.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ExceptionGroup no Python: múltiplos erros

    Aprenda ExceptionGroup no Python para múltiplos erros, except*, grupos aninhados, TaskGroup, filtros, logging e validação em lote.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A tranquil wooden pathway winds through a vibrant autumn forest, covered in fallen leaves.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk no Python: percorra diretórios

    Aprenda Path.walk no Python para percorrer diretórios, podar pastas, tratar erros, links simbólicos, tamanhos, remoção segura e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026