Seite 1 von 1

Python 2.6 und list comprehensions

Verfasst: Mittwoch 27. Juli 2016, 18:28
von Judge
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?

Re: Python 2.6 und list comprehensions

Verfasst: Mittwoch 27. Juli 2016, 19:33
von 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.

Re: Python 2.6 und list comprehensions

Verfasst: Mittwoch 27. Juli 2016, 19:47
von pillmuncher
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.

Re: Python 2.6 und list comprehensions

Verfasst: Mittwoch 27. Juli 2016, 22:20
von Sirius3
@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)

Re: Python 2.6 und list comprehensions

Verfasst: Mittwoch 27. Juli 2016, 23:52
von pillmuncher
@Sirius3: Ja, du hast recht.
@Judge: Was Sirius3 gesagt hat.

Re: Python 2.6 und list comprehensions

Verfasst: Donnerstag 28. Juli 2016, 08:48
von Judge
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 :)

Re: Python 2.6 und list comprehensions

Verfasst: Donnerstag 28. Juli 2016, 12:08
von snafu
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.

Re: Python 2.6 und list comprehensions

Verfasst: Donnerstag 28. Juli 2016, 12:25
von snafu
Ü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.

Re: Python 2.6 und list comprehensions

Verfasst: Donnerstag 28. Juli 2016, 12:58
von Sirius3
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())