Seite 1 von 2

Verfasst: Dienstag 26. Dezember 2006, 13:27
von rayo
Hi

Deine Funktion sollte nicht False zurückgeben bei Fehlern, sondern entweder die Exception einfach weitergeben (nichts machen) oder die Exception von os.listdir abfangen mit try except und eine eigene Exception auslösen und diese dann ausserhalb abfangen.

Code: Alles auswählen

class MeineException(Exception):
    pass
def list_dir(path):
    try:
        c = os.listdir(path)
    except Exception,e:
        raise MeineException("Fehler bei listdir")
    r = {
        'dirs': [],
        'files': [],
        'links': []
    }
    for i in c:
        item = os.path.join(path, i)
        if os.is_link(item):
            r['links'].append(i)
        elif os.is_dir(item):
            r['dirs'].append(i)
        elif os.is_file(item):
            r['files'].append(i)
    return r
try:
    a = list_dir(mein_pfad)
    print a
except MeineException, e:
    print 'listdir fehlgeschlagen'
So finde ich das am schönsten. Natürlich solltest du noch einen besseren Namen für die Exception ausdenken.

Gruss

PS: für den rekursiven durchlauf von Verzeichnissen ist os.walk ganz gut geeignet ;)

Re: Vereinfachung immerwiederkehrender Ausdruck

Verfasst: Dienstag 26. Dezember 2006, 14:41
von BlackJack
droptix hat geschrieben:In einem Fall habe ich ne Funktion ListDir, die mir den Verzeichnisinhalt eines Pfades einliest. Wenn alles klar ging, liefert die Funktion ein Dict zurück. Wenn dabei was schief ging (Pfad falsch, keine Zugriffsrechte etc.), dann eben False:
Ich schliesse mich rayo an: Ausnahmen wurden eingeführt um spezielle Rückgabewerte, die einen Fehler signalisieren, loszuwerden. Sie zu behandeln indem ein "Fehlerwert" zurückgegeben wird, ist ein Rückschritt. Behandele die Ausnahme lieber in Deiner `walk()`-Funktion.

Aber werde auf jeden Fall den Identitätstest auf `False` los. `True` und `False` sind nur Namen, es handelt sich nicht um Singletons wie bei `None`, also sollte man auch nicht auf Identität sondern auf Gleichheit testen.

Ich hätte die Funtkionen so geschrieben:

Code: Alles auswählen

def list_dir(path):
    dirs = list()
    files = list()
    links = list()
    for name in os.listdir(path):
        full_name = os.path.join(path, name)
        if os.path.islink(full_name):
            links.append(name)
        elif os.path.isdir(full_name):
            dirs.append(name)
        elif os.path.isfile(full_name):
            files.append(name)
    return dirs, files, links


def walk(path):
    try:
        dirs, dummy, dummy = list_dir(path)
        print path
        for dirname in dirs:
            walk(os.path.join(path, dirname))
    except IOError:
        pass
Wenn man etwas mehr Kontrolle über die Fehlerbehandlung in `walk()` haben möchte, kann man auch eine "callback"-Funktion einführen, die entscheidet was im Fehlerfall passieren soll:

Code: Alles auswählen

def walk(path, onerror=None):
    try:
        dirs, dummy, dummy = list_dir(path)
        print path
        for dirname in dirs:
            walk(os.path.join(path, dirname))
    except IOError, error:
        if onerror is not None:
            if onerror == 'raise':
                raise
            else:
                onerror(error)

Re: Vereinfachung immerwiederkehrender Ausdruck

