Python 2.6 und list comprehensions

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
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Hallo zusammen,

ich habe die im Betreff genannte Kombination:
  • Python 2.6.6
  • list comprehensions
Nun möchte ich gerne folgendes for-Konstrukt als list comprehension schreiben:

Code: Alles auswählen

from __future__ import print_function
from itertools import product


def allCaseCombosList(string):
    l = [(c, c.upper()) if not c.isdigit() else (c,) for c in string.lower()]
    return ["".join(item) for item in product(*l)]

choice = []
for x in ['test', 'live']:
    for y in allCaseCombosList(x):
        if y not in choice:
            choice.append(y)
Mir ist jetzt nicht klar, warum dieser Ansatz nicht funktioniert:

Code: Alles auswählen

from __future__ import print_function
from itertools import product


def allCaseCombosList(string):
    l = [(c, c.upper()) if not c.isdigit() else (c,) for c in string.lower()]
    return ["".join(item) for item in product(*l)]

choice = set([word for word in allCaseCombosList(element) for element in ['test', 'live']])
Da bekomme ich:

Code: Alles auswählen

NameError: name 'element' is not defined
Was mache ich falsch?
BlackJack

@Judge: Das hat nichts mit Python 2.6 zu tun, sondern einfach nur damit das man es mit „list comprehensions“ (LCs) nicht übertreiben sollte, insbesondere beim verschachteln von mehr als einem ``for`` innerhalb einer LC. Das wird auch in 2.7 oder 3.x nicht lauffähig.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Alles in LCs zu quetschen führt zu unübersichtlichem Code. Mach lieber sowas:

Code: Alles auswählen

from __future__ import print_function
from itertools import product

def case_variations(word):
    for char in word:
        if char.isalpha():
            yield char.lower(), char.upper()
        else:
            yield char,

def generate_spellings(*words):
    words = [word.lower() for word in words]
    todo = set(words)
    for word in words:
        if word in todo:
            todo.discard(word)
            for chars in product(*case_variations(word)):
                yield ''.join(chars)

print(list(generate_spellings('test', 'live', 'TesT')))
Ergebnis:

Code: Alles auswählen

['test', 'tesT', 'teSt', 'teST', 'tEst', 'tEsT', 'tESt', 'tEST', 'Test', 'TesT', 'TeSt', 'TeST', 'TEst', 'TEsT', 'TESt', 'TEST', 'live', 'livE', 'liVe', 'liVE', 'lIve', 'lIvE', 'lIVe', 'lIVE', 'Live', 'LivE', 'LiVe', 'LiVE', 'LIve', 'LIvE', 'LIVe', 'LIVE']
Statt doppelte Ergebnisse herauszufiltern, sollte man lieber gleich am Anfang dafür sorgen, dass es keine geben kann. Auch ist str.isalpha() hier besser als str.isdigit(), weil so auch Bindestriche und Satz- und Leerzeichen korrekt behandelt werden, nicht nur Ziffern.
In specifications, Murphy's Law supersedes Ohm's.
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

@pillmuncher: ich finde es ja immer übersichtlicher, ein Set aufzubauen, statt es zu leeren. Bei vielen Wörtern spart man sich dann auch das Erzeugen einer weiteren Liste:

Code: Alles auswählen

def generate_spellings(*words):
    seen = set()
    for word in words:
        word = word.lower()
        if word not in seen:
            seen.add(word)
            for chars in product(*case_variations(word)):
                yield ''.join(chars)
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Sirius3: Ja, du hast recht.
@Judge: Was Sirius3 gesagt hat.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

BlackJack hat geschrieben:Das hat nichts mit Python 2.6 zu tun,
Sag ich ja garnicht - sorry, wenn das impliziert wurde.
BlackJack hat geschrieben:sondern einfach nur damit das man es mit „list comprehensions“ (LCs) nicht übertreiben sollte, insbesondere beim verschachteln von mehr als einem ``for`` innerhalb einer LC. Das wird auch in 2.7 oder 3.x nicht lauffähig.
Alles klar, ich kannte die Grenzen von LCs nicht. Werde mir das merken, das man nur einen for-Level ersetzen sollte.

@Sirius3, pillmuncher: Perfekt! Vielen Dank für den Code - mehr als ich mir durch die Frage erhofft habe :)
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Judge hat geschrieben:Alles klar, ich kannte die Grenzen von LCs nicht.
Es ging hier aber nicht um eine technische Begrenzung, sondern um die Begrenztheit des menschlichen Fassungsvermögens. Soll heißen: Nur weil du etwas in einer mehrfach verschachtelten LC ausdrücken kannst, solltest du es noch lange nicht tun.

IMHO ist eine zweite Ebene bei LCs noch okay, solange es nicht zuviele Nebenbedingungen gibt und natürlich solange man weiß, was man tut. Man sollte jedoch nicht krampfhaft versuchen, jede `for`-Schleife, die eine neue Sequenz erzeugt, als eine einzige LC auszudrücken.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Übrigens hier mal deine problematische LC als normale `for`-Schleife ausgedrückt:

Code: Alles auswählen

choice = set([word for word in allCaseCombosList(element) for element in ['test', 'live']])
# ... wird zu
tmp = []
for word in allCaseCombosList(element):
    for element in ['test', 'live']:
        tmp.append(word)
choice = set(tmp)
Wie du siehst, musst du die `for`-Ausdrücke von links nach rechts ineinander verschachteln. Das am weitesten links stehende `for` gehört also zur äußersten Schleife. Und dann wird dir sicherlich auch klar, warum es zu dem `NameError` kommt.

Übrigens kann man die eckigen Klammern bei dem `set()` auch weglassen. Dann wird keine temporäre Liste als Zwischenstation erzeugt.
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ganze mal ohne for:

Code: Alles auswählen

from itertools import imap

def generate_cases(c):
    return set((c.lower(), c.upper()))

def generate_combinations(element):
    return set(imap("".join, product(*map(generate_cases, element))))

choice = reduce(set.union, imap(generate_combinations, ['test', 'live']), set())
Antworten