D-Strings no Python 3.15: Adeus textwrap.dedent()
Entenda as d-strings da PEP 822 no Python 3.15, como funcionam, sintaxe com f-strings e por que substituem textwrap.dedent() em strings multiline.
Se você já escreveu strings multiline dentro de funções ou classes em Python, provavelmente conhece o problema: a indentação do código contamina o conteúdo da string. A solução clássica sempre foi textwrap.dedent(), mas ela tem limitações e adiciona overhead em tempo de execução.
A PEP 822 propõe uma solução elegante para o Python 3.15: as d-strings (dedented strings). Com o prefixo d antes de aspas triplas, a indentação é removida automaticamente pelo compilador, sem custo em runtime. É uma daquelas mudanças que parece pequena, mas melhora o dia a dia de quem escreve Python profissionalmente.
O problema com strings multiline hoje
Veja um exemplo comum em código Python atual:
def gerar_sql():
query = """
SELECT u.nome, u.email
FROM usuarios u
WHERE u.ativo = true
ORDER BY u.nome
"""
return query
print(repr(gerar_sql()))
# '\n SELECT u.nome, u.email\n FROM usuarios u\n ...'
A string resultante vem com toda a indentação do bloco. Para resolver, usamos textwrap.dedent():
import textwrap
def gerar_sql():
query = textwrap.dedent("""
SELECT u.nome, u.email
FROM usuarios u
WHERE u.ativo = true
ORDER BY u.nome
""")
return query
Funciona, mas tem problemas:
- Overhead em runtime:
dedent()é uma função que processa a string toda vez que o código executa - Verbosidade: precisa importar
textwrape envolver a string na chamada - Não funciona com t-strings: a nova sintaxe de template strings da PEP 750 não é compatível com
textwrap.dedent() - Confusão com a primeira linha: o comportamento com a linha logo após as aspas de abertura nem sempre é intuitivo
O que são d-strings?
D-strings são strings multiline com o prefixo d que removem automaticamente a indentação comum de todas as linhas. O processamento acontece em tempo de compilação — sem nenhum custo em runtime.
A sintaxe básica:
s = d"""
Hello
World!
"""
print(repr(s)) # 'Hello\nWorld!\n'
Regras fundamentais:
- O prefixo
dsó funciona com aspas triplas ("""ou''') - As aspas de abertura devem ser seguidas de uma quebra de linha
- A quebra de linha após a abertura não é incluída no resultado
- A indentação a remover é calculada pela indentação comum mais longa entre todas as linhas (incluindo a linha das aspas de fechamento)
Se você está aprendendo sobre os diferentes tipos de strings em Python, vale revisar também nosso glossário sobre strings e o artigo sobre f-strings.
Exemplos práticos
SQL sem indentação indesejada
def buscar_usuarios(status):
return d"""
SELECT u.nome, u.email, u.criado_em
FROM usuarios u
WHERE u.status = '{status}'
ORDER BY u.criado_em DESC
LIMIT 100
"""
print(buscar_usuarios("ativo"))
# SELECT u.nome, u.email, u.criado_em
# FROM usuarios u
# WHERE u.status = 'ativo'
# ORDER BY u.criado_em DESC
# LIMIT 100
HTML em geradores de template
def card_usuario(nome, cargo):
return d"""
<div class="card">
<h3>{nome}</h3>
<p>{cargo}</p>
</div>
"""
Mensagens de log e docstrings de CLI
def mostrar_ajuda():
print(d"""
Uso: meuscript [opções] <arquivo>
Opções:
-v, --verbose Modo detalhado
-o, --output Arquivo de saída
-h, --help Mostra esta ajuda
""")
Controle do trailing newline
A posição das aspas de fechamento controla se há quebra de linha no final:
# Com newline no final (aspas na linha seguinte)
s = d"""
Hello
World!
"""
print(repr(s)) # 'Hello\nWorld!\n'
# Sem newline no final (aspas na mesma linha)
s = d"""
Hello
World!"""
print(repr(s)) # 'Hello\nWorld!'
Preservando indentação relativa
Se as aspas de fechamento estiverem alinhadas à esquerda, a indentação relativa é preservada:
s = d"""
def hello():
print("world")
"""
print(repr(s)) # ' def hello():\n print("world")\n'
Isso é muito útil para gerar código Python indentado corretamente.
Combinação com outros prefixos
Uma das grandes vantagens das d-strings é que combinam naturalmente com outros prefixos de string:
df-string (dedent + formatação)
nome = "Maria"
saudacao = df"""
Olá, {nome}!
Bem-vinda ao sistema.
"""
print(saudacao)
# Olá, Maria!
# Bem-vinda ao sistema.
dr-string (dedent + raw)
padrao = dr"""
^\d{3}\.\d{3}\.\d{3}-\d{2}$
"""
print(repr(padrao)) # '^\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}$\n'
Ideal para regex sem precisar escapar barras invertidas. Se você trabalha com expressões regulares, veja nosso artigo sobre regex em Python.
db-string (dedent + bytes)
dados = db"""
HTTP/1.1 200 OK
Content-Type: text/plain
"""
print(type(dados)) # <class 'bytes'>
dt-string (dedent + template)
nome = "João"
t = dt"""
Olá, {nome}!
"""
print(type(t)) # <class 'string.templatelib.Template'>
Essa combinação é especialmente relevante porque textwrap.dedent() não funciona com template strings da PEP 750, já que elas não são str. As d-strings resolvem esse problema.
Como funciona o algoritmo de dedent
O algoritmo é executado pelo compilador (não em runtime) e segue estes passos:
- Identifica a indentação de cada linha (espaços e tabs no início)
- Ignora linhas em branco (somente whitespace) no cálculo
- Inclui a linha das aspas de fechamento no cálculo
- Calcula a indentação comum mais longa compartilhada por todas as linhas
- Remove essa indentação do início de cada linha
Tabs e espaços são tratados como caracteres distintos — misturar os dois pode causar IndentationError:
# Mistura de tabs e espaços pode gerar erro
s = d"""
Hello # tab
World! # espaços
"""
# IndentationError se não houver prefixo comum
D-strings vs textwrap.dedent()
| Aspecto | textwrap.dedent() | D-strings |
|---|---|---|
| Quando executa | Runtime | Compilação |
| Overhead | Sim (processa a string) | Zero |
| Importação | Precisa de import textwrap | Nenhuma |
| Funciona com f-strings | Sim, mas verboso | Sim, com df""" |
| Funciona com t-strings | Não | Sim, com dt""" |
| Continuação de linha | Não dedenta a linha seguinte | Dedenta normalmente |
| Trailing newline | Remove a primeira, mantém a última | Controlado pela posição das aspas |
Migração de código existente
Se você tem código usando textwrap.dedent(), a migração é direta na maioria dos casos:
# Antes
import textwrap
msg = textwrap.dedent("""\
Linha 1
Linha 2
Linha 3
""")
# Depois
msg = d"""
Linha 1
Linha 2
Linha 3
"""
Atenção: o comportamento com a primeira e última linha pode diferir em casos específicos. Teste a saída ao migrar.
Erros comuns e restrições
A PEP define claramente o que não é permitido:
# Aspas simples ou duplas (sem triplas) — SyntaxError
s = d"hello"
# Conteúdo na mesma linha das aspas de abertura — SyntaxError
s = d"""Hello
World!"""
# Barra invertida logo após a abertura — SyntaxError
s = d"""\
Hello"""
Essas restrições existem para manter a sintaxe previsível e consistente.
Status atual e quando usar
A PEP 822 está em status Draft e é direcionada ao Python 3.15. O Steering Council ainda não aprovou formalmente, mas a proposta tem boa recepção na comunidade. Uma implementação de referência já existe.
Quando a feature estiver disponível, use d-strings sempre que:
- Escrever SQL, HTML, YAML ou qualquer texto multiline dentro de funções
- Precisar de strings formatadas multiline com
df""" - Trabalhar com template strings (
dt""") - Quiser eliminar o
import textwrape o overhead dededent()
Se você quer acompanhar outras novidades do Python 3.15, confira nosso artigo sobre lazy imports (PEP 810) e fique de olho no blog para mais atualizações.
Conclusão
As d-strings são o tipo de melhoria que não revoluciona a linguagem, mas remove uma fonte constante de atrito. Quem já perdeu tempo debugando indentação em strings multiline ou se incomodou com o boilerplate de textwrap.dedent() vai entender o valor dessa PEP.
A combinação com f-strings e t-strings é o ponto mais forte: pela primeira vez teremos uma forma limpa e sem overhead de escrever templates multiline formatados dentro de código indentado. Para quem trabalha com geração de código, SQL dinâmico ou templates HTML em Python, essa é uma adição bem-vinda ao toolkit da linguagem.
Equipe python.dev.br
Contribuidor do Python Brasil — Aprenda Python em Português