Seite 1 von 1

String-Formatter finden...

Verfasst: Samstag 1. Oktober 2005, 21:52
von jens
Gibt's eine bessere/direktere Möglichkeit an die Platzhalter in einem Text zu kommen, als:

Code: Alles auswählen

import re

txt = "bla bla %(test1)s und bla %(test2)s xyz %(test3)s"

print re.findall(r"%\(.*?\)s", txt)
Ausgabe:

Code: Alles auswählen

['%(test1)s', '%(test2)s', '%(test3)s']

Verfasst: Samstag 1. Oktober 2005, 22:35
von BlackJack
Ich wüsste keine, aber Dein regulärer Ausdruck ist etwas zu einfach wenn Du wirklich alle Platzhalter finden willst. Es ja nicht nur '%s' und man kann noch die Parameter zum formatieren einbauen, also z.B. '%(name)10s %(pi).2f' usw.

Verfasst: Samstag 1. Oktober 2005, 22:37
von jens
BlackJack hat geschrieben:Dein regulärer Ausdruck ist etwas zu einfach
Da hast du recht, aber erstmal gibt's bei mir nur die einfache %(name)s Version...

Verfasst: Sonntag 2. Oktober 2005, 08:35
von henning
Was meinst du mit einfachere Methode?
Was willst du denn mit den Platzhaltern machen?
Wenn du sie durch Werte ersetzen willst, solltest du natürlich den %-Operator verwenden.

Wenn du nen längeren Text hast und wissen willst, welche Platzhalter darin verlangt werden und du den Text ändern kannst, würde ich eine Template-Engine empfehlen, z.B. cheetah.

Du kannst natürlich auch sowas machen (ungetestet):

Code: Alles auswählen

class FindKey(dict):
  def __init__(self, *args, **kwargs):
     dict.__init__(self, *args, **kwargs)
     self.found_keys = {}
  def __getattr__(self, attr):
     self.found_keys[attr] = True
     return ""

txt = "bla bla %(test1)s und bla %(test2)s xyz %(test3)s"

f = FindKey()
dumy = txt % f
print f.found_keys.keys()
Ich weiß nicht, ob das eleganter oder direkter als ne re ist, aber auf jeden Fall findet es alle Platzhalter.

Achso: Wenn in txt nach was anderem als strings gefragt wird, solltest du bei __getattr__ natürlich was zurückgeben, was sich zumindest problemlos casten lässt.

Verfasst: Sonntag 2. Oktober 2005, 11:28
von jens
Ich brauche das ganze zum Fehler-Handling. Um besser Anzeigen zu können, wo der Fehler steckt ;)
Ich werd erstmal bei der re-Lösung bleiben, weil's kleiner ist und bisher nur die eine Form %(name)s vorkommt.
Werd aber deine Lösung mal im Hinterkopf behalten, wer weiss...

Verfasst: Sonntag 2. Oktober 2005, 14:51
von Joghurt
jens hat geschrieben:Ich brauche das ganze zum Fehler-Handling. Um besser Anzeigen zu können, wo der Fehler steckt ;)
:?: :?:

Verfasst: Sonntag 2. Oktober 2005, 19:33
von jens
Joghurt hat geschrieben: :?: :?:
Also es geht natürlich mal wieder um PyLucid. Dort gibt es interne-Seiten, die halt String-Formater als Platzhalter beinhalten. Die Seiten kann der Admin allerdings editieren. Wenn er nun einen String-Formater löscht/umbenennt, funktioniert die internal_page nicht mehr richtig.
In dem Fall ist es hilfreich einen gescheiten Fehler anzuzeigen ;)

Verfasst: Montag 19. Dezember 2005, 17:49
von jens
Das ist meine aktuelle Version um bei einem "not enough arguments for format string" Fehler, mehr Informationen auszugeben.

Dabei ist content, ein String mit den String-Formatter-Platzhalter und page_dict das Dict. mit den Angaben:

Code: Alles auswählen

import sys

def test(content, page_dict):
    try:
        print content % page_dict
    except Exception, e:
        import re
        content_placeholder = re.findall(r"%\((.*?)\)s", content)
        content_placeholder.sort()
        given_placeholder = page_dict.keys()
        given_placeholder.sort()
        diff_placeholders = []
        for i in content_placeholder:
            if (not i in given_placeholder) and (not i in diff_placeholders):
                diff_placeholders.append(i)
        for i in given_placeholder:
            if (not i in content_placeholder) and (not i in diff_placeholders):
                diff_placeholders.append(i)
        diff_placeholders.sort()
        raise Exception(
            "%s: '%s': Can't fill internal page '%s'. \
            *** placeholder in internal page: %s *** given placeholder for that page: %s *** diff: %s" % (
                sys.exc_info()[0], e, internal_page_name, content_placeholder, given_placeholder, diff_placeholders
            )
        )