Verfasst: Dienstag 26. Dezember 2006, 16:45
von sape
droptix hat geschrieben:[...]
Macht bitte keine Wissenschaft draus... mit foo() meinte ich eine simple Funktion und mit Prozedur die Aufgabe, die die Funktion zu erfüllen hat. Wenn die Aufgabe fehlschlägt (z.B. Division durch Null oder was weiß ich sonst noch), dann liefert die Funktion halt False zurück.
Du hast von einer Methode innerhalb von `foo`geredet und da bin ich dann davon ausgegangen das `foo` eine Klasse sein muss, weil alle Funktionen innerhalb einer Klasse eben Methode genannt werden. Daher meine frage. Also Sorry, war mein Fehler...
In einem Fall habe ich ne Funktion ListDir, die mir den Verzeichnisinhalt eines Pfades einliest. Wenn alles klar ging, liefert die Funktion ein Dict zurück. Wenn dabei was schief ging (Pfad falsch, keine Zugriffsrechte etc.), dann eben False:
Da würde ich eine Exception liefern! Grade bei solchen Sachen wie "Datei nicht vorhanden, Falscher Pfad, ungültige Dateizugriffsrechte" _müssen_ Exceptions (die darauf hinweißen) ausgelöst werden. Schließlich ist das ja eine Ausnahme verhalten. Ein schlichtes `False` ist einfach ungenügend und man weiß dann nicht woran der Fehler lag und kann dann nicht darauf entsprechend richtig reagieren.

Versuch deine Sachen so zu schreiben das man die auch unabhängig von deinem Programm benutzen könnte und denke dann dabei was du gerne erwarten würdest, von einer nicht von dir geschriebenen Klasse/Funktion, wenn eine Sache nicht richtig ist? -> Würdest du lieber ein `False` wollen Für 3 Unterschiedliche Fehler oder lieber eine Konkrete Exception (jeweile eine eigene für unterschiedliche Fehler), mit dessem Auslösen du dann jeweils drauf entsprechend reagieren kannst?

Davon abgesehen bietet es sich an `True`und `False`nur bei `is_xyz` als Rückgabewert zu benutzen. Es gibt kaum ncoh adere Sinnvolle Sachen wo sich das anbietet würde. (Meiner Meinung nach).

Und wie BlackJack schon schrieb, wurde das Exceptionhandling (ich glaube C++ war so mit der ersten Sprachen die das angeboten hat) eingeführt, damit wir von dieser lästigen Fehlercode Überprüfung verschont werden.

lg

Verfasst: Mittwoch 27. Dezember 2006, 11:44
von droptix
rayo hat geschrieben:PS: für den rekursiven durchlauf von Verzeichnissen ist os.walk ganz gut geeignet ;)
In meinem Fall ungenügend, weil ich auch Symlinks auswerten möchte und später auch Junctions.
BlackJack hat geschrieben:Ich schliesse mich rayo an: Ausnahmen wurden eingeführt um spezielle Rückgabewerte, die einen Fehler signalisieren, loszuwerden. Sie zu behandeln indem ein "Fehlerwert" zurückgegeben wird, ist ein Rückschritt.
Hmmm, manchmal ist das einfach unangenehm, viele try-except-Statements zu verwenden, wenn mir ein einfaches False reichen würde. Denn ich möchte gar nicht weiter auswerten, warum os.listdir() eine Ausnahme erzeugte. Wenn ein Fehler vorkommt, wird das aktuelle Verzeichnis eben übersprungen.
BlackJack hat geschrieben:Aber werde auf jeden Fall den Identitätstest auf `False` los. `True` und `False` sind nur Namen, es handelt sich nicht um Singletons wie bei `None`, also sollte man auch nicht auf Identität sondern auf Gleichheit testen.
Uh, was ist der genaue Unterschied zwischen Gleicheit und Identität? Und was ist ein Singleton?
BlackJack hat geschrieben:Ich hätte die Funtkionen so geschrieben:

Code: Alles auswählen

def list_dir(path):
    dirs = list()
    files = list()
    links = list()
    for name in os.listdir(path):
        full_name = os.path.join(path, name)
        if os.path.islink(full_name):
            links.append(name)
        elif os.path.isdir(full_name):
            dirs.append(name)
        elif os.path.isfile(full_name):
            files.append(name)
    return dirs, files, links


def walk(path):
    try:
        dirs, dummy, dummy = list_dir(path)
        print path
        for dirname in dirs:
            walk(os.path.join(path, dirname))
    except IOError:
        pass
