Verbesserungsvorschläge für's besondere pprint.pformat() ?

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
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab da sowas:

Code: Alles auswählen

module_names = ["BaseHTTPServer","Bastion","CGIHTTPServer","ConfigParser",
    "Cookie","DocXMLRPCServer","HTMLParser","MimeWriter","Queue",
    #...
    "timeit","toaiff","token","tokenize","tputil","trace","tty","urllib",
    "urllib2","urlparse","user","uu","uuid","webbrowser","whichdb","xdrlib",
    "xmllib","xmlrpclib","zipfile"
]

print(module_names)

def format(l, indent=1, width=80):
    pos = 0
    indent_txt = " "*(indent*4)
    txt = indent_txt
    for i, item in enumerate(l):
        if len(txt)-pos > width:
            txt += "\n%s" % indent_txt
            pos=len(txt)
        txt += '"%s",' % item
    txt = txt.rstrip(",")
    return txt

module_names_txt = "module_names = [\n%s\n]" % (
    format(module_names)
)

print(module_names_txt)
Ausgabe:

Code: Alles auswählen

['BaseHTTPServer', 'Bastion', 'CGIHTTPServer', 'ConfigParser', 'Cookie', 'DocXMLRPCServer', 'HTMLParser', 'MimeWriter', 'Queue', 'timeit', 'toaiff', 'token', 'tokenize', 'tputil', 'trace', 'tty', 'urllib', 'urllib2', 'urlparse', 'user', 'uu', 'uuid', 'webbrowser', 'whichdb', 'xdrlib', 'xmllib', 'xmlrpclib', 'zipfile']
module_names = [
    "BaseHTTPServer","Bastion","CGIHTTPServer","ConfigParser","Cookie","DocXMLRPCServer",
    "HTMLParser","MimeWriter","Queue","timeit","toaiff","token","tokenize","tputil","trace",
    "tty","urllib","urllib2","urlparse","user","uu","uuid","webbrowser","whichdb","xdrlib",
    "xmllib","xmlrpclib","zipfile"
]
Diese Art der Formatierung möchte ich gern haben. (also die zweite print Ausgabe)

Aber meine Art das zu machen, erscheint mir umständlich... Verbesserungsvorschläge?

Normalerweise wäre das was für pprint. Aber da kommt das raus:

Code: Alles auswählen

>>> import pprint
>>> pprint.pprint(module_names)

['BaseHTTPServer',
 'Bastion',
 'CGIHTTPServer',
 'ConfigParser',
 'Cookie',
 'DocXMLRPCServer',
 'HTMLParser',
 'MimeWriter',
 'Queue',
 'timeit',
 'toaiff',
 'token',
 'tokenize',
 'tputil',
 'trace',
 'tty',
 'urllib',
 'urllib2',
 'urlparse',
 'user',
 'uu',
 'uuid',
 'webbrowser',
 'whichdb',
 'xdrlib',
 'xmllib',
 'xmlrpclib',
 'zipfile']

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Ein paar Kleinigkeiten:

`l` ist ein blöder Name. :-P

`i` und damit auch `enumerate()` wird überhaupt nicht verwendet.

Statt '"%s",' was Probleme mit Zeichenketten macht die " enthalten, oder auch ein paar andere Sonderzeichen, würde ich die `repr()`-Form der Listenelemente nehmen, also '%r,'.

Testfall der mindestens eine Zeichenkette enthält die länger als `width` ist?

Und das ``+=`` mit Zeichenketten in einer Schleife ist natürlich nicht so toll.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Und das ``+=`` mit Zeichenketten in einer Schleife ist natürlich nicht so toll.
Soll aber effizienter sein, als list .append() und .join()...

Neue Version:

Code: Alles auswählen

module_names = ["BaseHTTPServer","Bastion","CGIHTTPServer","ConfigParser",
    "Cookie","DocXMLRPCServer","HTMLParser","MimeWriter","Queue",
    #...
    "timeit","toaiff","token","tokenize","tputil","trace","tty","urllib",
    "urllib2","urlparse","user","uu","uuid","webbrowser","whichdb","xdrlib",
    "xmllib","xmlrpclib","zipfile"
]
print(module_names)

def format(items, indent=1, width=80):
    pos = 0
    indent_txt = " "*(indent*4)
    txt = indent_txt
    for item in items:
        if len(txt)-pos > width:
            txt += "\n%s" % indent_txt
            pos=len(txt)
        txt += '%r, ' % item
    txt = txt.rstrip(" ,")
    return txt

print("-"*79)
module_names_txt = "module_names = [\n%s\n]" % (
    format(module_names)
)
print(module_names_txt)
print("-"*79)
print(format(["foo"]))
print("-"*79)
Ausgabe:

Code: Alles auswählen

['BaseHTTPServer', 'Bastion', 'CGIHTTPServer', 'ConfigParser', 'Cookie', 'DocXMLRPCServer', 'HTMLParser', 'MimeWriter', 'Queue', 'timeit', 'toaiff', 'token', 'tokenize', 'tputil', 'trace', 'tty', 'urllib', 'urllib2', 'urlparse', 'user', 'uu', 'uuid', 'webbrowser', 'whichdb', 'xdrlib', 'xmllib', 'xmlrpclib', 'zipfile']
-------------------------------------------------------------------------------
module_names = [
    'BaseHTTPServer', 'Bastion', 'CGIHTTPServer', 'ConfigParser', 'Cookie', 'DocXMLRPCServer', 
    'HTMLParser', 'MimeWriter', 'Queue', 'timeit', 'toaiff', 'token', 'tokenize', 'tputil', 
    'trace', 'tty', 'urllib', 'urllib2', 'urlparse', 'user', 'uu', 'uuid', 'webbrowser', 
    'whichdb', 'xdrlib', 'xmllib', 'xmlrpclib', 'zipfile'
]
-------------------------------------------------------------------------------
    'foo'
