String - dynamische Ersetzungen mit Werten aus einem Dictionary

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
mhard666
User
Beiträge: 8
Registriert: Dienstag 22. September 2020, 13:14

Hallo allerseits,

möglicherweise ist der Betreff etwas unglücklich gewählt, aber mir fiel spontan nichts besseres ein ;)

Folgendes Problem:
ich habe ein Dictionary mit Keys und Werten. Die Keys repräsentieren Variablennamen, die Werte sind halt die Werte der Variablen.
Des Weiteren habe ich einen String, in dem für diese Variablennamen Platzhalter vorgesehen sind.

Beispiel:

Code: Alles auswählen

thisdict =	{
  "brand": "Ford",
  "model": "Mustang",
  "year": "1964"
}
thisstring = "Das ist ein Text in dem die Variable brand durch den Wert @@brand@@ ersetzt werden soll und model durch @@model@@. @@horst@@ wird aber nicht ersetzt, da es keinen Key im Dictionary gibt."
Sollte dann etwas auswerfen wie:

Code: Alles auswählen

Das ist ein Text in dem die Variable brand durch den Wert Ford ersetzt werden soll und model durch 
Mustang. @@horst@@ wird aber nicht ersetzt, da es keinen Key im Dictionary gibt.
Ich würde jetzt hergehen, das Dictionary durchlaufen und in jedem Durchlauf nach dem/den entsprechenden Platzhalter/n im String suchen und diese mit dem Wert aus dem Dictionary ersetzen. Was es im Dict nicht gibt, wird nicht ersetzt.

Ist das so in etwa realisierbar (ist es sicher ;) oder gibt es da eine elegantere Lösung? Oder ist es abwegig, auf diese Weise Ersetzungen vorzunehmen (die Strings beinhalten vorrangig Pfadangaben und URLs, die dynamisch zusammengebaut werden müssen).

Danke schonmal.

VG mhard666.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Stichwort: Textformatierung. Alles ab Python >=3.6: f-strings:

Code: Alles auswählen

car = {
    "brand": "Ford",
    "model": "Mustang",
    "year": "1964"
}

text = f"Das ist ein Text in dem die Variable brand durch den Wert " \
       f"{car['brand']} ersetzt werden soll und model durch {car['model']}." \
       f"@@horst@@ wird aber nicht ersetzt, da es keinen Key im" \
       f"Dictionary gibt."
print(text)
Das mit dem Horst habe ich mal weggelassen, weil das keinen Sinn macht. Entweder es gibt eine Variable - oder es gibt sie nicht.

Bitte gewöhne dir gleich vernünftige Variablennamen an.


Und bevor du anfängst Pfade aus Zeichenketten zu stückeln, schau dir mal das pathlib Modul an.
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

das `string` Modul kennt die `Template` Klasse:

Code: Alles auswählen

>>> from string import Template
>>> template = Template('Das ist ein Text in dem die Variable brand durch den Wert $brand ersetzt werden soll und model durch $model. $horst wird aber nicht ersetzt, da es keinen Key im Dictionary gibt.')
>>> data = {'brand': 'Ford', 'model': 'Mustang', 'year': 1964}
>>> template.safe_substitute(data)
'Das ist ein Text in dem die Variable brand durch den Wert Ford ersetzt werden soll und model durch Mustang. $horst wird aber nicht ersetzt, da es keinen Key im  Dictionary gibt.'
>>>
Das Standard-Präfix ist $ für den Platzhalter, das kann man aber überschreiben, siehe Doku

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 14030
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wenn die @@…@@ eine Vorgabe sind, die sich nicht ändern lässt, kann man das mit dem `re`-Modul machen. Man kann beim Ersetzen für den Ersatzwert auch ein aufrufbares Objekt übergeben, welches das Match-Objekt als Argument übergeben bekommt, und den Text durch den der Treffer ersetzt werden soll als Rückgabewert haben muss.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
mhard666
User
Beiträge: 8
Registriert: Dienstag 22. September 2020, 13:14

Guten Abend,

vielen Dank für die Antworten.

@noisefloor:
Genau so hab ich mir das vorgestellt.