Ist netter, geb ich zu. Bei except passiert hier halt nix. Aber es wäre zumindest vorbereitet, wenn das später mal interessant werden sollte... :) Macht es hier eigentlich einen Unterschied, ob man foo = [] oder foo = list() verwendet?
sape hat geschrieben:Du hast von einer Methode innerhalb von `foo`geredet und da bin ich dann davon ausgegangen das `foo` eine Klasse sein muss, weil alle Funktionen innerhalb einer Klasse eben Methode genannt werden.
Nee, hab ich nie so erwähnt. Daher verwunderte mich deine Interpretationsfreude :D

Verfasst: Mittwoch 27. Dezember 2006, 12:16
von Leonidas
droptix hat geschrieben:Macht es hier eigentlich einen Unterschied, ob man foo = [] oder foo = list() verwendet?
Nein, im Großen und Ganzen eigentlich nicht. Aber guck doch mal in die [wiki]FAQ#GibtEsEinenUnterschiedZwischenlistBzwdict[/wiki], dort wird genau diese Frage diskutiert.

Verfasst: Mittwoch 27. Dezember 2006, 13:20
von BlackJack
droptix hat geschrieben:Uh, was ist der genaue Unterschied zwischen Gleicheit und Identität?
Das ist der Unterschied zwischen "das selbe" und "das gleiche", den es laut Duden im deutschen leider nicht mehr gibt. :-(

Mit ``is`` testet man, ob es sich tatsächlich um das selbe Objekt handelt. Es gibt also nur ein einziges Objekt, das diese Eigenschaft erfüllt. Mit ``==`` testet man auf Gleichheit, was nicht so scharf definiert ist, bzw. in Python vom Programmierer festgelegt werden kann. Beispiel:

Code: Alles auswählen

In [38]: a = 1

In [39]: b = 1.0

In [40]: type(a), type(b)
Out[40]: (<type 'int'>, <type 'float'>)

In [41]: a is b
Out[41]: False

In [42]: a == b
Out[42]: True
`a` und `b` sind ganz offensichtlich zwei verschiedene Objekte, sogar von unterschiedlichem Typ, aber der Wert ist gleich.

`True` und `False` gab es früher in Python nicht, darum haben sich viele Leute in ihren Modulen die Namen `True` und `False` an "wahre"/"falsche" Werte gebunden, z.B. 1 und 0, um den Quelltext lesbarer zu machen. Deshalb kann es verschiedene `True`-Objekte in einem Programm geben, die mit ``is`` verglichen, "falsch" ergeben aber mit ``==`` "wahr" sind.
In [43]: 1 is True
Out[43]: False

In [44]: 1 == True
Out[44]: True
Auf Identität sollte man wirklich nur prüfen, wenn man exakt ein ganz bestimmtes Objekt erwartet. Das ist bei `None` gegeben, weil `None` ein Singleton ist, also ein Objekt von dem es nur ein Exemplar gibt, und der Name `None` auch nicht an andere Objekte gebunden werden kann:

Code: Alles auswählen

In [45]: None = "hallo"
------------------------------------------------------------
SyntaxError: assignment to None
Ansonsten kann man ``is`` noch in Graphenalgorithmen verwenden um zum Beispiel festzustellen, ob man bei einem Objekt, und genau dem Objekt, schon einmal vorbeigekommen ist, oder um bei Algorithmen den Knoten auf dem die Methode ausgeführt wird auszuschliessen, indem man auf ``is not self`` testet.

Eine weitere Einsatzmöglichkeit sind "Sentinel"-Objekte, deren einziger Zweck es ist, in einem Identitätsvergleich verwendet zu werden. Zum Beispiel wenn man eine Funktion schreibt, die ein optionales Argument erhält, welches wirklich jeden Wert annehmen darf, also auch `False`, `None` und so weiter, aber nur berücksichtigt werden soll, wenn es beim Aufruf angegeben wurde. Das kann man z.B. so lösen:

Code: Alles auswählen

NOTHING = object()  # Absolut einzigartiges Objekt

def spam(eggs, opt=NOTHING):
    # ...
    if opt is not NOTHING:
        # ...
    # ...

Zurück zum Ursprungsthema

Verfasst: Freitag 2. März 2007, 02:27
von droptix
Zurück zum Ursprungsthema! Mir ist wieder eingefallen, wo ich diese PHP-ähnliche Syntax gern hätte bzw. wo sie wirklich Sinn machen würde, weil sie die Lesbarkeit des Codes verbessert. Vielleicht habt ihr aber auch wieder eine alternative Lösung… ich bin gespannt:

1) Momentan mache ich folgendes:

