¿Cómo convertir% s en {0}, {1} ... menos torpe?

11

Tengo que tomar una cadena que contenga marcadores de posición para una sustitución posterior, como:

"A %s B %s"

Y convertir eso en:

"A {0} B {1}"

Se me ocurrio:

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

Eso funciona, pero se siente súper torpe, y en absoluto pitón "idiomático".

¿Cómo sería una solución de pitón bien idiomática?

Actualizaciones para aclaraciones:

  • Las cadenas resultantes no se utilizan en Python. Yo hago necesitar los números del contador de allí! (¡entonces {}no es lo suficientemente bueno!)
  • Solo necesito preocuparme por las %scadenas, ya que los mensajes están garantizados para usar solo %s(sin ningún %i %ftipo)
GhostCat saluda a Monica C.
fuente
"re.sub" puede tomar una función como reemplazo para reemplazar dinámicamente con llaves numeradas. Por cierto: reemplazar con {} sin números también funcionaría.
Michael Butscher
3
Cuando tenga una solución que funcione y desee mejorarla, considere codereview.stackexchange.com
kojiro el
@kojiro No está funcionando para mí, debido a "demasiado torpe" ;-)
GhostCat saluda a Monica C.

Respuestas:

5

Haría lo que Reznik sugirió originalmente y luego invocaría .formateso:

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
fuente
esto funcionará solo si tienes 2 %sen el texto?
Charif DZ
@CharifDZ y Aran-Fey: vea la edición. Solo los
Dan
Parece un buen compromiso ... legible, pero no demasiado elegante.
GhostCat saluda a Monica C.
8

Úselo re.subcon una función lambda para volver a aplicar la sustitución una vez para cada elemento, y itertools.countpara obtener números secuencialmente:

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}'

Recuerde incluir esto en una función para realizar esta operación más de una vez, ya que necesitará actualizar itertools.count.

jfaccioni
fuente
Agradable, aunque me parece un poco "oscuro" ;-)
GhostCat saluda a Monica C.
@GhostCat no es oscuro, el contador es un generador cuando lo llamas next(counter)producir el siguiente valor, siempre me olvido de esta respuesta realmente agradable del generador
Charif DZ
@GhostCat regex y programación funcional en la misma línea: -PI supongo que la "oscuridad" depende de cuánto estés acostumbrado a estas herramientas.
jfaccioni
3

Creo que debería funcionar

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

Reznik
fuente
1
@GhostCat no se deje tentar por una frase, recuerde que las personas deben mantener el código
Dan
1
@ ¿Lo sé? Pero menos código también es menos código para mantener.
GhostCat saluda a Monica C.
Sin embargo, no es menos, es más difícil de depurar y más difícil de leer para un futuro desarrollador. Esta respuesta está bastante cerca de ser idéntica a la mía, tiene las mismas llamadas de función (intercambiar reemplazar por dividir y unir), pero ¿qué pasa si hay un problema en un paso intermedio? ¿Cómo lo aislarías? Y es más rápido que un nuevo desarrollador entienda lo que hace solo desde el código. Realmente recomiendo que no pongas tanta lógica en una sola línea.
Dan
1
No es una buena frase. X.join(Y.split(Z))es solo una forma enrevesada de escribir Y.replace(X, Z), y tampoco es necesario ajustar la rangellamada list(...).
Aran-Fey
1
@Dan sí, lo siento, (solo en una línea ahora) :)
Reznik
1

Utilizando re.subpara el reemplazo dinámico:

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
fuente
1
Tenga en cuenta que esto no tiene en cuenta los marcadores de posición escapados ( %%s): si eso es una preocupación, puede usar la expresión regular en su r'(?<!%)%s'lugar.
Aran-Fey
Si permite la estúpida pregunta: ¿de qué se trata en {{{}}}realidad?
GhostCat saluda a Monica C.
Formateo de cadenas para escapar, {no hacemos lo que hacemos \{habitualmente {{.
Charif DZ
1

Usando un generador:

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
fuente
-4

¿Has probado con .format?

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

Entonces:

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

Alfonso
fuente
1
¿Cómo esto ayuda a nadie a su vez %sen {}?
Aran-Fey