@sparrow:
Interessanter Ansatz, funktioniert leider nur bedingt bei mir. Den String und die Keys/Werte des Dictionarys kommen nicht aus dem Script, sondern aus einer Konfigdatei bzw. werden aus Webseiten ausgelesen. Ich muss davon ausgehen, dass der Nutzer nicht den Variablennamen des Dictionarys kennt...
Das Pfadgefrickel lässt sich dabei leider nicht zur Gänze vermeiden, allerdings lässt sich mit Unterstützung von pathlib warscheinlich einiges vereinfachen. Schaue ich mir auf jeden Fall genauer an.
Übrigens: Der Variablenname inkl. Werte des Dictionarys waren geklaut ;-) den des Strings habe ich nur analog dazu benannt - im richtigen Leben benutze ich vernünftige Namen.

@__blackjack__:
@@...@@ hab ich mal irgendwo gesehen. Ist keine Vorgabe, funktioniert aber mit string.replace() ganz vernünftig und es ist relativ unwarscheinlich, dass sich im konkreten Anwendungsfall da was in die Quere kommt. Die Lösung von noiseflor finde ich aber um Längen eleganter ;-)

VG mhard666
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Mein Ansatz ist nicht interessant, sondern dir Art wie man Stringformatierunt 2020 in Python macht.
Ich befürchte, wie so oft, dass das, was du für die Lösung deines Problems hälst, nicht die Lösung ist.
Leg doch mal dein konkretes Problen da. Denn wenn der Anwender nicht weiß, was da für Namen kommen, kann er sie sowieso nicht in der Zeichenkette unterbringen.
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

@sparrow: für solche Fälle mit Wörterbuch kann man noch das alte format_map nutzen.

Code: Alles auswählen

class DefaultMapping(dict):
    def __missing__(self, key):
        return f"@@{key}@@"

car = DefaultMapping({
    "brand": "Ford",
    "model": "Mustang",
    "year": "1964"
})

text = ("Das ist ein Text in dem die Variable brand durch den Wert "
       "{brand} ersetzt werden soll und model durch {model}. "
       "{horst} wird aber nicht ersetzt, da es keinen Key im "
       "Dictionary gibt.").format_map(car)
print(text)
mhard666
User
Beiträge: 8
Registriert: Dienstag 22. September 2020, 13:14

Moin, moin,

@sparrow:
ich denke hier liegt ein Missverständnis vor. Es geht nicht darum, dass er die Variablen nicht kennt, sondern den Variablennamen des Dictionarys, welcher ja im Code vorliegt, den der User ja nicht kennen muss. Um bei dem Beispiel zu bleiben: "...durch {car['model']}...". Die Bezeichnung des Dictionarys "car" kennt der User ja gar nicht.

Um besser zu verstehen, wo die Reise hingehen soll, versuche ich mein Ansinnen mal gestrafft darzulegen:
Das Script soll automatisiert aktuelle Programmversionen herunterladen und in einem Softwareverzeichnis ablegen. Dazu soll es aus einer Website verschiedene Infos auslesen (Version, Build, Downloadlink, Majorversion etc.). Da diese Infos bei den Herstellern unterschiedlich genutzt werden und auch ein Downloadlink nicht immer direkt verfügbar ist, ist eine gewisse Flexibilität erforderlich. Das Basisverzeichnis für die Softwareablage kommt aus einer globalen Config oder von der Kommandozeile. Hersteller, Softwarebezeichnung, Plattform kommen aus einer Software-spezifischen Config. Die Dateiablage wird dann aus Basisverzeichnis + Plattform + Hersteller + Software + Version zusammengebaut - also aus den Werten die aus Website und Config ermittelt werden und die in dem Dictonary stehen. Allerdings nicht fest nach diesem Schema, sondern ganz nach Wunsch des Nutzers (dafür das String-Template). Analog wird bei Bedarf der Downloadlink zusammengesetzt. Wenn verfügbar kann er natürlich auch von der Website kommen. Falls nicht wird er auf gleiche Weise wie der DL-Pfad zusammengebaut (auch hierfür das String-Template).
Die beiden Template-Strings werden mit der globalen Konfig (Dateiablage) und der Software-spezifischen Konfig (DL-Link) geliefert und sind nicht fest im Code "verdrahtet".

Hoffe das ist soweit verständlich. Für Windows gibt es ein Tool "Ketarin", das macht soetwas in der Art (und etwas mehr...). Leider ist das halt nur für Windows...

