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.
Verständnisfrage zur Parameterübergabe
-
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.
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.
-
NullPylung
- User
- Beiträge: 4
- Registriert: Mittwoch 30. September 2015, 10:21
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.
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.
- pillmuncher
- User
- Beiträge: 1532
- Registriert: Samstag 21. März 2009, 22:59
- Wohnort: Pfaffenwinkel
@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.
In specifications, Murphy's Law supersedes Ohm's.
-
NullPylung
- User
- Beiträge: 4
- Registriert: Mittwoch 30. September 2015, 10:21
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
Mittlerweile finde ich das Verwenden "sprechender" Variablennamen aber deutlich einfacher.
Es ist halt schwer einem Fisch das Laufen beizubringen
- pillmuncher
- User
- Beiträge: 1532
- Registriert: Samstag 21. März 2009, 22:59
- Wohnort: Pfaffenwinkel
Meinst du "sprechenden" Variablennamen solche, bei denen der Typname mitkodiert ist? Also so: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.
Code: Alles auswählen
l_items = ['foo', 'bar', 'baz']
i_counter = 0
i_counter += 1Bei 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))Code: Alles auswählen
c = Cart()
c.add_line_item(123, 5)
c.add_line_item(123, 7)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]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 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.
In specifications, Murphy's Law supersedes Ohm's.
-
NullPylung
- User
- Beiträge: 4
- Registriert: Mittwoch 30. September 2015, 10:21
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.
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.