Code: Alles auswählen

x="blah"
f=foo(x)
if f:
	print f
else:
	b=bar(x)
	if b:
		print b
	else:
		print "strange"
Ich prüfe also zuerst, ob das return von `foo(x)` einen Wert liefert. Wenn dem so ist, das Ergebnis "ausdrucken". Wenn nicht, muss ich weiterhin prüfen, ob dann vielleicht `bar(x)` einen Wert liefert. Wenn das auch nicht der Fall ist, dann entspricht `x` nicht meinen Erwartungen für die Weiterverarbeitung.

Man könnte das Ganze auch so schreiben:

Code: Alles auswählen

x="blah"
f=foo(x)
b=bar(x)
if f:
	print f
elif b:
	print b
else:
	print "strange"
Aber: Wenn `bar()` eine sehr lange Durchlaufzeit hat und ich schon nach `if f:` ein sinnvolles Ergebnis erhalten würde, hätte ich mir den Durchlauf von `bar(x)` schenken können. Das mal noch in einer Schleife vorgestellt kann extreme Wartezeiten produzieren.

2) Nach der PHP-Methode müsste es in Python-Code ungefähr so aussehen:

Code: Alles auswählen

x="blah"
if f=foo(x):
	print f
elif b=bar(x):
	print b
else:
	print "strange"
Also gleich im `if`-Statement kann der Rückgabewert aufgefangen werden, um ihn anschließend weiter zu verarbeiten.

Wenn sich das jetzt nicht nur auf 2 Funktionen beschränkt, die `x` überprüfen sollen, wird der Python-Code (s. erstes Snippet) extrem lang und sehr verschachtelt. Der PHP-Code bleibt dagegen übersichtlich.

Was meint ihr? Gibt's eine Alternative für meinen Wunsch in Python?

Verfasst: Freitag 2. März 2007, 08:11
von BlackJack
"Gehe eine Reihe von Funktionen durch, bis eine davon mit dem `x` etwas brauchbares liefert" klingt sehr nach einer Schleife:

Code: Alles auswählen

    for func in (foo, bar, baz):
        result = func(x)
        if result:
            print result
            break
    else:
        print "strange"
Die Liste der Funktionen lässt sich einfach an genau einer Stelle erweitern.

Verfasst: Freitag 2. März 2007, 10:17
von droptix
Geile Idee! :D

Nachschlag: Wenn ich nun aber nicht nur `print x` brauche sondern in jedem Fall eine individuelle Auswertung benötige, was dann? Eine Schleife geht dann nicht mehr. Übertragen meine ich sowas:

Code: Alles auswählen

x="lala"
if f=foo(x):
    return blah(f)
elif b=bar(x):
    return blubb(b)
else:
    return None

Selbstantwort

Verfasst: Freitag 2. März 2007, 10:29
von droptix
Hm, zur Not vielleicht so hier:

Code: Alles auswählen

ok = False
for func in (foo, bar):
    result = func(x)
    if result:
        ok = True
        break

if ok:
    if func == foo:
        return blah(result)
    elif func == bar:
        return blubb(result)
    return False
else:
    return False
Das sieht aber auch unschön aus und man verwurstet die Funktionsnamen doppelt: einmal in der Schleife und dann unten in der `if`-Auswertung.

Verfasst: Freitag 2. März 2007, 11:20
von rayo
Hi

So siehts schöner aus

Code: Alles auswählen

functions = [(foo, blah), (bar, blubb)]

for first,second in functions:
    d = first(x)
    if d:
        return second(d)
Gruss

Verfasst: Freitag 2. März 2007, 11:42
von droptix
Ach ihr wieder :wink: Ich hatte sowas schon geahnt.

Ich wollte es wieder mal extra knapp halten, aber ich meinte es eigentlich so, dass jeder Fall eine individuelle Auswertung benötigt, die man nicht wieder unbedingt in eine Funktion packen muss. Zwei Zeilen Code pro Fall könnten da schon reichen, was eine Extra-Funktion unnötig macht. Also hier mal ein sinnvolleres Beispiel:

Code: Alles auswählen

# who knows what code happened before...

res = "strange"
x = "lala"
if r = foo(x):
	# this result could be a string
	res = "result is a foo: %s" % r
elif r = bar(x):
	# maybe this result is a number
	res = "result is a percent value: %s%%" % constantDefinedBefore * r / 100
elif r = baz(x):
	# or result is a special object
	res = "result is an object having the name %s and the following properties:" % r.name
	res += r.list_properties()
print res

Verfasst: Freitag 2. März 2007, 21:11
von BlackJack
Schreiben muss man den Code so oder so und ein zusätzliches ``lambda x:`` ist IMHO vertretbar. Oder eben kleine (lokale) Funktionen für etwas komplexere Fälle.

Verfasst: Samstag 3. März 2007, 08:13
von nkoehring
Leonidas hat geschrieben:
droptix hat geschrieben:Macht es hier eigentlich einen Unterschied, ob man foo = [] oder foo = list() verwendet?
Nein, im Großen und Ganzen eigentlich nicht. Aber guck doch mal in die [wiki]FAQ#GibtEsEinenUnterschiedZwischenlistBzwdict[/wiki], dort wird genau diese Frage diskutiert.
Ich verwende der Lesbarkeit meistens list() und dict() ... falls das fuer dich eine Rolle spielen sollte :)

Ansonsten hab ich gerade keine Lust gehabt das alles zu lesen und ueberflog es nur mal schnell.

Kann es vielleicht sein, dass logische Operatoren vielleicht was mit dem zu tun haben, was du suchst (ich hoffe, ich steuere jetzt nicht and der Threadentwicklung vorbei :roll: )?

Code: Alles auswählen

a=foo()
if a: print a
ginge auch so hier:

Code: Alles auswählen

a=foo()
print a or "a is false"
oder so hier:

Code: Alles auswählen

a = foo() or "a is false"
print a
...ich hab das jetzt nicht extra probiert... aber normalerweise mache ich das oefter so ;)
Bitte kritisiert mich, wenn das als "unpythonisch", "unlesbar", "schlicht und einfach bloed" oder sowas gilt... dann beruecksichtige ich das vielleicht in Zukunft 8)

Verfasst: Samstag 3. März 2007, 19:49
von droptix
nkoehring hat geschrieben:(ich hoffe, ich steuere jetzt nicht and der Threadentwicklung vorbei :roll: )?
Du triffst es zwar nicht ganz, aber das bringt mich grad auf eine Idee:

Code: Alles auswählen

r = foo(x) or bar(x) or baz(x) or None
Geht das? Es ist zwar trotzdem nicht ganz brauchbar für meine Zwecke, aber eine coole Idee wie ich finde. Ich kann damit immernoch nicht individuell auf eine Aktion reagieren, da ich am Ende nicht weiß, welche Funktion in der OR-Verknüpfung einen gültigen Rückgabewert erzeugt hat.

Verfasst: Samstag 3. März 2007, 22:58
von rayo
Hi

In welchem Zusammenhang brauchst du solch ein verhalten?
Irgendwie hab ich das Gefühl, dass man das ganze anders gestalten könnte.

Gruss

Verfasst: Sonntag 4. März 2007, 00:10
von droptix
rayo hat geschrieben:In welchem Zusammenhang brauchst du solch ein verhalten?
Ich parse eine bestimmte Dateistruktur. Ähnlich zu INI-Dateien gibt es darin Sektionen, Schlüssel und Werte, also z.B. sowas hier:

Code: Alles auswählen

[Foo]
spam=eggs
Kennen viele von der Samba- oder PHP-Config… Nicht dass mir jetzt jemand mit dem ConfigParser ankommt :D es ist keine Config-Datei und daher lässt sie sich damit auch nicht parsen.

Ich gehe also jede Zeile der Datei durch und prüfe deren Aufbau mittels RegExe. Beispiel:

Code: Alles auswählen

reSection = re.compile(r"^\[([^\]]*)\]$")
reKeyValue = re.compile(r"^([^\=]+)\=(.*)$")