VG mhard666.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

@mhard666: Den _Namen_ des dicts hat der _Anwender_ ja auch gar nicht zu kennen. Denn der Name ändert sich hoffentlich nicht. Denn das wäre das dynamische Anlegen von Variablen im Code. Und das macht man nicht. Deshalb nimmt man ja dicts, weil deren Schlüssel dynamisch sind. Der Name des dicts ist nur für den Enwickler relevant. Wenn der nicht konstant ist, dann stimmt etwas mit deiner Programmlogik oder Datenstruktur nicht.

Der Vorschlag von Sirius 3 zeigt, wie man direkt die Schlüssel eines dicts ansprechen kann und ein fehlender Schlüssel entsprechend anders behandelt wird.

Deine Beschreibung für die Software klingt auch ein bisschen seltsam. Wenn ich als Anwender diese ganzen Informationen auseinaderfrickeln muss, dann ist das so aufwändig, da kann ich die Software, die das tut selbst schreiben oder per Hand prüfen. Denn irgendwann müssen ja Webseiten ausgelesen werden. Und wenn das mehr als eine ist, dann ist das wie bei den Leuten, die automatisch Preise aus Webshops auslesen wollen: Es gibt keinen generischen Ansatz. Das heißt, man muss für jede Webseite grob etwas eigenes basteln.
Also ist das sinnvollste, alle diese Informationen zentral in einer Ressourcen-Datei zu speichern und zum Beispiel im Internet dem Anwender zur Verfügung zu stellen. Das wäre dann ein wirklicher Mehrwert für den Anwender.
Wobei man dann auch direkt die detaillierten Informationen zur Verfügung stellen kann und nicht nur die Schablone, wie die zu erlangen sind. Dann braucht jeder Anwender die Liste nur holen, statt die Webseite selber auszulesen.
Nur meine 2 cent.
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

also basierend auf dem Ausgangsposte wäre ich davon ausgegangen, dass der TE eine (einfach) Template-Engine sucht. Was string.Template ja letztendlich ist. Aus der ausführlicheren Beschreibung werde ich nicht schlau...

Gruß, noisefloor
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

@mhard666: wie weit bist Du denn schon an Deinem Ziel? Hast Du schon Deine Downloader für die verschiedenen Seiten fertig und möchtest nur noch eine Konfigurationsdatei dafür erstellen?
mhard666
User
Beiträge: 8
Registriert: Dienstag 22. September 2020, 13:14

n'Abend,

ich hatte ehrlich nicht gedacht, dass die Problemstellung das Thema derart aufbläst, aber ich finde die Lösungsansätze und auch die Diskussion um den Rest darum trotzdem interessant.

Der Ansatz von @Sirius3 würde natürlich auch passen. Wichtig ist für den Anwendungsfall einfach, dass der Template-String keinen Verweis auf die internen Variablen des Scripts beinhalten darf.
Über Sinn und Unsinn des Vorhabens könnte man streiten - mache ich aber nicht ;-) Für mich ist es ein Projekt, wo ich an einem (für mich) nutzbringenden Thema arbeiten und meine Python Kenntnisse ausbauen kann. Ich habe zahlreiche Anwendungen, die regelmäßig geupdatet werden wollen und wenn ich die alle von Hand auf Updates prüfen und downloaden will, brauche ich keine anderen Hobbies mehr :lol: Ja, es ist ein Aufwand, für jeden Download eine Konfigdatei zu erstellen, aber das ist mit etwas Übung in kurzer Zeit erledigt. Bleiben ja immernoch die Downloads, die nur über POST-Requests (mit Benutzeanmeldung o.ä. Scherze) zu holen sind...

@Sirius3:
Ich habe das Ganze soweit, dass ich die Website auslesen kann, den DL-Link aus der Website lese oder selbst zusammenbauen kann und die Datei downloaden und an einem fixen Pfad speichern kann. Allerdings alles noch statisch, mit festen Variablen im Script. Jetzt habe ich zwei Ansätze für die Verwendung der Templates und wenn das soweit funktioniert baue ich Auswertung von Kommandozeilenparametern und Konfigdateien mit ein. Achja, der Code ist definitiv noch optimierungsbedürftig...

VG mhard666
Antworten