Como transformar% s em {0}, {1} ... menos desajeitado?

11

Eu tenho que pegar uma string contendo espaços reservados para substituição posterior, como:

"A %s B %s"

E transforme isso em:

"A {0} B {1}"

Eu vim com:

def _fix_substitution_parms(raw_message):
  rv = raw_message
  counter = 0
  while '%s' in rv:
    rv = rv.replace('%s', '{' + str(counter) + '}', 1)
    counter = counter + 1
return rv

Isso funciona, mas parece super desajeitado, e nem um pouco python "idiomático".

Como seria uma solução python bem idiomática?

Atualizações para esclarecimentos:

  • as seqüências resultantes não são usadas no python. I fazer precisar os números do contador em lá! (então {}não é bom o suficiente)!
  • Eu só preciso me preocupar com %sseqüências de caracteres, pois as mensagens são garantidas para usar apenas %s(não é o %i %fque for)
GhostCat saúda Monica C.
fonte
"re.sub" pode assumir uma função como substituição para substituir dinamicamente por chaves numeradas. A propósito: Substituir por {} sem números também funcionaria.
Michael Butscher 14/10/19
3
Quando você tem uma solução de trabalho e você gostaria de melhorá-lo, por favor considere codereview.stackexchange.com
kojiro
@kojiro Não está funcionando para mim, devido a "muito desajeitado" ;-)
GhostCat saúda Monica C.

Respostas:

5

Eu faria o que Reznik originalmente sugeriu e depois apelou .format:

def _fix_substitution_parms(raw_message: str) -> str:
    num_to_replace = raw_message.count("%s")
    python_format_string_message = raw_message.replace("%s", "{{{}}}")
    final_message = python_format_string_message.format(*range(num_to_replace))
    return final_message
Dan
fonte
isso funcionará apenas se você tiver 2 %sno texto?
Charif DZ
@CharifDZ e Aran-Fey - veja a edição. Você apenas contá-las antes da mão ...
Dan
Parece um bom compromisso ... legível, mas não muito chique.
GhostCat saúda Monica C.
8

Use re.subcom uma função lambda para reaplicar a substituição uma vez para cada elemento e itertools.countpara obter números sequencialmente:

import itertools
import re

s = "A %s B %s"

counter = itertools.count()
result = re.sub('%s', lambda x: f'{{{next(counter)}}}', s)
print(result)  # 'A {0} B {1}'

Lembre-se de agrupar isso em uma função para executar esta operação mais de uma vez, pois você precisará atualizar itertools.count.

jfaccioni
fonte
Bom, embora eu encontrá-lo um pouco "obscuro" ;-)
GhostCat saudações Monica C.
@GhostCat não é obscuro, contador é um gerador sempre que você chamá next(counter)-lo produzir o próximo valor, eu sempre esquecer este gerador realmente resposta agradável
Charif DZ
Regex @GhostCat e programação funcional na mesma linha: -PI acho que a "obscuridade" depende de quanto você está acostumado a essas ferramentas.
Jfaccioni # 14/19
3

Eu acho que esse trabalho deve

rv.replace('%s','{{{}}}').format(*range(rv.count('%s')))

Reznik
fonte
11
@GhostCat não ser tentado pelo one-liners, lembre-se que as pessoas têm para manter o código
Dan
11
@ Dan eu sei. Mas menos código também é menos código para manter.
GhostCat saúda Monica C.
Não é menos, é apenas mais difícil depurar e mais difícil para um desenvolvedor futuro ler. Essa resposta está bem próxima de ser idêntica à minha, tem as mesmas chamadas de função (troca de substituição por divisão e junção), mas e se houver um problema em uma etapa intermediária? Como você o isolaria. E o que é mais rápido para o novo desenvolvedor entender o que ele faz apenas com o código. Eu realmente recomendo que você não coloque tanta lógica em uma única linha.
Dan
11
Não é uma frase legal. X.join(Y.split(Z))é apenas uma maneira complicada de escrever Y.replace(X, Z)e não há necessidade de encerrar a rangeligação list(...).
21419 Aran-Fey
11
@ Dan sim, sinto muito por isso, (apenas em uma linha agora) :)
Reznik
1

Usando re.subpara substituição dinâmica:

import re

text = "A %s B %s %s B %s"


def _fix_substitution_parms(raw_message):
    counter = 0
    def replace(_):
        nonlocal counter
        counter += 1
        return '{{{}}}'.format(counter - 1)
    return re.sub('%s', replace, raw_message)


print(_fix_substitution_parms(text))  # A {0} B {1} {2} B {3}
Charif DZ
fonte
11
Lembre-se de que isso não leva em consideração os espaços reservados vazados ( %%s). Se isso for um problema, você pode usar a regex r'(?<!%)%s'.
22819 Aran-Fey
Se você permitir a pergunta estúpida: sobre o que {{{}}}realmente é?
GhostCat saúda Monica C. 15/10/19
Formatação de string para escapar {, não fazemos \{como de costume {{.
Charif DZ
1

Usando um gerador:

def split_and_insert(mystring):
    parts = iter(mystring.split('%s'))
    yield next(parts)
    for n, part in enumerate(parts):
        yield f'{{{n}}}'
        yield part

new_string = ''.join(split_and_insert("A %s B %s"))
Pulsar
fonte
-4

Você já tentou com .format?

string.format (0 = valor, 1 = valor)

Assim:

"A {0} B {1}".format(0=value, 1=value)

Alfonso
fonte
11
Como isso ajuda qualquer um transformar %sem {}?
22819 Aran-Fey