Seite 1 von 1

Verständnisfrage zur Parameterübergabe

Verfasst: Mittwoch 30. September 2015, 10:56
von NullPylung
Hallo,

ich bin neu in Python unterwegs und hab so meine Verständnisprobleme. Ich tu mich reichlich schwer mit den untypisierten Variablen. Ich bin ständig versucht, exakt zu formulieren, was eine Funktion oder Methode erwartet. Konkret habe ich folgendes Problem:

Wie übergebe ich einer Funktion oder Methode eine Liste?
Ich möchte eine Liste von Tupeln aus Strings verarbeiten. Aber ich kriege sie nicht zu fassen.

Reicht es einfach eine beliebige Variable als Argument zu definieren?
Woher weiß die Funktion / Methode dann, was sie bekommt? Und woher weiß ich, was sie erwartet?
Ich pyls einfach nicht.

Re: Verständnisfrage zur Parameterübergabe

Verfasst: Mittwoch 30. September 2015, 11:09
von BlackJack
@NullPylung: Vielleicht erst einmal ein wenig Begriffsauflösung: „untypisierte Variable“ ist kein so guter Begriff weil eine Variable aus einem Namen und einem Wert besteht und der *Wert* hat in Python sehr wohl einen Typ. Es ist der Name dem kein fester Typ anhaftet.

Einer Funktion oder Methode übergibst Du eine Liste in dem Du genau das tust: Du übergibst einfach eine Liste. Ich sehe da kein Problem‽

Und wenn Du eine Liste von Tupeln mit Zeichenketten verarbeiten willst, dann verarbeite den Wert einfach als wäre er eine Liste von Tupeln mit Zeichenketten. Also verwende einfach die Operationen die für diese Typen dokumentiert sind. Das bedeutet dann praktischerweise auch das Dein Code auch *alles andere* verarbeiten kann was sich entsprechend verhält. Wenn es watschelt wie eine Ente, und quakt wie eine Ente, dann geht man einfach davon aus es ist eine Ente → „duck typing“. :-)

Als Argument definierst Du einfach einen Namen der dann beim Aufruf an den Wert gebunden wird den der Aufrufer angibt — egal was das ist. Die Funktion/Methode weiss was sie bekommt weil jeder Wert in Python seinen Typ kennt. Allerdings fragt die Funktion das in aller Regel nicht, die benutzt den Wert einfach ohne sich um den Typ zu kümmern. Wenn es ein zu den Operationen passender Typ ist, dann funktioniert das, sonst halt nicht.

Was eine Funktion erwartet steht hoffentlich in der Dokumentation der Funktion. Oder es ist offensichtlich.

Re: Verständnisfrage zur Parameterübergabe

Verfasst: Mittwoch 30. September 2015, 11:21
von NullPylung
Vielen Dank für die schnelle Antwort.

Wie Du siehst liegt es daran, daß ich gewohnt bin Variablennamen an einen Typ zu binden.
Es ist nicht so, daß ich das Prinzip nicht verstanden hätte, aber bei der konkreten Formulierung fehlt's dann meistens.

Re: Verständnisfrage zur Parameterübergabe

Verfasst: Mittwoch 30. September 2015, 11:56
von pillmuncher
@NullPylung: Noch das: manche versuchen, die in Python fehlenden Typdeklarationen durch Kodierung des Typnamens in den Variablennamen zu simulieren (AKA Ungarische Notation - the tactical nuclear weapon of code obfuscation). Das ist in statisch typisierten Sprachen schon fast immer schlecht, und in dynamisch typisierten immer. Vermeide das. Lass dich auf Duck Typing ein. In Python soll der Aufrufer die Kontrolle darüber haben, was passiert, nicht der Aufgerufene. Wenn die gewünschte Operation nicht möglich ist, weil auf einem Objekt eine Mothode aufgerufen wird, die nicht existiert, fliegt sowieso ein Laufzeitfehler. Siehe auch: EAFP.

Re: Verständnisfrage zur Parameterübergabe

Verfasst: Mittwoch 30. September 2015, 12:12
von NullPylung
Ich muß zugeben, daß mir die fehlende Typisierung anfangs echt Kopfzerbrechen gemacht hat.
Mittlerweile finde ich das Verwenden "sprechender" Variablennamen aber deutlich einfacher.

Es ist halt schwer einem Fisch das Laufen beizubringen :-)

Re: Verständnisfrage zur Parameterübergabe

Verfasst: Mittwoch 30. September 2015, 13:56
von pillmuncher
NullPylung hat geschrieben:Ich muß zugeben, daß mir die fehlende Typisierung anfangs echt Kopfzerbrechen gemacht hat.
Mittlerweile finde ich das Verwenden "sprechender" Variablennamen aber deutlich einfacher.
Meinst du "sprechenden" Variablennamen solche, bei denen der Typname mitkodiert ist? Also so:

Code: Alles auswählen

l_items = ['foo', 'bar', 'baz']
i_counter = 0
i_counter += 1
usw? Dann wiederhole ich nochmal: vermeide das. Wer sich aus dem Namen counter nicht zusammenreimen kann, dass hier etwas gezählt wird, dem wird ein vorangestelltes i_ auch nicht weiterhelfen. Und zählen tun wir Menschen nunmal primär mit ganzen Zahlen, nicht mit Strings, IP-Sockets oder Regex-Matches (oder was es halt sonst so an Objekten gibt).

