Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 3126 – Entfernung der impliziten String-Verkettung

Autor:
Jim J. Jewett <JimJJewett at gmail.com>, Raymond Hettinger <python at rcn.com>
Status:
Abgelehnt
Typ:
Standards Track
Erstellt:
29. Apr. 2007
Post-History:
29-Apr-2007, 30-Apr-2007, 07-May-2007

Inhaltsverzeichnis

Ablehnungsbescheid

Dieser PEP wird abgelehnt. Es gab nicht genügend Unterstützung dafür, die zu entfernende Funktion ist nicht allzu schädlich, und es gibt einige Anwendungsfälle, die dadurch erschwert würden.

Zusammenfassung

Python hat viele seiner Parsing-Regeln von C übernommen. Dies war im Allgemeinen nützlich, aber es gibt einige einzelne Regeln, die für Python weniger nützlich sind und eliminiert werden sollten.

Dieser PEP schlägt vor, die implizite String-Verkettung, die nur auf der Aneinanderreihung von Literalen basiert, abzuschaffen.

Anstatt

"abc" "def" == "abcdef"

müssen Autoren explizit sein und entweder die Strings hinzufügen

"abc" + "def" == "abcdef"

oder sie verbinden

"".join(["abc", "def"]) == "abcdef"

Motivation

Ein Ziel für Python 3000 sollte es sein, die Sprache durch Entfernung unnötiger Features zu vereinfachen. Die implizite String-Verkettung sollte zugunsten bestehender Techniken gestrichen werden. Dies vereinfacht die Grammatik und ein einfacheres mentales Bild von Python für den Benutzer. Letzteres ist wichtig, damit die Sprache „in den Kopf passt“. Eine große Gruppe aktueller Benutzer weiß nicht einmal von der impliziten Verkettung. Von denen, die davon wissen, nutzt ein großer Teil sie gar nicht oder vermeidet sie gewohnheitsmäßig. Von denen, die sie sowohl kennen als auch nutzen, könnten nur sehr wenige mit Sicherheit die implizite Operator-Präzedenz angeben und unter welchen Umständen sie beim Kompilieren oder zur Laufzeit berechnet wird.

Historie oder Zukunft

Viele Python-Parsing-Regeln sind absichtlich mit C kompatibel. Dies ist ein nützlicher Standardwert, aber Sonderfälle müssen auf ihre Nützlichkeit in Python hin begründet werden. Wir sollten nicht mehr davon ausgehen, dass Python-Programmierer auch mit C vertraut sind, daher sollte die Kompatibilität zwischen Sprachen als Tie-Breaker und nicht als Begründung behandelt werden.

In C ist die implizite Verkettung die einzige Möglichkeit, Strings ohne einen (Laufzeit-)Funktionsaufruf zu einem Variablenwert zusammenzufügen. In Python können Strings auf standardmäßigere Python-Idiome wie + oder "".join zusammengefügt werden (und bleiben als unveränderlich erkennbar).

Problem

Die implizite String-Verkettung führt zu kürzeren Tupeln und Listen, als sie erscheinen; dies kann zu verwirrenden oder sogar stillen Fehlern führen. Wenn zum Beispiel eine Funktion mehrere Parameter akzeptiert, aber Standardwerte für einige davon anbietet

def f(fmt, *args):
    print fmt % args

Dies sieht wie ein gültiger Aufruf aus, ist aber keiner

>>> f("User %s got a message %s",
      "Bob"
      "Time for dinner")

Traceback (most recent call last):
  File "<pyshell#8>", line 2, in <module>
    "Bob"
  File "<pyshell#3>", line 2, in f
    print fmt % args
TypeError: not enough arguments for format string

Aufrufe dieser Funktion können stillschweigend falsch funktionieren

def g(arg1, arg2=None):
    ...

# silently transformed into the possibly very different
# g("arg1 on this linearg2 on this line", None)
g("arg1 on this line"
  "arg2 on this line")

Um Jason Orendorff zu zitieren [1]

Oh. Mir ist gerade aufgefallen, dass das hier oft vorkommt. Wo ich arbeite, benutzen wir scons, und jedes SConscript hat eine lange Liste von Dateinamen
sourceFiles = [
    'foo.c'
    'bar.c',
    #...many lines omitted...
    'q1000x.c']

Es ist ein häufiger Fehler, ein Komma wegzulassen, und dann beschwert sich scons, dass es „foo.cbar.c“ nicht findet. Dies ist ein ziemlich verwirrendes Verhalten, selbst wenn man ein Python-Programmierer *ist*, und nicht jeder hier ist einer.

Lösung

In Python sind Strings Objekte und sie unterstützen den __add__ Operator, daher ist es möglich, Folgendes zu schreiben:

"abc" + "def"

Da dies Literale sind, kann diese Addition immer noch vom Compiler wegoptimiert werden; der CPython-Compiler macht das bereits. [2]

Andere bestehende Alternativen umfassen mehrzeilige (dreifach maskierte) Strings und die join-Methode

"""This string
   extends across
   multiple lines, but you may want to use something like
   Textwrap.dedent
   to clear out the leading spaces
   and/or reformat.
"""


>>> "".join(["empty", "string", "joiner"]) == "emptystringjoiner"
True

>>> " ".join(["space", "string", "joiner"]) == "space string joiner"
True

>>> "\n".join(["multiple", "lines"]) == "multiple\nlines" == (
"""multiple
lines""")
True

