Funktionsrückgabe abhängig vom Aufruf

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
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

Hallo Leute,

gibt es eine Möglichkeit die Anzahl der Rückgabewerte einer Funktion abhängig von der Anzahl der Ausgabevariablen beim Aufruf der Funktion zu steuern?
Mein Minimalbeispiel würde so aussehen:

Code: Alles auswählen

def test(a,b):
    c = a*b
    d = a+b
    e = a-b
    return c,d,e

c,d,e = test(2,3)
print c,d,e
c,d   = test(2,3)
print c
(In Matlab funktioniert das also gibt es doch bestimmt auch eine Möglichkeit das Ganze in Python umzusetzen)
BlackJack

@schneitzmaster: Das geht in Python nicht. Man würde die nicht benötigten Werte an den Namen `_` binden, also ``c, d, _ = test(2, 3)`` wenn man den dritten Wert nicht braucht. Andere Alternative ist ein `collections.namedtuple()` als Rückgabewert.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

mhh mist, die funktion die ich jetzt doch noch einen rückgabewert mehr hat wird in vielen anderen unterprogrammen aufgerufen... das müsste ich jetzt alles im gesamten code nochmal ändern.
schade.... am besten ist wohl gleich immer die rückgabewerte einer funktion als tuple zu nehmen und dann auf die einzelnen tuple einträge zugreifen. so ist man flexibler oder wie würdest du das machen?
Man kann ja nicht immer a priori wissen was die funktion später einmal alles können soll...
BlackJack

@schneitzmaster: Ich würde keine Funktionen schreiben in der so viele Einzelwerte zurückgegeben werden.

Kann man die nicht sinnvoll zu Datentypen zusammenfassen? Und wenn Tupel, dann wie gesagt ein `collections.namedtuple()`, denn man möchte ganz bestimmt nicht den Quelltext mit vielen kryptischen Indexzugriffen mit magischen Indexwerten vollkleistern.

Man weiss in der Regel schon was eine Funktion später alles können soll. So wenig wie möglich und so viel wie nötig. Eine Funktion oder Methode sollte im Idealfall genau *eine*, übersichtliche, in sich geschlosse Sache erledigen. Und ist in der Regel höchtens 25 Zeilen lang (reine Codezeilen), in einigen Fällen auch mal bis zu 50, aber länger eher wirklich nicht.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@BlackJack: Vielen Dank für deine Hinweise. Meine Funktion soll in diesem Fall eine Textdatei lesen und Daten extrahieren (Geometrie Daten). Diese Daten sind natürlich unterschiedlichen Typs. Große Teile lasse ich mir als Klassen zurückgeben, andere wiederrum in Form von arrays oder strings.

Ich gebe dir Recht, das bei einem Tuple die ganze Indizierung unübersichtlich sein wird. Deinen Vorschlag mit dem 'collections.namedtuple()' habe ich nicht ganz verstanden. Könntest du dazu ein Minimalbeispiel geben?

Bei der Entwicklung meines Programs habe ich die "Lese"-Funktion zunächst so geschrieben, dass sie nur die Information extrahiert die benötigt wird (nach der prämisse so wenig wie möglich). Leider ist bei der Entwicklung meines Programs nun der Fall eingetreten, dass ich doch noch mehr Information benötige. Ich glaube nicht, dass man das immer schon vorher absehen kann, insbesondere wenn sich die Entwicklung eines Programs über einen längeren Zeitraum erstreckt...also ich kann das nicht.

Bzgl. der Code Zeilen verstehe ich den Ansatz eine Funktion mit max. 50 Code zeilen auszustatten nicht. Gerade beim lesen komplexer text-dateien mit vielen abschnitten ist es doch nur möglich die Hauptfunktionen klein zu halten, wenn man mehrere Funktionen verschachtelt. Das wiederrum finde ich an diesem Beispeil für die Aufgabe "extrahiere Daten aus Textdatei" unübersichtlich.... ist aber wohl ansichtssache.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@schneitzmaster: es ist eigentlich egal, um welche Aufgabe es sich handelt, ob Lesen, Schreiben oder Rechnen, ein komplexes Problem muß man im Kopf sowieso in einfachere Teile herunterbrechen. Diese kann man dann als einzelne Funktionen schreiben. Wenn Du Deine Daten als Klasse repräsentierst, dann ist das doch kein Problem, weitere Information als zusätzliche Attribute mit aufzunehmen. Wenn Du meinst, eine Funktion liefert unterschiedliche Rückgabetypen, je nach Input, dann ist das sowieso ein Design-Fehler. Was macht eine Funktion, die einen bestimmten Typ erwartet und jetzt plötzlich was ganz anderes bekommt?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

