Jak zmienić% s w {0}, {1}… mniej niezręczny?

11

Muszę wziąć ciąg zawierający symbole zastępcze do późniejszego podstawienia, na przykład:

"A %s B %s"

I zamień to w:

"A {0} B {1}"

Wymyśliłem:

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

To działa, ale wydaje się super niezręczne i wcale nie jest „idiomatycznym” pytonem.

Jak wyglądałoby dobrze idiomatyczne rozwiązanie python?

Aktualizacje w celu wyjaśnienia:

  • wynikowe ciągi nie są używane w Pythonie. I zrobić potrzebne numery liczników w tam! (więc {}nie jest wystarczająco dobry)!
  • Muszę tylko dbać o %sciągi, ponieważ gwarantuje się, że wiadomości będą używać tylko %s(żadnych %i %f)
GhostCat pozdrawia Monikę C.
źródło
„re.sub” może pełnić funkcję zastępczą w celu dynamicznego zastąpienia numerowanymi nawiasami klamrowymi. Nawiasem mówiąc: również zastąpienie {} bez liczb będzie działać.
Michael Butscher,
3
Kiedy masz działające rozwiązanie i chcesz je ulepszyć, rozważ codereview.stackexchange.com
kojiro
@kojiro To nie działa dla mnie z powodu „zbyt niezgrabnego” ;-)
GhostCat pozdrawia Monikę C.

Odpowiedzi:

5

Zrobiłbym to, co pierwotnie sugerował Reznik , a następnie przywołałbym .formatto:

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
źródło
zadziała to tylko, jeśli masz 2 %sw tekście?
Charif DZ
@CharifDZ i Aran-Fey - zobacz edycję. Po prostu policzysz je przed rozdaniem ...
Dan
Wygląda na dobry kompromis ... czytelny, ale niezbyt fantazyjny.
GhostCat pozdrawia Monikę C.
8

Użyj re.subz funkcją lambda do ponownego zastosowania podstawienia jeden raz dla każdego elementu i itertools.countdo sekwencyjnego pobierania liczb:

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

Pamiętaj, aby zawinąć to w funkcję, aby wykonać tę operację więcej niż jeden raz, ponieważ musisz odświeżyć itertools.count.

jfaccioni
źródło
Fajnie, choć uważam to za nieco „niejasne” ;-)
GhostCat pozdrawia Monikę C.
@GhostCat nie jest to niejasne, licznik jest generatorem, gdy tylko next(counter)go nazwiesz , generuje następną wartość, zawsze zapominam o tym generatorze naprawdę ładna odpowiedź
Charif DZ
Wyrażenie regularne @GhostCat i programowanie funkcjonalne w tym samym wierszu: -Myślę, że „niejasność” zależy od tego, jak bardzo jesteś przyzwyczajony do tych narzędzi.
jfaccioni
3

Myślę, że to powinno zadziałać

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

Reznik
źródło
1
@GhostCat nie kusi jedno-liniowiec, pamiętaj, że ludzie muszą utrzymywać kod
Dan
1
@ Dan wiem. Ale mniej kodu to także mniej kodu do utrzymania.
GhostCat pozdrawia Monikę C.
Nie mniej jednak jest to trudniejsze do debugowania i trudniejsze do odczytania dla przyszłego twórcy. Ta odpowiedź jest prawie identyczna z moją, ma te same wywołania funkcji (zamiana zamień na dzielenie i łączenie), ale co, jeśli wystąpi problem na etapie pośrednim? Jak byś to wyizolował. I co jest szybsze od nowego dewelopera, aby zrozumieć, co robi po prostu z kodu. Naprawdę polecam nie umieszczać tyle logiki w jednym wierszu.
Dan
1
Niezbyt fajny jednowarstwowy. X.join(Y.split(Z))to po prostu zawiły sposób pisania Y.replace(X, Z)i nie trzeba kończyć rangepołączenia list(...).
Aran-Fey,
1
@Dan tak, przepraszam za to, (teraz tylko w jednej linii) :)
Reznik
1

Używanie re.subdo dynamicznego zastępowania:

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
źródło
1
Pamiętaj, że nie uwzględnia to znaków zastępczych ( %%s) - jeśli jest to problem, możesz r'(?<!%)%s'zamiast tego użyć wyrażenia regularnego .
Aran-Fey
Jeśli pozwolisz na głupie pytanie: o co {{{}}}właściwie chodzi?
GhostCat pozdrawia Monikę C.
Formowanie łańcuchów do ucieczki {Nie robimy \{tak jak zwykle {{.
Charif DZ
1

Za pomocą generatora:

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
źródło
-4

Czy próbowałeś z .format?

string.format (0 = wartość, 1 = wartość)

Więc:

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

Alfonso
źródło
1
Jak to pomaga komukolwiek zmienić się %sw {}?
Aran-Fey