def is_section(line):
    m = reSection.search(line)
    if m:
        # return section name
        return m.group(1)
    else:
        return False

def is_key_value(line):
    m = reKeyValue.search(line)
    if m:
        # return key and value as a tuple
        return m.groups()
    else:
        return False
Gut soweit. Neben Sections und Key/Value-Paaren gibt es noch eine Menge von Sonderformen. Es werden also mehr als zwei Prüffunktionen auf die Zeile angesetzt, aber für's erste soll das reichen. Nun frage ich also jede Zeile ab:

Code: Alles auswählen

for line in lines:
    # check if the line is a section
    section = is_section(line)
    if section:
        # create a new section object and submit the name
        so = SectionObject(section)
        # register the section at the parser
        myParser.register_section(so)
    else:
        # check if the line is a key/value pair
        keyval = is_key_value(line)
        if keyval and s:
            # parse key only if a section object has already been created
            key, value = keyval
            # create a new key object and submit the value if any
            ko = KeyObject(key)
            if value:
                ko.value = value
            # append the key object to the last section
            s.add_key(ko)
        else:
            # more options later...
            more = is_more(line)
            # ...
            else:
                # this line is not interesting, so ignore it
                pass
Schon bei drei Prüffunktionen wird die Verschachtelung recht unübersichtlich, weil ich nicht gleich in einem `elif` den Rückgabewert der Funktion auffangen kann, um damit weiter zu arbeiten. Daher muss ich immer erst ein `else` dazwischenklemmen… jedenfalls sehr unkomfortabel.

Verfasst: Sonntag 4. März 2007, 10:19
von rayo
Hi

Also ich würde Funktionen schreiben (z.B. handleSection, ...) und eine Liste mit [(is_section, handleSection), ...] machen und mit einer schleife durchgehen.

Da du dass aber nicht möchtest würde ich dir vorher noch continue nahelegen, damit gibts keine verschachtelungen

Code: Alles auswählen

for line in lines:
    # check if the line is a section
    section = is_section(line)
    if section:
        # create a new section object and submit the name
        so = SectionObject(section)
        # register the section at the parser
        myParser.register_section(so)
        continue

    keyval = is_key_value(line)
    if keyval and s:
        # parse key only if a section object has already been created
        key, value = keyval
        # create a new key object and submit the value if any
        ko = KeyObject(key)
        if value:
            ko.value = value
        # append the key object to the last section
        s.add_key(ko)
        continue

    # more options later...
    more = is_more(line)
    if more:
        continue
    
    #code ohne match
Gruss

Verfasst: Sonntag 4. März 2007, 10:46
von EyDu
Das Folgende ist zum Beispiel eine recht allgemeingültige Lösung für das Problem. Über die Verwendug von "break" darf sich natürlich ausgibig ausgelassen werden.

Code: Alles auswählen

class IniComponent:
    @staticmethod
    def test(expr)
        assert 0, "implement me!"

    def register(self, parser):
        assert 0, "implement me!"

class Section:
    @staticmethod
    def test(expr):
        #test here
        return ...

    def __init__(self, line):
        #...

    def register(self, parser):
        parser.register_section(self)

class KeyValue:
    @staticmethod
    def test(expr):
        #test here
        return ...

    def __init__(self, line):
        #...

    def register(self, parser):
        parser.register_key_value(self)

for line in lines:
    for i in [Section, KeyValue]:
        if i.test(line):
            i(line).register(parser)
            break
    else:
        print "invalid component"
EDIT: Ach ja, die "return ..." geben natürlich "True" oder "False" zurück. Alternativ könnte man auch gleich eine Instanz erzeugen und registrieren, sieht dann aber irgendwie seltsam aus.

Verfasst: Sonntag 4. März 2007, 11:13
von droptix
Oha, da gibt's gleich noch ein paar Fragen zum Code :D
EyDu hat geschrieben:

Code: Alles auswählen

class IniComponent:
    @staticmethod
    def test(expr)
        assert 0, "implement me!"
Was macht `@staticmethod` und was bewirkt die Zeile mit `assert`?

Insgesamt aber auch keine schlechte Idee, gefällt mir! Ich lass mir das mal durch den Kopf gehen.