schneitzmaster hat geschrieben:Deinen Vorschlag mit dem 'collections.namedtuple()' habe ich nicht ganz verstanden. Könntest du dazu ein Minimalbeispiel geben?
https://docs.python.org/2/library/colle ... namedtuple
schneitzmaster hat geschrieben:Bzgl. der Code Zeilen verstehe ich den Ansatz eine Funktion mit max. 50 Code zeilen auszustatten nicht. Gerade beim lesen komplexer text-dateien mit vielen abschnitten ist es doch nur möglich die Hauptfunktionen klein zu halten, wenn man mehrere Funktionen verschachtelt. Das wiederrum finde ich an diesem Beispeil für die Aufgabe "extrahiere Daten aus Textdatei" unübersichtlich.... ist aber wohl ansichtssache.
Ansichtssache halt auch irgendwie Ansichtssache. Auf der einen Seite stehst du und auf der anderen Seite eigentlich alle anderen Informatiker ;-)
Das Leben ist wie ein Tennisball.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Mit der folgenden Syntax kommst Du der Matlab Syntax recht nahe:

Code: Alles auswählen

def test(a,b):
    c = a*b
    d = a+b
    e = a-b
    return c,d,e
 
c,d,e = test(2,3)
print c,d,e
c,d,_   = test(2,3)
print c,d
c,_,_   = test(2,3)
print c
Anstatt dummy Variablennamen nimmst Du _ als Variablennamen. Dann markiert PyDev die Variable auch nicht als unbenutzt.
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

@schneitzmaster: Wieso sollte es nicht möglich sein die Funktionen beim lesen komplexer Textdateien klein zu halten?

Wenn man mit einer kleinen Funktion anfängt und die mit der Zeit grösser wird, fängt man halt an die Funktion aufzuteilen und Teilaufgaben in weitere Funktionen auszulagern. Das muss man nicht vorher planen, das ergibt sich während man den Code erweitert.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

okay danke für eure beiträge.
@ EyDu: ich bin halt kein informatiker und programmier immer einfach drauf los. Meistens muss es halt schnell gehen und da hab ich keine Zeit bzw. keine Erfahrung schon vorher alles soweit abzusehen und zum Bsp. Daten in Klassen abzuspeichern. Ich werd es aber in Zukunft probieren ;)
@ BlackJack: klar kann ich die funktion klein halten, doch dann wird ja immer mehr verschachtelt. Die Frage ist nun was besser ist Verschachtelung oder lange Funktionen. Bis zu einem gewissen Grad finde ich die langen Funktionen besser, da man zum Beispiel beim Debuggen nicht immer von Textdatei zu Textdatei bzw. von Funktion zu Funktion springen muss.
@ Siruius3: Ich würde denken das eine Funktion eine Fehlermeldung gibt wenn sie nicht den Typ bekommt den sie erwartet so wie man NaN bekommt, wenn man durch null teil.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

schneitzmaster hat geschrieben: @ BlackJack: klar kann ich die funktion klein halten, doch dann wird ja immer mehr verschachtelt. Die Frage ist nun was besser ist Verschachtelung oder lange Funktionen. Bis zu einem gewissen Grad finde ich die langen Funktionen besser, da man zum Beispiel beim Debuggen nicht immer von Textdatei zu Textdatei bzw. von Funktion zu Funktion springen muss.
Die Verschachtelung ist doch vom Problem abhängig und nicht von der Aufteilung in Blöcke oder Funktionen!

Und das "Debuggen" ist letztlich nur das *letzte* Glied in der Kette, wenn das "Gesamtsystem" nicht funktioniert. Die einzelnen Komponenten (wie eben Funktionen!) sollte man eh sofort testen, wenn man sie "fertig" programmiert hat. Dies geht bei *kleinen* Komponenten deutlich besser, da man sie in einer REPL auch *ohne* den "Rest" testen kann. Dies gilt genauso für das Unit Testing - welches jedem Entwickler besser früher als später über den Weg laufen sollte.

Im übrigen kommt man an Funktionen (oder auch Methoden im Falle von Klassen) nicht vorbei, wenn man DRY vermeiden will - was man tunlichst sollte! Denn wenn man schon sonst keine Entwicklungsprinzipien kennt, dieses eine ist einfach ein *Muss*!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@schneitzmaster: Was ist denn an Verschachtelung von Funktionsaufrufen schlecht? Da hat man dann kleine, in sich geschlossene Funktionen die man einzeln und unabhängig testen kann *bevor* man sie benutzt → weniger Fehler die man später mühsam in mehr Code suchen muss.

Statische Verschachtelung im Quelltext ist schlecht weil darunter die Lesbarkeit leidet und man Probleme bekommt den Überblick zu behalten was genau auf welcher Ebene passiert und wie das alle anderen Ebenen und nachfolgenden Code beeinflusst. Und *diese* Art von Verschachtelung beseitigt man durch auslagern in Funktionen, wo man dann lokal weniger im Kopf behalten muss was da passiert und man verringert die Gefahr von Beeinflussung von Dingen die im gleichen Namensraum liegen, die man aber gar nicht braucht.

