Einführung

Das Zeichenfolgenmodul verfügt über eine Vorlagenklasse, mit der Sie mithilfe eines Zuordnungsobjekts Ersetzungen in einer Zeichenfolge vornehmen können, z.

>>> string.Template('var is $var').substitute({'var': 1})
'var is 1'

Die Ersatzmethode kann eine KeyError-Ausnahme auslösen, wenn beispielsweise versucht wird, ein Element zu ersetzen, das in der Zuordnung fehlt

>>> string.Template('var is $var and foo is $foo').substitute({'var': 1})
KeyError: 'foo'

Oder kann einen ValueError auslösen, wenn die Vorlagenzeichenfolge ungültig ist, z. Es enthält ein $ Zeichen gefolgt von einem Leerzeichen:

>>> string.Template('$ var is $var').substitute({'var': 1})
ValueError: Invalid placeholder in string: line 1, col 1

Das Problem

Mit einer Vorlagenzeichenfolge und einer Zuordnung möchte ich bestimmen, ob alle Platzhalter in der Vorlage ersetzt werden. Zu diesem Zweck würde ich versuchen, die Ersetzung vorzunehmen und eine KeyError-Ausnahme abzufangen:

def check_substitution(template, mapping):
    try:
        string.Template(template).substitute(mapping)
    except KeyError:
        return False
    except ValueError:
        pass
    return True

Dies funktioniert jedoch nicht, da nachfolgende KeyErrors nicht abgefangen werden, wenn die Vorlage ungültig ist und ein ValueError ausgelöst wird:

>>> check_substitution('var is $var and foo is $foo', {'var': 1})
False
>>> check_substitution('$ var is $var and foo is $foo', {'var': 1})
True

Aber ValueErrors sind mir egal . Was wäre der richtige Ansatz für dieses Problem?

14
Ernest A 7 Okt. 2012 im 14:44

3 Antworten

Beste Antwort

In den Dokumenten heißt es, dass Sie das Muster ersetzen können, solange es alle enthält notwendige benannte Gruppen:

import re
from string import Template


class TemplateIgnoreInvalid(Template):
    # override pattern to make sure `invalid` never matches
    pattern = r"""
    %(delim)s(?:
      (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
      (?P<named>%(id)s)      |   # delimiter and a Python identifier
      {(?P<braced>%(id)s)}   |   # delimiter and a braced identifier
      (?P<invalid>^$)            # never matches (the regex is not multilined)
    )
    """ % dict(delim=re.escape(Template.delimiter), id=Template.idpattern)


def check_substitution(template, **mapping):
    try:
        TemplateIgnoreInvalid(template).substitute(mapping)
    except KeyError:
        return False
    else:
        return True

Tests

f = check_substitution
assert f('var is $var', var=1)
assert f('$ var is $var', var=1)
assert     f('var is $var and foo is $foo', var=1, foo=2)
assert not f('var is $var and foo is $foo', var=1)
assert     f('$ var is $var and foo is $foo', var=1, foo=2)
assert not f('$ var is $var and foo is $foo', var=1)
# support all invalid patterns
assert f('var is $var and foo is ${foo', var=1)
assert f('var is $var and foo is ${foo', var=1, foo=2) #NOTE: problematic API
assert     f('var is $var and foo is ${foo and ${baz}', var=1, baz=3)
assert not f('var is $var and foo is ${foo and ${baz}', var=1)

Es funktioniert für alle ungültigen Vorkommen des Trennzeichens ($).

Die Beispiele zeigen, dass das Ignorieren ungültiger Muster einfache Tippfehler in der Vorlage verbirgt, sodass dies keine gute API ist.

6
jfs 7 Okt. 2012 im 13:08

Dies ist eine schnelle Lösung (mit Rekursion):

def check_substitution(tem, m):
    try:
        string.Template(tem).substitute(m)
    except KeyError:
        return False
    except ValueError:
        return check_substitution(tem.replace('$ ', '$'), m) #strip spaces after $
    return True

Ich weiß, dass es länger dauert, wenn zwischen $ und var mehr als ein Leerzeichen steht. Sie können es also verbessern, indem Sie den regulären Ausdruck verwenden.

BEARBEITEN

Es ist sinnvoller, $ in $$ zu maskieren [Danke @Pedro], damit Sie ValueError anhand dieser Anweisung abfangen können:

return check_substitution(tem.replace('$ ', '$$ '), m) #escaping $ by $$
4
MBarsi 7 Okt. 2012 im 11:30

Python führt keine Zeichenfolgenersetzung über mehrere Zeilen durch

Wenn Sie diese Zeichenfolge haben

criterion = """
    <criteria>
    <order>{order}</order>
      <body><![CDATA[{code}]]></body>
    </criteria>
"""

criterion.format(dict(order="1",code="Hello")

Ergebnisse in:

KeyError: 'order'

Eine Lösung besteht darin, das Modul string.Template zu verwenden

from string import Template

criterion = """
    <criteria>
    <order>$order</order>
      <body><![CDATA[$code]]></body>
    </criteria>
"""

Template(criterion).substitute(dict(order="1",code="hello")

HINWEIS: Sie müssen den Schlüsselwörtern ein $ voranstellen, ohne sie in {} einzuschließen

Ausgabe ist:

 <criteria>
    <order>1</order>
      <body><![CDATA[hello]]></body>
    </criteria>

Vollständige Dokumente sind: https://docs.python.org/2/ library / string.html # template-strings

-1
knowingpark 13 Dez. 2016 im 05:00