internal_page_name = "KeinName"

test(
    content = "Bla %(platzhalter1)s blup %(Platzhalter2)s...",
    page_dict = {
        "platzhalter1": "jau",
        "PPPaltzhalter": "Fehler"
    }
)
Ausgabe:
Exception: exceptions.KeyError: ''Platzhalter2'': Can't fill internal page 'KeinName'. *** placeholder in internal page: ['Platzhalter2', 'platzhalter1'] *** given placeholder for that page: ['PPPaltzhalter', 'platzhalter1'] *** diff: ['PPPaltzhalter', 'Platzhalter2']

Was mir nicht gefällt sind die beiden for-Schleifen um diff_placeholders zusammen zu bauen...

Verfasst: Montag 19. Dezember 2005, 17:56
von henning
Ich finde meine Version schöner ;-)

Verfasst: Montag 19. Dezember 2005, 18:57
von jens
henning hat geschrieben:Ich finde meine Version schöner ;-)
Jein... Deine Funktioniert aber nur, wenn in txt und in t die Format-String richtig geschrieben und vollständig erhalten sind...

Meine *Fehler*abfrage ist aber für den Fall, das halt die Operatoren nicht passen!

Verfasst: Montag 19. Dezember 2005, 19:02
von Leonidas
Eine andere Möglichkeit wäre, str.__mod__ selbst zu implementieren und dort ein anderes Error-Handling zu machen, welches bessere Fehlermeldungen wirft.

Verfasst: Montag 19. Dezember 2005, 19:39
von Joghurt
henning hat geschrieben:Ich finde meine Version schöner ;-)
Man sollte allgemein so Programmieren, dass man nicht versucht, die Ursachen für einen Fehler abzufangen (da man immer etwas übersehen kann). Besser ist es, und das ist ja auch die Pythonphilosophie, auf Fehler zu reagieren, und das macht Jens ja. Von daher finde ich seine Lösung auch "eleganter"

Beispiel:

Code: Alles auswählen

# Schauen ob Datei existiert
if os.path.isfile(datei):
  file = open(datei)
  ... etc ...
else:
  print "Datei nicht gefunden
ist schlecht, denn das fängt nicht die Möglichkeit ab, dass die Datei nicht lesbar ist, etc. besser

Code: Alles auswählen

try:
  file = open(datei)
  ... etc ...
except:
  print "Fehler beim lesen"

Verfasst: Montag 19. Dezember 2005, 20:45
von joe
Joghurt hat geschrieben:besser

Code: Alles auswählen

try:
  file = open(datei)
  ... etc ...
except:
  print "Fehler beim lesen"
Immer nur ausnahmen abfangen, die man auch erwartet (also IOError beim lesen von dateien). Wenn z.B. open() vorher versehentlich überschrieben wurde, wird man oben mit einer falschen fehlermeldung in die irre geführt. Besser:

Code: Alles auswählen

open = "bla"
try: 
  f = open(datei) 
  ... etc ...
except IOError: 
  print "Fehler beim lesen"
joe

Verfasst: Montag 19. Dezember 2005, 20:52
von Joghurt
Ja, hast ja Recht. Nur wenn jemand schon open überschreibt, kann er sie auch so modifizieren, dass sie bei jeder Exception ein IOError zurückgibt :twisted:

Verfasst: Dienstag 20. Dezember 2005, 08:21
von jens
Also ich komm irgendwie doch nicht weiter...