Bei dem Beispiel mit l_items ist es so, dass eine Funktion, die über die darüber iterieren und mit den Elementen irgendetwas tun soll, überhaupt nicht wissen muss, dass sie eine Liste vor sich hat. Elementweises iterieren geht über viele Datenstrukturen, nicht nur über Listen. Und für den Fall, dass sie Elemente hinzufügen oder löschen soll: eine Funktion sollte als Argumente übergebene Datenstrukturen idR. nicht verändern. Das führt langfristig nur zu Schmerz und Tränen. Statt dessen sollte man die Datenstruktur so wegkapseln, dass es zentrale Stellen im Code gibt, wo sie manipuliert wird. Beispiel:

Code: Alles auswählen

from uuid import uuid4


class LineItem:

    def __init__(self, article_id, amount):
        self.article_id = article_id
        self.amount = amount


class Cart:

    def __init__(self, customer_id):
        self.id = uuid4()
        self._items = []

    def __iter__(self):
        return iter(self._items)

    def add_line_item(self, article_id, amount):
        if amount < 1:
            raise ValueError('Amount must be greater than zero!')
        self._items.append(LineItem(article_id, amount))
Der Code ist unmittelbar einleuchtend, aber auch ungünstig:

Code: Alles auswählen

c = Cart()
c.add_line_item(123, 5)
c.add_line_item(123, 7)
Wollen wir wirklich in ein und demselben Warenkorb mehrmals dieselbe Sache mit jeweils unterschiedlichen Mengen stehen haben? Oder nur einmal mit der Gesamtmenge? Und wie sähe eine remove_line_item() Methode aus? Soll sie alle items mit derselben Artikel-ID löschen? Oder soll man angeben, wieviele gelöscht werden sollen? Habe ich im Beispiel oben nun -1 und 7 oder 5 und 1 oder gleich 6 stehen? Nehmen wir also an, pro Artikel-ID darf es nur ein LineItem im Warenkorb geben, und add_line_item() und remove_line_item() müssen diese Invariante aufrecht erhalten. Dann käme zB. sowas heraus:

Code: Alles auswählen

    def add_line_item(self, article_id, amount):
        if amount < 1:
            raise ValueError('Amount must be greater than zero!')
        try:
            index = self._items[article_id]
        except ValueError;
            self._items.append(LineItem(article_id, amount))
        else:
            self._items[index].amount += amount

    def remove_line_item(self, article_id, amount):
        index = self._items.index(article_id)
        item = if self._items[index]
        if item.amount > amount:
            self._items[index].amount -= amount
        else:
            del self._items[index]
Ziemlich umständlich, finde ich. Dann lieber gleich mit einer Assoziativen Datenstruktur:

Code: Alles auswählen

class LineItems(dict):

    def __missing__(self, article_id):
        item = LineItem(article_id, 0)
        self[article_id] = item
        return item


class Cart:

    def __init__(self, customer_id):
        self.id = uuid4()
        self._items = LineItems()

    def __iter__(self):
        return self._items.values()

    def add_line_item(self, article_id, amount):
        if amount < 1:
            raise ValueError('Amount must be greater than zero!')
        self._items[article_id].amount += amount

    def remove_line_item(self, article_id, amount):
        item = self._items[article_id]
        if item.amount > amount:
            item.amount -= amount
        else:
            del self._items[article_id]
Anmerkung 1: der einzelne führende Unterstriche bei den Attributnamen bedeutet: Implementierungsdetail, bitte nicht von außen verwenden. Das ist reine Konvention, und wer mag, kann trotzdem darauf zugreifen. Wer sich selber in den Fuß schießen will, indem er auf Implementierungsdetails zugreift, kann das gerne tun. Ist ja sein Fuß.

Anmerkung 2: Doku zur __missing__() Methode.

Was haben wir erreicht? Wir haben die Implementation ändern können, ohne dass sich irgendwas daran, wie ein Warenkorb verwendet wird, geändert hat. Und wir haben keine Ungarische Notation gebraucht. Der Bereich, in dem _items sichtbar ist, ist so klein, dass man einfach nachsehen kann, auf was dieser Name verweist. Weil wir ihn als Implementierungsdetail gekennzeichnet haben, können wir davon ausgehen, dass er auf nichts anderes als ein LineItems-Objekt verweist, denn in unserem Code wird self._items nur einmal am Anfang gesetzt und nie überschrieben. Und man kann über ein Cart-Objekt iterieren und erhält dabei jedes LineItem, denn wir haben das Iterator-Protokoll implementiert. Wie wir das getan haben, geht den Benutzer eines Cart-Objektes nichts an.

Re: Verständnisfrage zur Parameterübergabe

Verfasst: Mittwoch 30. September 2015, 16:21
von NullPylung
Nein, das mit den Typen im Namen finde ich in der Tat nicht sehr hilfreich.

Ich musste das früher häufiger mal für eine Firma machen, die eine strikte Namensgebung mit solchen Kürzeln verlangte. Es macht aber keinen Spaß, weil man ewig braucht, um diese Kürzel zu erlernen und die Konventionen sind ja nicht unbedingt überall gleich. Zudem ist auch der praktische Nutzen fraglich, denn wenn ich den Typ einer Variablen kenne, verstehe ich noch lange nicht ihre Funktion.

Mit "sprechenden" Variablennamen meine ich also, daß sie Auskunft über ihre Funktion geben, also counter wäre ein Beispiel. Was da gezählt wird, erfährt man im besten Fall aus der Dokumentation und im schlechtesten zumindest aus dem Kontext.

Aber danke für deine Erklärung.