Comment transformer% s en {0}, {1}… moins maladroit?

11

Je dois prendre une chaîne contenant des espaces réservés pour une substitution ultérieure, comme:

"A %s B %s"

Et transformez cela en:

"A {0} B {1}"

Je suis venu avec:

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

Cela fonctionne, mais il se sent super maladroit, et pas du tout python "idiomatique".

À quoi ressemblerait une solution de python idiomatique?

Mises à jour pour clarification:

  • les chaînes résultantes ne sont pas utilisées dans python. Je fais besoin des numéros de compteur là - dedans! (donc ça {}ne suffit pas)!
  • J'ai seulement besoin de me soucier des %schaînes, car les messages sont garantis pour n'utiliser que %s(pas du %i %ftout)
GhostCat salue Monica C.
la source
"re.sub" peut prendre une fonction de remplacement pour remplacer dynamiquement par des accolades numérotées. Soit dit en passant: le remplacement par {} sans chiffres fonctionnerait également.
Michael Butscher
3
Lorsque vous avez une solution de travail et que vous souhaitez l'améliorer, veuillez considérer codereview.stackexchange.com
kojiro
@kojiro Cela ne fonctionne pas pour moi, en raison de "trop ​​maladroit" ;-)
GhostCat salue Monica C.

Réponses:

5

Je ferais ce que Reznik a suggéré à l' origine , puis j'appellerais .formatcela:

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
la source
cela ne fonctionnera que si vous en avez 2 %sdans le texte?
Charif DZ
@CharifDZ et Aran-Fey - voir l'édition. Vous les comptez juste avant la main ...
Dan
On dirait un bon compromis ... lisible, mais pas trop chic.
GhostCat salue Monica C.
8

À utiliser re.subavec une fonction lambda pour réappliquer la substitution une fois pour chaque élément et itertools.countpour obtenir des nombres séquentiellement:

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

N'oubliez pas de mettre cela dans une fonction pour effectuer cette opération plusieurs fois, car vous devrez actualiser itertools.count.

jfaccioni
la source
Sympa, bien que je trouve ça un peu "obscur" ;-)
GhostCat salue Monica C.
@GhostCat ce n'est pas obscur, le compteur est un générateur lorsque vous l'appelez next(counter), produisez la valeur suivante, j'oublie toujours ce générateur vraiment une bonne réponse
Charif DZ
@GhostCat regex et programmation fonctionnelle dans la même ligne: -PI suppose que "l'obscurité" dépend de combien vous êtes habitué à ces outils.
jfaccioni
3

Je pense que ça devrait marcher

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

Reznik
la source
1
@GhostCat ne vous laissez pas tenter par les one-liners, rappelez-vous que les gens doivent maintenir le code
Dan
1
@Dan je sais. Mais j'ai moins de code, c'est aussi moins de code à maintenir.
GhostCat salue Monica C.
Ce n'est pas moins cependant, c'est juste plus difficile à déboguer et plus difficile à lire pour un futur développeur. Cette réponse est assez proche de la mienne, elle a les mêmes appels de fonction (échange de remplacement pour split et join) mais qu'en est-il s'il y a un problème dans une étape intermédiaire? Comment pourriez-vous l'isoler. Et qui est plus rapide pour un nouveau développeur de comprendre ce qu'il fait uniquement à partir du code. Je vous recommande vraiment de ne pas mettre autant de logique sur une seule ligne.
Dan
1
Pas une belle ligne. X.join(Y.split(Z))est juste une façon compliquée d'écrire Y.replace(X, Z), et il n'est pas nécessaire non plus de terminer l' rangeappel list(...).
Aran-Fey
1
@Dan oui, désolé, (en une seule ligne maintenant) :)
Reznik
1

Utilisation re.subpour le remplacement dynamique:

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
la source
1
Gardez à l'esprit que cela ne tient pas compte des espaces réservés échappés ( %%s) - si c'est un problème, vous pouvez utiliser l'expression régulière à la r'(?<!%)%s'place.
Aran-Fey
Si vous autorisez la question stupide: de quoi s'agit-il {{{}}}réellement?
GhostCat salue Monica C.
La formation de chaînes pour {nous échapper ne fait pas \{comme d'habitude {{.
Charif DZ
1

À l'aide d'un générateur:

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
la source
-4

Avez-vous essayé avec .format?

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

Donc:

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

Alfonso
la source
1
Comment cette aide quelqu'un ne se transforme %sen {}?
Aran-Fey