Bedenken

Operator-Präzedenz

Guido hat angegeben [2], dass diese Änderung durch einen PEP gehandhabt werden sollte, da es ein paar Grenzfälle mit anderen String-Operatoren gab, wie z.B. dem %. (Vorausgesetzt, dass str % bestehen bleibt – es könnte zugunsten von PEP 3101 – Erweiterte String-Formatierung gestrichen werden. [3])

Die Lösung ist die Verwendung von Klammern zur Erzwingung der Präzedenz – dieselbe Lösung, die heute verwendet werden kann

# Clearest, works today, continues to work, optimization is
# already possible.
("abc %s def" + "ghi") % var

# Already works today; precedence makes the optimization more
# difficult to recognize, but does not change the semantics.
"abc" + "def %s ghi" % var

im Gegensatz zu

# Already fails because modulus (%) is higher precedence than
# addition (+)
("abc %s def" + "ghi" % var)

# Works today only because adjacency is higher precedence than
# modulus.  This will no longer be available.
"abc %s" "def" % var

# So the 2-to-3 translator can automatically replace it with the
# (already valid):
("abc %s" + "def") % var

Lange Befehle

… lesbare SQL-Abfragen aufbauen (was ich als lesbar betrachte) [4]
rows = self.executesql("select cities.city, state, country"
                       "    from cities, venues, events, addresses"
                       "    where cities.city like %s"
                       "      and events.active = 1"
                       "      and venues.address = addresses.id"
                       "      and addresses.city = cities.id"
                       "      and events.venue = venues.id",
                       (city,))

Alternativen sind wieder dreifach maskierte Strings, + und .join

query="""select cities.city, state, country
             from cities, venues, events, addresses
             where cities.city like %s
               and events.active = 1"
               and venues.address = addresses.id
               and addresses.city = cities.id
               and events.venue = venues.id"""

query=( "select cities.city, state, country"
      + "    from cities, venues, events, addresses"
      + "    where cities.city like %s"
      + "      and events.active = 1"
      + "      and venues.address = addresses.id"
      + "      and addresses.city = cities.id"
      + "      and events.venue = venues.id"
      )

query="\n".join(["select cities.city, state, country",
                 "    from cities, venues, events, addresses",
                 "    where cities.city like %s",
                 "      and events.active = 1",
                 "      and venues.address = addresses.id",
                 "      and addresses.city = cities.id",
                 "      and events.venue = venues.id"])

# And yes, you *could* inline any of the above querystrings
# the same way the original was inlined.
rows = self.executesql(query, (city,))

Reguläre Ausdrücke

Komplexe reguläre Ausdrücke werden manchmal in Bezug auf mehrere implizit verkettete Strings ausgedrückt, wobei jede Regex-Komponente auf einer anderen Zeile steht und von einem Kommentar gefolgt wird. Der Plus-Operator kann hier eingefügt werden, aber er macht die Regex schwerer lesbar. Eine Alternative ist die Verwendung der re.VERBOSE Option. Eine weitere Alternative ist der Aufbau der Regex mit einer Reihe von += Zeilen

# Existing idiom which relies on implicit concatenation
r = ('a{20}'  # Twenty A's
     'b{5}'   # Followed by Five B's
     )

# Mechanical replacement
r = ('a{20}'  +# Twenty A's
     'b{5}'   # Followed by Five B's
     )

# already works today
r = '''a{20}  # Twenty A's
       b{5}   # Followed by Five B's
    '''                 # Compiled with the re.VERBOSE flag

# already works today
r = 'a{20}'   # Twenty A's
r += 'b{5}'   # Followed by Five B's

Internationalisierung

Einige Internationalisierungswerkzeuge – insbesondere xgettext – wurden bereits für die implizite Verkettung speziell behandelt, jedoch nicht für die explizite Verkettung von Python. [5]

Diese Werkzeuge werden das (bereits legale) nicht extrahieren können

_("some string" +
  " and more of it")

aber oft einen Sonderfall für

_("some string"
  " and more of it")

Es sollte auch möglich sein, einfach eine zu lange Zeile (xgettext begrenzt Meldungen auf 2048 Zeichen [7], was weniger ist als die erzwungene Grenze von Python) oder dreifach maskierte Strings zu verwenden, aber diese Lösungen opfern etwas Lesbarkeit im Code

# Lines over a certain length are unpleasant.
_("some string and more of it")

# Changing whitespace is not ideal.
_("""Some string
     and more of it""")
_("""Some string
and more of it""")
_("Some string \
and more of it")

Ich sehe keine gute kurzfristige Lösung dafür.

Übergang

Die vorgeschlagenen neuen Konstrukte sind im aktuellen Python bereits legal und können sofort verwendet werden.

Der 2-zu-3-Übersetzer kann mechanisch ändern

"str1" "str2"
("line1"  #comment
 "line2")

in

("str1" + "str2")
("line1"   +#comments
 "line2")

Wenn Benutzer eines der anderen Idiome verwenden möchten, können sie das tun; da diese Idiome bereits in Python 2 legal sind, können die Bearbeitungen am Originalquellcode vorgenommen werden, anstatt den Übersetzer zu patchen.

Offene Fragen

Gibt es eine bessere Möglichkeit, externe Textextraktionswerkzeuge zu unterstützen, oder zumindest xgettext [6] im Besonderen?

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-3126.rst

Zuletzt geändert: 2025-02-01 08:51:20 GMT