Es kommt immer noch zu einem 'not enough arguments for format string' Fehler :( Trotz meiner Fehlermeldung kann ich nicht erkennen woran es liegt :(

Somit ist es vielleicht doch keine schlechte Idee, tiefer anzufangen. d.H. vielleicht den String-Operator direkt zu manipulieren und darin eine Fehlermeldung einzubauen...
Allerdings weiß ich nicht wirklich wo ich ansetzten kann :(

Verfasst: Dienstag 20. Dezember 2005, 09:03
von jens
Nun hab ich es... Ich hab ein %-zeichen vergessen, welches eigentlich als %% Escaped werden muß...

Code: Alles auswählen

import sys

def test(content, page_dict):
    try:
        print content % page_dict
    except Exception, e:
        import re
        content_placeholder = []
        for i in re.findall(r"%\((.*?)\)s", content):
            if not i in content_placeholder:
                content_placeholder.append(i)
        content_placeholder.sort()
        given_placeholder = page_dict.keys()
        given_placeholder.sort()
        diff_placeholders = []
        for i in content_placeholder:
            if (not i in given_placeholder) and (not i in diff_placeholders):
                diff_placeholders.append(i)
        for i in given_placeholder:
            if (not i in content_placeholder) and (not i in diff_placeholders):
                diff_placeholders.append(i)
        diff_placeholders.sort()
        raise Exception(
            "%s: '%s': Can't fill internal page '%s'. \n\
            *** %s placeholder in internal page: %s \n\
            *** %s given placeholder for that page: %s \n\
            *** diff: %s" % (
                sys.exc_info()[0], e, internal_page_name,
                len(content_placeholder), content_placeholder,
                len(given_placeholder), given_placeholder,
                diff_placeholders
            )
        )

internal_page_name = "KeinName"

test(
    content = "Bla %(platzhalter1)s blup %<-FEHLER..",
    page_dict = {
        "platzhalter1": "jau",
    }
)
In dem Fall kommt nämlich keine gute Fehlermeldung zustande:

Code: Alles auswählen

Exception: exceptions.TypeError: 'not enough arguments for format string': Can't fill internal page 'KeinName'. 
            *** 1 placeholder in internal page: ['platzhalter1'] 
            *** 1 given placeholder for that page: ['platzhalter1'] 
            *** diff: []
Wie könnte man das besser machen???

btw. Es gibt auch Template strings, die eine bessere Fehlermeldung haben soll... Gibt's allerdings erst seit Python 2.4 :(

Verfasst: Dienstag 20. Dezember 2005, 12:44
von jens
Ich suche gerade nach eine RE für die Aufgabe:

Code: Alles auswählen

import re

txt = """Soll auf alle %-Zeichen treffen, außer %(Beispiel)s und %%
Wobei ein %% quasi Escaped ist.
Soll aber auch auf %(nicht abgeschossene Klammern treffen...
"""

class FindKeys:
    def __init__(self, txt):
        pattern = re.compile(r"([^%]%[^%(])")
        pattern.sub(self.handle, txt)

    def handle(self, matchobj):
        print matchobj
        print matchobj.pos
        print matchobj.group(0)

FindKeys(txt)
Allerdings klappt die RE bei %(nicht abgeschossene Klammern, nicht :(
Außerdem gibt matchobj.pos nicht wirklich die Position wieder... Es ist immer =0 ?!?!?
Denn ich möchte eigentlich die Position wissen, um den Treffer mit Textteilen vor und hinter dem Treffer anzeigen zu können...

Eine andere Variante wäre re.split() zu nehmen und die Ausgabe dann aus dem Ergebniss zu bauen... Aber das ist irgendwie dumm...

Verfasst: Dienstag 20. Dezember 2005, 13:55
von jens
Nun hab ich mir was gebastelt:

Code: Alles auswählen

class Find_StringOperators:
    """
    Sucht in einem String nach %-StringOperatoren.
    Dabei wird zwischen richtigen und falschen unterschieden.

    Dient zur besseren Fehlerausgabe, bei String Operationen.

    Test-Text:
    ----------
    Hier ist ein Beispieltext mit %(richtigen)s Platzhaltern.
    Aber auch mit %(falschen, da die hier Klammer nicht geschlossen ist.
    Außerdem müßen einzelne %-Zeichen, immer escaped werden. Das wird
    mit doppelten %% Zeichen gemacht, die nach dem String-Operation,
    bei dem die %(Platzhalter)s durch Daten aus einem Dict ersetzt werden,
    wieder zu einfachen %-Zeichen umgewandelt werden.
    """

    cutout = 20

    def __init__(self, txt):
        self.correct_hit_pos = []
        self.incorrect_hit_pos = []
        self.txt = txt

        # alle %-Zeichen, die nicht mit %%-Escaped sind
        pattern = re.compile(r"([^%]%[^%])")
        pattern.sub(self._incorrect_hit, txt)

        # Richtig %(formatierte)s String
        pattern = re.compile(r"([^%]%\(.*?\)s)")
        pattern.sub(self._correct_hit, txt)

    def _incorrect_hit(self, matchobj):
        self.incorrect_hit_pos.append(matchobj.start())

    def _correct_hit(self, matchobj):
        self.incorrect_hit_pos.remove(matchobj.start())
        self.correct_hit_pos.append(matchobj.start())

    #_______________________________________________________________________
    # Zugriff auf die Daten

    def get_incorrect_pos(self):
        "Start- & End-Liste der falschen %-Operatoren"
        return self.get_pos(self.incorrect_hit_pos)

    def get_correct_pos(self):
        "Start- & End-Liste der richtigen %-Operatoren"
        return self.get_pos(self.correct_hit_pos)

    def get_pos(self, poslist):
        """
        Wandelt aus der Positionsliste eine Liste mit
        Start- und End-Positionen für einen Text-Slice
        """
        results = []
        for pos in poslist:
            start = pos - self.cutout
            end = pos + self.cutout
            if start<0:
                start = 0
            if end>len(self.txt):
                end = len(self.txt)

            results.append((start, end))
        return results

    def slice_pos_list(self, pos_list):
        """
        Liefert eine Liste der Textstellen zurück.
        """
        result = []
        for start,end in pos_list:
            result.append(
                "...%s..." % self.txt[start:end].encode("String_Escape")
            )
        return result

    #_______________________________________________________________________
    # Debug

    def debug_results(self):
        print "self.incorrect_hit_pos:", self.incorrect_hit_pos
        print "incorrect_pos:", self.get_incorrect_pos()
        print ">>> Textstellen mit falsche %-Zeichen im Text:"
        for i in self.slice_pos_list(self.get_incorrect_pos()):
            print i

        print
        print "self.correct_hit_pos:", self.correct_hit_pos
        print "correct_pos:", self.get_correct_pos()
        print ">>> Textstellen mit richtige %-StringOperatoren:"
        for i in self.slice_pos_list(self.get_correct_pos()):
            print i

doc = Find_StringOperators.__doc__
print doc
print "-"*80
s = Find_StringOperators(doc)
print "incorrect_pos:", s.get_incorrect_pos()
print "correct_pos:", s.get_correct_pos()
print "-"*80
s.debug_results()
Ausgaben:
Sucht in einem String nach %-StringOperatoren.
Dabei wird zwischen richtigen und falschen unterschieden.

Dient zur besseren Fehlerausgabe, bei String Operationen.

Test-Text:
----------
Hier ist ein Beispieltext mit %(richtigen)s Platzhaltern.
Aber auch mit %(falschen, da die hier Klammer nicht geschlossen ist.
Außerdem müßen einzelne %-Zeichen, immer escaped werden. Das wird
mit doppelten %% Zeichen gemacht, die nach dem String-Operation,
bei dem die %(Platzhalter)s durch Daten aus einem Dict ersetzt werden,
wieder zu einfachen %-Zeichen umgewandelt werden.

--------------------------------------------------------------------------------
incorrect_pos: [(11, 51), (267, 307), (353, 393), (563, 603)]
correct_pos: [(221, 261), (480, 520)]
--------------------------------------------------------------------------------
self.incorrect_hit_pos: [31, 287, 373, 583]
incorrect_pos: [(11, 51), (267, 307), (353, 393), (563, 603)]
>>> Textstellen mit falsche %-Zeichen im Text:
...in einem String nach %-StringOperatoren....
...n.\n Aber auch mit %(falschen, da die ...
...dem m\xc3\xbc\xc3\x9fen einzelne %-Zeichen, immer es...
... wieder zu einfachen %-Zeichen umgewande...

self.correct_hit_pos: [241, 500]
correct_pos: [(221, 261), (480, 520)]
>>> Textstellen mit richtige %-StringOperatoren:
...ein Beispieltext mit %(richtigen)s Platz...
...ion,\n bei dem die %(Platzhalter)s dur...

Verfasst: Freitag 15. September 2006, 17:58
von jens
Ich hab mir nun eine bessere Variante zusammen gebaut. Dabei wird einfach fehlende Keys im context eingefügt. So kann die Operation doch durchgeführt werden und gleichzeitig hat man eine Fehlermeldung:

Code: Alles auswählen

    def fill_string_context(self, internal_page_name, content, context):
        """
        Mach die Python-String-Operation. Wenn ein key im context fehlt, wird
        der Fehler mit page_msg ausgegeben, gleichzeitig wird der fehlende Key
        in den context eingefügt und nochmals probiert...
        """
        try:
            return content % context
        except KeyError, key:
            key = key[0]
            self.page_msg(
                "Key-Error: '%s' in internal page '%s'!" % (
                    key, internal_page_name
                )
            )
            context[key] = "" # Fehlenden Key im context einfügen

            # String-Operation nochmals versuchen:
            content = self.fill_string_context(
                internal_page_name, content, context
            )
            return content
self.page_msg() könnte auch sein: sys.stderr.write() :)

Verfasst: Freitag 15. September 2006, 18:47
von mitsuhiko
Warum nicht so?

Code: Alles auswählen

class PlaceholderMissmatch(Exception):

    def __init__(self, required, found, diff):
        self.required = required
        self.found = found
        self.diff = diff
        Exception.__init__(self, 'required: %s; found: %s; diff: %s' % (
            ', '.join(required), ', '.join(found), ', '.join(diff)
        ))


def test(s, page_dict):
    found = []
    class FakeMap(object):
        def __getitem__(self, name):
            found.append(name)
            return ''
    s % FakeMap()
    required = page_dict.keys()
    if found != required:
        diff = set(found).intersection(set(required))
        raise PlaceholderMissmatch(required, found, sorted(diff))


test(
    'Bla %(platzhalter1)s blup %(Platzhalter2)s...',
    {
        "platzhalter1": "jau",
        "PPPaltzhalter": "Fehler"
    }
)