Vereinfachung immerwiederkehrender Ausdruck

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.
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:
        # ...
    # ...
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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?
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.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

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
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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
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.
Benutzeravatar
nkoehring
User
Beiträge: 543
Registriert: Mittwoch 7. Februar 2007, 17:37
Wohnort: naehe Halle/Saale
Kontaktdaten:

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)
[url=http://www.python-forum.de/post-86552.html]~ Wahnsinn ist auch nur eine andere Form der Intelligenz ~[/url]
hackerkey://v4sw6CYUShw5pr7Uck3ma3/4u7LNw2/3TXGm5l6+GSOarch/i2e6+t2b9GOen7g5RAPa2XsMr2
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Hi

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

Gruss
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

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
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

droptix hat geschrieben: Was macht `@staticmethod`
Eine Methode die mit "staticmethod" gekennzeichnet ist, kann ohne eine konkrete Instanz der Klasse aufgerufen werden. Da sollten sich genug Informationen in Texten über OOP finden lassen. Sonst gibt es hier auch noch eine kurze Beschreibung (unter "staticmethod"): HIER.
droptix hat geschrieben: und was bewirkt die Zeile mit `assert`?
Schau dir das mal in der Doku an. Hier wird es dafür verwendet, um zu prüfen ob die methoden auch tatsächlich in den geerbten Klassen implementiert wurden.
droptix hat geschrieben: Insgesamt aber auch keine schlechte Idee, gefällt mir! Ich lass mir das mal durch den Kopf gehen.
Naja, ist ja nicht meine Idee :D In der OOP ein gängiger Weg.

EDIT: Das böse Worte "eigentlich" entfernt :D
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

EyDu hat geschrieben:

Code: Alles auswählen

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

    def register(self, parser):
        assert 0, "implement me!"
...
1. Hier wäre eher ein raisen der ``NotImplementedError`` Exception angebracht.

Code: Alles auswählen

@staticmethod
    def test(expr)
        raise NotImplementedError

    def register(self, parser):
        raise NotImplementedError
Ein text wie "implement me!" ist auch eigentlich nicht notwendig, da an dem Typ der Exception schon zu erkenne ist worum es sich handelt.

2. ``assert``s werden zur zusicherung benutzt. Damit teste man Sachen die unmöglich passieren können/dürfen. Falls doch eine ``assert`` Bedingung ``False`` zurückgibt, dann kann von einem Programmfehler (Programmierfehler) ausgegangen werden.

Sehr guter Thread zu dem Thema: http://www.python-forum.de/post-53814.html#53814

IMHO würde ich daher eine nicht implementierte Funktion/Methode, nicht in diese Kategorie werfen und daher lieber die standard Exception ``NotImplementedError`` für solch eine Situation auslösen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

sape hat geschrieben: 1. Hier wäre eher ein raisen der ``NotImplementedError`` Exception angebracht.
Stimmt, da hatte ich nicht dran gedacht.
sape hat geschrieben: 2. ``assert``s werden zur zusicherung benutzt. Damit teste man Sachen die unmöglich passieren können/dürfen. Falls doch eine ``assert`` Bedingung ``False`` zurückgibt, dann kann von einem Programmfehler (Programmierfehler) ausgegangen werden.
Und das ist es ja auch. Aber da es eine entsprechene Exception gibt, sollte man diese auch nutzen.
Antworten