Es kann auch Arbeitsspeicher sparen wenn man einzelne Funktionen hat, statt einer grossen wo über die gesamte Laufzeit auch alle Zwischenergebnisse die noch an Namen gebunden sind aufgehoben werden, auch wenn man die im weiteren Verlauf der Funktion gar nicht mehr benötigt.

Man muss das wie gesagt auch nicht alles vorher planen und wissen. Es reicht wenn man den Code umschreibt wenn man merkt das etwas zu lang und unübersichtlich wird, oder wenn man immer die gleichen Werte zusammen aber einzeln an Funktionen übergibt, dass man sich dann Gedanken macht ob die nicht semantisch sinnvoller zu einem Datentyp zusammengefasst werden können, und ob und welche Funktionen dann zu diesem Typ gehören und damit Methoden sein sollten.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@ BlackJack: Danke für den kleinen exkurs. Insbesondere den Aspekt des Arbeitsspeichers hatte ich noch gar nicht auf dem schirm. eine letzte Frage hätte ich dazu allerdings noch. Wenn es sich als sinnvoller erweist die Daten zu kapseln erstellst du dann die Klasse in der funktion selber oder ausserhalb?

Code: Alles auswählen

def test(a,b):
    class ergebnis(object):
        def __init__(self):
            self.c = 0
            self.d = 0
            self.e = 0
    ergebnis = ergebnis()
    ergebnis.c = a*b
    ergebnis.d = a+b
    ergebnis.e = a-b
    return ergebnis

ergebnis = test(2,3)

print ergebnis.c
print ergebnis.d
print ergebnis.e
BlackJack

@schneitzmaster: Ausserhalb, denn in der Funktion würde man ja ständig neue Klassen erstellen die sich aber von der Funktionalität her gar nicht unterscheiden. Und ob man dann noch eine Funktion braucht statt das gleich in der Klasse abzuhandeln wäre auch noch eine Frage. Das Beispiel wäre doch viel einfacher so geschrieben:

Code: Alles auswählen

from __future__ import absolute_import, division, print_function

class Ergebnis(object):

    def __init__(self, a, b):
        self.c = a * b
        self.d = a + b
        self.e = a - b
 
ergebnis = Ergebnis(2, 3)
 
print(ergebnis.c)
print(ergebnis.d)
print(ergebnis.e)
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@ BlackJack: das ist ja clever. darauf bin ich gar nicht gekommen. klar, gleich alles als klasse schreiben. so mach ich das auch mit meiner Textdatei. gleich die lese-funktion als klasse an sich definieren. Danke
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@schneitzmaster: Wobei Klassen an sich kein Allheilmittel sind! Es kommt eben immer auf den Kontext an. Also blos nicht einfach *alles* in eine Klasse flantschen und denken, dass das gut ist ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Die drei Ausdrücke für die Werte c, d, e könnte man wiederrum als Funktionen definieren. Immerhin gelten sie für sich eigenständig und können als solche getestet werden:

Code: Alles auswählen

def func_c(a, b):
    return a * b
    
def func_d(a, b):
    return a + b
    
def func_e(a, b):
    return a - b

Code: Alles auswählen

>>> func_c(2, 3) == 6 # Expected: True
True
>>> func_d(2, 3) == 5 # Expected: True
True
>>> func_e(2, 3) == -1 # Expected: True
True
Funktionen sind ebenfalls Objekte und können z. B. wie andere Werte in Listen organisiert werden, über welche dann iteriert werden kann:

Code: Alles auswählen

>>> for f in [func_c, func_d, func_e]:
...     print(f(2, 3))
...
6
5
-1
Ein tolles Werkzeug sind List-Comprehensions. Mit diesen tat ich mich am Anfang recht schwer, aber wenn man sie einmal verinnerlicht hat, will man sie nicht mehr missen:

Code: Alles auswählen

>>> [f(2, 3) for f in [func_c, func_d, func_e]]
[6, 5, -1]
Aber hier kommt es auch, wie bei Klassen, auf den Kontext und das Problem an. Probleme sollten so weit wie möglich auf Teilprobleme heruntergebrochen werden, welche sich isoliert testen lassen (zum Beispiel in der Python-Konsole).
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wobei ich einen Einzeiler meistens eher ausschreiben würde anstatt ihn in eine Funktion zu kapseln. Einzeiler als Funktionen sind nur dann wirklich sinnvoll, wenn man sie mehrfach im Code einsetzen möchte, quasi ähnlich wie ein Makro. Oder wenn man irgendwelche freakigen Schreibweisen für ein Framework anbieten will, wie man das ja mitunter vorfindet.
Antworten