-------------------------------------------------------------------------------

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: ``+=`` ist eben nicht effizienter, darum sollte man es ja nicht nehmen. CPython hat da zwar aktuell einen kleinen Trick implementiert, aber davon sollte man nicht ausgehen, das ist ein Implementierungsdetail das auch von der Art der Speicherverwaltung abhängt. Zeichenketten sind unveränderlich, also ist ein ``a = a + b`` immer ein neuanlegen einer Zeichenkette der Länge ``len(a) + len(b)`` und das umkopieren der Daten aus beiden Zeichenketten in diese neue Zeichenkette und dann eventuell das freigeben von `a` falls das sonst nirgends verwendet wird. So werden in einer Schleife immer die selben, leicht anwachsenden Daten im Speicher hin und her kopiert, was ineffizient ist.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

als liste ist es ein wenig länger:

Code: Alles auswählen

module_names = ["BaseHTTPServer","Bastion","CGIHTTPServer","ConfigParser",
    "Cookie","DocXMLRPCServer","HTMLParser","MimeWriter","Queue",
    #...
    "timeit","toaiff","token","tokenize","tputil","trace","tty","urllib",
    "urllib2","urlparse","user","uu","uuid","webbrowser","whichdb","xdrlib",
    "xmllib","xmlrpclib","zipfile"
]
print(module_names)

def format(items, indent=1, width=80):
    indent_txt = " "*(indent*4)
    txt = [indent_txt]
    pos = len(indent_txt)
    for item in items:
        if pos > width:
            txt.append("\n%s" % indent_txt)
            pos = 0
        item = '%r, ' % item
        pos += len(item)
        txt.append(item)

    txt = "".join(txt)
    txt = txt.rstrip(" ,")
    return txt

print("-"*79)
module_names_txt = "module_names = [\n%s\n]" % (
    format(module_names)
)
print(module_names_txt)
print("-"*79)
print(format(["foo"]))
print("-"*79)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@jens: Bei deiner zuletzt geposteten Version beachtest du ab der zweiten Zeile nicht mehr die Einrückung, wenn `pos` neu definiert wird. Mit anderen Worten: Innerhalb deiner `for`-Schleife solltest du aus dem ``pos = 0`` ein ``pos = len(indent_txt)`` machen, so wie du es wenige Zeilen zuvor ja auch schon machst. Sinnvollerweise würde ich da aber gar nicht mal die Länge des Strings ermitteln lassen, sondern "indent * 4" nehmen.

Ich persönlich hätte den Code wohl so in der Art geschrieben:

Code: Alles auswählen

def get_line_chunks(items, width, sep_len):
    start = 0
    consumed = 0
    for i, item in enumerate(items):
        additional = len(item) + sep_len
        if consumed + additional > width:
            yield items[start:i]
            start = i
            consumed = 0
        consumed += additional
    if items:
        # Last line with remaining items
        yield items[start:(i + 1)]

def get_formatted(items, indent=0, sep=', ', width=80):
    lines = []
    indent_string = ' ' * indent
    items = [repr(item) for item in items]
    chunks = get_line_chunks(items, width - indent, len(sep))
    for chunk in chunks:
        lines.append(indent_string + sep.join(chunk))
    return (sep.rstrip() + '\n').join(lines)

def run_test():
    items = [
        'BaseHTTPServer', 'Bastion', 'CGIHTTPServer', 'ConfigParser', 'Cookie', 'DocXMLRPCServer', 
        'HTMLParser', 'MimeWriter', 'Queue', 'timeit', 'toaiff', 'token', 'tokenize', 'tputil', 'trace', 'tty', 
        'urllib', 'urllib2', 'urlparse', 'user', 'uu', 'uuid', 'webbrowser', 'whichdb', 'xdrlib', 'xmllib', 
        'xmlrpclib', 'zipfile'
    ]
    template = 'modules = [\n{}\n]'
    formatted = get_formatted(items, indent=4)
    print(template.format(formatted))


if __name__ == '__main__':
    run_test()
Welche Variante schneller ist, müsste man nachmessen. Ob die paar Millisekunden Unterschied jetzt so wichtig sind, sei mal dahingestellt...

EDIT: Im Übrigen überschreitetet dein Algorithmus bei den angegebenen Testdaten gleich mehrfach die Maximallänge für eine Zeile, jens. Selbst wenn man die oben angesprochene vergessene Einrückung außen vor lässt, liegt man noch deutlich drüber. Deine erste (und längste) Zeile hat stolze 94 Zeichen, obwohl es nur 80 sein sollen. Das Ding ist halt, dass du die Zeilenumbrüche immer erst dann machst, wenn die Überlänge bereits erreicht wurde. Oder habe ich den Sinn von `width` nicht verstanden?
BlackJack

@snafu: Ich denke man muss nicht nachmessen denn hier geht es letztendlich um O(n) vs. O(n²). Ausserdem hat man beim Nachmessen das Problem das die derzeitige Optimierung bei CPython das Ergebnis drastisch verfälscht. Diese Optimierung ist aber nicht garantiert und kann nicht in jeder Implementierung vorgenommen werden. Eventuell auch nicht in kommenden CPython-Versionen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

snafu hat geschrieben:Im Übrigen überschreitetet dein Algorithmus bei den angegebenen Testdaten gleich mehrfach die Maximallänge für eine Zeile
Ja, da wirst du recht haben... Ich hab das nicht genau getestet :P

Geschwindikteit ist auch egal.

Ich hab das ganze nur lokal genutzt um eine Liste zu formatieren... Produktiv im Einsatz ist es nicht...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten