Einsteigerfrage zur Strukturierung und Arrays

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
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

Hallo, ich fange gerade an Python zu lernen und bin dabei auf ein kleines Problem gestoßen. Bisher kann ich nur Pascal.

Was ich erreichen möchte ist etwas in der folgenden Art:

a[1].b == 1
a[2].b == 2
a[1].c == 0.2
a[2].c == 0.3

In Pascal kann man sowas wie folgt deklarieren und initialisieren:

Code: Alles auswählen

type 
  type_a = record 
    b: integer; 
    c: real; end;

var
  a: array [1..2] of type_a;

begin
  a[1].b := 1;
  a[2].b := 2;
  a[1].c := 0.2;
  a[2].c := 0.3;
Wie kann man das in Python hinkriegen?

Da fällt mir noch eine zweite Frage ein. In Pascal gibt es einen "with ... do" Befehl:

Code: Alles auswählen

with a[1] do begin
  b := 1;
  c := 0.2; end;
dabei kommt das selbe raus wie bei:

Code: Alles auswählen

  a[1].b := 1;
  a[1].c := 0.2;
Gibt es soetwas auch in Python?
Zuletzt geändert von Anonymous am Mittwoch 21. August 2013, 07:35, insgesamt 1-mal geändert.
Grund: Quelltext in Pascal-Code-Tags gesetzt.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Hallo Aries, Willkommen im Forum!

Eine ziemlich direkte Übersetzung sähe zB. so aus:

Code: Alles auswählen

class TypeA(object):
    def __init__(self, b, c):
        self.b = b
        self.c = c

a = [TypeA(1, 0.2), TypeA(2, 0.3)]
Alles in Python ist ein Objekt, selbst Module, Klassen, Funktionen und Methoden. Variablen haben keinen Typ in Python, nur das, auf was sie verweisen hat einen Typ. Sie sind nur Namen, mit denen Objekte referenziert werden können. Namen gelten immer nur innerhalb eines Namensraumes. Der kann implizit sein, oder man muss ihn ggf. explizit machen. In der __init__()-Methode (mit der Objekte initialisiert werden) oben sind die Parameter self, b und c Namen im lokalen Namensraum. Sie sind damit nur innerhalb dieser Methode sichtbar. Durch die Zuweisung self.b = b wird automagisch ein Name (a) im Namensraum des Objekts erzeugt, das hier mit self referenziert wird. Das ist das, was ich oben mit "explizit machen" meinte.

Zu deiner zweiten Frage: es gibt ein with-Statement in Python, das allerdings etwas komplett anderes tut, als das in Pascal. Etwas dem Pascal-with Vergleichbares gibt es nicht in Python. Ich persönlich habe es noch nie vermisst. Es würde nicht zur Python-Philosophie passen, insbesondere würde es dem Punkt "Explicit is better than implicit" widersprechen. Ein Fall, wo es etwas umständlich ist, ist dieser hier:

Code: Alles auswählen

for j in range(len(a)):
    a[j].b = 1
    a[j].c = 0.2
Das würde man in Python aber gar nicht so programmieren, sondern so:

Code: Alles auswählen

for o in a:
    o.b = 1
    o.c = 0.2
Man kann in Python direkt über Listen und andere Collections iterieren. Ach ja: a ist eine Liste und kein Array. Python-Listen sind eher den ArrayLists aus Java vergleichbar. Es gibt auch Arrays in Python, im array Modul, aber ich habe sie noch nie verwendet. Für Anwendungen, die hohe Performance benötigen, verwendet man meistens numpy.

Ein paar Anmerkungen, um ein paar möglichen Verwirrungen vorzubeugen: self ist immer der erste Parameter einer Methode und wird bei deren Aufruf nicht mit angegeben. Dafür, dass eine Referenz auf das Objekt, auf dem eine Methode aufgerufen wird, in self landet, sorgt Python, ebenfalls automagisch. Dass der Parameter self heißt, ist bloß eine Konvention, man könnte auch Peter oder Salat hinschreiben, aber das wäre sehr ungewöhnlich und ist nicht ratsam, weil Pythonistas ihre Konventionen lieben: in einer Sprache, bei dem extrem viel auf reiner Konvention beruht, sorgt diese dafür, dass Code lesbar und übersichtlich bleibt. Die Konventionen werden großteils in PEP-8 beschrieben. Für alle weiteren Informationen konsultiere die Dokumentation, wo sich auch das Tutorial findet. Als Einführung eignet sich auch Learn Python The Hard Way (nicht vom Titel abschrecken lassen, es ist sehr gut). Hier im Forum werden ebenfalls Fragen von Python-Anfängern regelmäßig sehr ausführlich von den Regulars beantwortet. Dieser Thread ist vielleicht noch hilfreich: Offener Brief an Pythonneulinge.

Viele Grüße,
Mick.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@Aries: Noch Ergänzend:

In Python fangen Listenindizes immer bei 0 an.

Es wird zwischen Gross und Kleinschreibung unterschieden, dass heisst `a` ist etwas anderes als `A`. Darum ist es auch nicht üblich irgendwelche Prä- oder Postfixe zu verwenden um Namen von Datentypen von Namen von (anderen) Werten zu unterscheiden. Datentypen, also Klassen, werden per Konvention in „MixedCase” mit führendem Grossbuchstaben geschrieben und Namen für Werte in kleinbuchstaben_mit_unterstrichen. Ausnahme sind Konstanten deren Namen durchgehend GROSS geschrieben werden. pillmuncher hätte bei der Übersetzung die Klasse auch einfach nur `A` nennen können, denn so etwas wie ``a = A()`` ist in Python gültig und üblich. Also mal davon abgesehen, dass in den seltensten Fällen einbuchstabige Namen sinnvoll sind und ausreichend vermitteln worum es sich bei den Werten dahinter handelt. Aber das ist ja nur ein Beispiel.

Falls es sich bei `type_a`/`A` um nicht veränderbare Werte handelt, kann man statt einer Klasse auch ein `collections.namedtuple` verwenden. Dann lassen sich die Attribute nach dem erstellen nicht mehr neu zuweisen:

Code: Alles auswählen

from collections import namedtuple

A = namedtuple('A', 'b c')

a = [A(1, 0.2), A(2, 0.3)]
for o in a:
    print o.b, o.c
    o.b = 42    # Fehler!
Das Grundgerüst eines Pascal-Programms würde man idiomatisch so „übersetzen”:

Code: Alles auswählen

program something

begin
    WriteLn('Hello, World!');
end.
in Python:

Code: Alles auswählen

#!/usr/bin/env python


def main():
    print 'Hello, World!'


if __name__ == '__main__':
    main()
Im Gegensatz zu Pascal muss man das allerdings nicht so schreiben. Es ist nur übliche Praxis und keine syntaktische Vorgabe. Sinn dieses Idioms ist, dass die Hauptfunktion, und damit das Programm, nur automatisch ausgeführt wird, wenn man das Modul direkt ausführt, aber nicht wenn man es per ``import`` in einem anderen Modul oder in einer Python-Shell interaktiv importiert. So kann man die Werte, Funktionen und Klassen in dem Modul in anderen Modulen verwenden, oder automatisierte Tests durchführen. Und auch zur Fehlersuche das Modul in einer Python-Shell importieren und einzelne Funktionalitäten interaktiv ausprobieren.
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

Danke für die Antworten.
pillmuncher hat geschrieben: Eine ziemlich direkte Übersetzung sähe zB. so aus:

Code: Alles auswählen

class TypeA(object):
    def __init__(self, b, c):
        self.b = b
        self.c = c

a = [TypeA(1, 0.2), TypeA(2, 0.3)]
Gut, das funktioniert und ich komme damit klar. Auch eine weitergehende Verschachtelung kriege ich so hin (z. B. a[1].b[1].c)

Ich habe auch herausgefunden, dass folgendes funktioniert:

Code: Alles auswählen

class A():
    b = 0
    c = 0

a = [A()] * 10

a[1].b = 1
Frage: Was bewirkt es, wenn man "object" in die Klammer schreibt?

Ich habe gesehen, dass in Python statt einer Ganzzahl auch ein String der Index einer Liste sein kann (a["Name"]). Das erscheint in dem Zusammenhang praktisch, da man sich so den Namen mittels a[1].Name anzugeben sparen kann. Aber wie ergibt sich die Reihenfolge der String-Indizes? Und kann man auch nachträglich einen Ganzzahl-Index durch einen String ersetzen bzw. einen String zuweisen?

Was muss ich an ...

Code: Alles auswählen

a = [A()]
... ändern damit der Index keine Zahl sondern ein String ist?
Zu deiner zweiten Frage: es gibt ein with-Statement in Python, das allerdings etwas komplett anderes tut, als das in Pascal. Etwas dem Pascal-with Vergleichbares gibt es nicht in Python. Ich persönlich habe es noch nie vermisst.
Ein Beispiel: Wir haben Staaten wie Deutschland mit Bundesländern wie Bayern mit Regierungsbezirken wie Oberfranken und mit Kreisen, und wir wollen z. B. pro Kreis den Gewinn aus Einnahmen und Ausgaben berechnen. In Pascal würde ich schreiben:

Code: Alles auswählen

for Zaehler1:=1 to Staatenanzahl do with Staat[Zaehler1] do
  for Zaehler2:=1 to Bundeslaenderanzahl do with Bundesland[Zaehler2] do
    for Zaehler3:=1 to Regierungsbezirkanzahl do with Regierungsbezirk[Zaehler3] do
      for Zaehler4:=1 to Kreisanzahl do with Kreis[Zaehler4] do 
        Gewinn := Einnahmen - Ausgaben;
Die Alternative wäre:

Code: Alles auswählen

for Zaehler1:=1 to Staatenanzahl do 
  for Zaehler2:=1 to Staat[Zaehler1].Bundeslaenderanzahl do 
    for Zaehler3:=1 to Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirkanzahl do 
      for Zaehler4:=1 to Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreisanzahl do 
        Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreis[Zaehler4].Gewinn :=
        Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreis[Zaehler4].Einnahmen -
        Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreis[Zaehler4].Ausgaben;
Statt nur einer Sache könnnte man pro Kreis auch z. B. 10 Sachen berechnen wollen. Oder man könnte auch etwas für nochmals untergeordnete Orte oder Ortsteile berechnen wollen. Man sieht wie es ausarten kann. Von Pascal bin ich gewöhnt sowas mit "with" zu vereinfachen. In Python muss ich damit wohl anders umgehen. Aber das scheint mir bisher das einzige zu sein, was womöglich komplizierter wird. :)
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Aries hat geschrieben:Ich habe gesehen, dass in Python statt einer Ganzzahl auch ein String der Index einer Liste sein kann (a["Name"]).
Das ist dann keine Liste, sondern ein Dictionary. Daher vielleicht auch noch einmal der Hinweis auf Learn Python the hard way.
Aries hat geschrieben:Ein Beispiel: Wir haben Staaten wie Deutschland mit Bundesländern wie Bayern mit Regierungsbezirken wie Oberfranken und mit Kreisen, und wir wollen z. B. pro Kreis den Gewinn aus Einnahmen und Ausgaben berechnen. In Pascal würde ich schreiben:
Statt jetzt zu überlegen, wie sich dieses Konstrukt in Python übersetzen lässt, halte ich diese Aufgabe für einen typischen Fall von "Algorithmen und Datenstrukturen" (übrigens ein Originaltitel von Wirth). D.h wie soll für die konkrete Aufgabe eine sinnvolle Datenstruktur aussehen? Dann kann ein passender Algorithmus entworfen werden. Wenn das übel ausgeht, kann man auch anders herum vorgehen — je nach dem, was besser passt.
BlackJack

@Aries: Auch wenn Du eine weitergehende Verschachtelung der Art ``a[1].b[1].c`` hinbekommst: So etwas würde man hoffentlich nie in einem Python-Programm sehen.

Beim Beispiel mit `b` und `c` auf Klassenebene ist die Frage was Du unter „funktioniert” verstehst. Ist die Syntax legal — ja. Läuft das Programm ohne einen Laufzeitfehler — auch ja. Tut es das was Du vielleicht denkst — sehr wahrscheinlich nicht. `b` und `c` sind Attribute die auf der *Klasse* definiert sind, also Werte die für alle Exemplare von diesem Datentyp gleich sind. Im Gegensatz zu den Attributen die man über `self` in der `__init__()`-Methode setzt, die bei jedem Exemplar unabhängig von den anderen definiert sind. Klassenattribute sind in aller Regel Konstanten und nur in sehr seltenen Ausnahmefällen Werte die neu gebunden oder verändert werden.

Das nächste Problem ist die Multiplikation der Liste mit einer Zahl. Damit erzeugst Du eine Liste in der 10 mal das *selbe* Objekt steht, und nicht 10 unterschiedliche, unabhängige Objekte. In Python werden nie implizit Objekte/Werte kopiert. Nach der Zuweisung von 1 an das `b`-Attribut ist an allen Indizes in der Liste `b` an 1 gebunden, weil jeder Indexzugriff zum selben Objekt führt. Die Multiplikation kann man machen wenn man unveränderliche Werte wie Zahlen oder Zeichenketten hat, oder wenn man sicher ist, dass es nichts ausmacht, dass man eine Liste voll mit dem selben Objekt hat. Wenn man 10 gleiche, aber voneinander unabhängige Werte in der Liste haben möchte, muss man 10 `A`-Exemplare erstellen. Mit einer Schleife, oder kompakter einer „list comprehension” (LC):

Code: Alles auswählen

a = list()
for _ in xrange(10):
    a.append(A())
# 
# oder als LC:
# 
a = [A() for _ in xrange(10)]
`object` in den Klammern bedeutet das `A` von `object` abgeleitet wird. Das sollte man unter Python 2.x grundsätzlich machen, weil sich der Datentyp sonst unter gewissen Umständen anders verhalten kann, als das in der Dokumentation beschrieben steht.

Was für Typen und Werte bei einem Indexzugriff verwendet werden können hängt vom Datentyp ab auf den der Zugriff ausgeführt wird. Bei Listen kann man ganze Zahlen und Objekte vom Typ `slice` übergeben. Für letztere gibt es eine eigene Syntax. Mehr dazu in einem Grundlagentutorial Deiner Wahl. :-) Man kann den Indexoperator auch bei eigenen Datentypen überschreiben, da kann man also grundsätzlich alles übergeben was man auch an eine Funktion mit einem Argument übergeben könnte, solange der Datentyp etwas damit anfangen kann.

Ich sehe nicht, dass das ohne Pascal's ``with`` komplizierter wird:

Code: Alles auswählen

for staat in staaten:
    for bundeslaender in staat:
        for bundesland in bundeslaender:
            for regierungsbezirk in bundesland:
                for kreis in regierungsbezirk:
                    gewinn = kreis.einnahmen - kreis.ausgaben
Im Gegenteil finde ich das Pascal-``with`` verwirrend. `Einnahmen` und `Ausgaben` kommen vom `Kreis` nehme ich an. Und `Gewinn`? Ist das eine Variable die weiter oben definiert ist und in keinem der Typen vorkommt die mit ``with`` verwendet werden? Falls es doch in einem vorkommt was passiert dann? Das sieht mir nach einer fiesen Fehlerquelle aus. So im Sinne von: Man definiert nachträglich ein neues Feld in einem Datentyp und irgendwo im Programm geht dadurch etwas kaputt. Das ist zumindest der Grund warum einem bei ``with`` in JavaScript von jedem abgeraten wird.

Ich habe so ein bisschen die Befürchtung Du wirst Pascal mit Python-Syntax programmieren wenn Du weiter versuchst Pascal-Konstruke auf Python-Konstrukte abzubilden, statt tatsächlich Python in Python zu programmieren.

Edit: Die letzte Zeile in Beispiel würde wahrscheinlich eher ``kreis.gewinn`` lauten und der Gewinn würde von einer Methode bzw. einem Property im Datentyp `Kreis` berechnet werden.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Aries hat geschrieben:Ich habe auch herausgefunden, dass folgendes funktioniert:

Code: Alles auswählen

class A():
    b = 0
    c = 0

a = [A()] * 10

a[1].b = 1
Frage: Was bewirkt es, wenn man "object" in die Klammer schreibt?
In Python schreibt man bei der Klassendefinition die Name derjenigen Klassen, von denen man erben möchte, in runden Klammern mit Kommata getrennt zwischen den Namen der Klasse, die man definiert, und den Doppelpunkt:

Code: Alles auswählen

class A(B, C):
    ...
Ein Paar leere Klammern bedeuten genau dasselbe, wie gar keine Klammern. Ich würde sie weglassen, da sie keinerlei Funktion haben, sondern nur für optischen Lärm sorgen. Von object erbt man in Python 2.x, wenn man sog. new style classes definieren möchte, im Gegensatz zu old style classes. Letzere können bestimmte Dinge nicht, zB. Descriptoren, was allerdings ein etwas fortgeschritteneres Thema ist, mit dem du dich vorerst nicht belasten solltest. In Python 3.x braucht man nicht mehr explizit von object zu erben, da es da keine old style classes mehr gibt und alle Klassen automatisch von object erben.

Im Übrigen ist der Code der Klasse A oben nicht äquivalent zur Definition meiner Klasse TypeA. Der Block, der dem Klassendefinitionsheader folgt, bildet einen Namensraum, und zwar den der Klasse, nicht den Namensraum eines Objekts der Klasse. Folglich gehören die dort definierten Namen zur Klasse. In Python funktioniert die Auflösung eines qualifizierten Namens (also einem mit der Struktur <Zielobjekt>.<Name>) so, <Name> zuerst im Namensraum des Zielobjekts gesucht wird, und wenn er da nicht gefunden wurde, in der Klasse von Zielobjekt. Das Ganze wird noch komplizierter, wenn es Zuweisungen gibt, da es davon abhängt, wem was zugewiesen wird:

Code: Alles auswählen

>>> class C:
...     x = 1
...
>>> a = C()
>>> print(C.x, a.x)
1 1
>>> C.x = 2
>>> print(C.x, a.x)
2 2
>>> a.x = 3
>>> print(C.x, a.x)
2 3
>>> del a.x
>>> print(C.x, a.x)
2 2
Wenn man in der Dokumentation nachliest, wie das alles funktioniert, wird einem auch klar, warum das so funktioniert und funktionieren muss. Am Anfang kann es allerdings verwirrend sein. Deswegen schlage ich vor, einfach den von mir gezeigten Weg einzuschlagen, und __init__()-Methoden zu schreiben.
Aries hat geschrieben:

Code: Alles auswählen

for Zaehler1:=1 to Staatenanzahl do 
  for Zaehler2:=1 to Staat[Zaehler1].Bundeslaenderanzahl do 
    for Zaehler3:=1 to Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirkanzahl do 
      for Zaehler4:=1 to Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreisanzahl do 
        Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreis[Zaehler4].Gewinn :=
        Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreis[Zaehler4].Einnahmen -
        Staat[Zaehler1].Bundesland[Zaehler2].Regierungsbezirk[Zaehler3].Kreis[Zaehler4].Ausgaben;
In Python würde man sowas eher so machen:

Code: Alles auswählen

for staat in staaten:
    for bundesland in staat.bundeslaender:
        for bezirk in bundesland.bezirke:
            for landkreis in bezirk.landkreise:
                landkreis.gewinn = landkreis.einnahmen - landkreis.ausgaben
Vorausgesetzt natürlich, dass staat.bundeslaender, bundesland.bezirke und bezirk.landkreise geeignete Datenstrukturen sind, zB. Mengen oder Listen. Oder man definiert einfach, dass jeder Staat die Menge seiner Bundesländer, jedes Bundesland die seiner Bezirke und jeder Bezirk der seiner Landkreise ist, indem man die zugrunde liegenden Klassen iterierbar macht:

Code: Alles auswählen

for staat in staaten:
    for bundesland in staat:
        for bezirk in bundesland:
            for landkreis in bezirk:
                landkreis.gewinn = landkreis.einnahmen - landkreis.ausgaben
Die einfachste Art, Iterierbarkeit zu erreichen, ist enweder von einer Klasse zu erben, die diese Funktionalität bereits hat (Mengen, Listen, Tupel, ...) oder die __iter__()-Methode zu implementieren:

Code: Alles auswählen

>>> class MyIterable:
...     def __iter__(self):
...         for each in 'xyz':
...             yield each
...
>>> mi = MyIterable()
>>> for c in mi:
...     print(c)
...
x
y
z
Strings sind übrigens nicht-mutierbare Sequenzen von Buchstaben. Man kann direkt über sie iterieren, aber sie sind unveränderlich.

Vielleicht noch das: Wenn man anfängt Python zu lernen und schon Sprachen wie Pascal, C/C++ oder Java kennt, ist es wichtig, dass man sich klar macht, dass Python viel mehr Verwandschaft mit Lisp hat, als mit den genannten Sprachen.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@pillmuncher: Beim Thema „new style classes” würde ich Dir widersprechen: Damit sollte man sich auseinandersetzen. Denn sonst funktionieren Properties nicht. Die gibt es in Pascal, und gerade in dem Beispiel bräuchte man das für `gewinn`. Wenn das wie in Deiner Interpretation ein Attribut vom Landkreis sein soll, denn von aussen ein ``kreis.gewinn = kreis.einnahmen - kreis.ausgaben`` wäre ein schlechter Entwurf, weil die Objekte in einem inkonsistenten Zustand sind bis das von aussen mit dieser Rechnung korrigiert wird. Mit einem Property hätte man das Objekt immer in einem konsistenten Zustand:

Code: Alles auswählen

class Landkreis(object):
    def __init__(self, einnahmen=0, ausgaben=0):
        self.einnahmen = einnahmen
        self.ausgaben = ausgaben

    @property
    def gewinn(self):
        return self.einnahmen - self.ausgaben
Und falls die ganzen verschachtelten Schleifen nur dazu dienten den `gewinn` zu aktualisieren, hätte man sie damit auch mal eben komplett überflüssig gemacht. :-)

In Python 2.x würde ich dazu raten einfach grundsätzlich von `object` zu erben (solange man nicht von etwas anderem erbt), um sich spätere Überraschungen zu ersparen.

@Aries: Wenn man so eine regelmässige, verschachtelte Struktur hat und über alle „inneren” Objekte auf einer bestimmten Tiefe iterieren möchte, kann man sich auch eine Funktion schreiben der man die oberste Ebene angibt und wie tief man runtergehen möchte. Dann spart man sich die ganzen verschachtelten Schleifen hinzuschreiben:

Code: Alles auswählen

from itertools import chain


def flatten_to_depth(iterable, depth):
    if depth:
        result = flatten_to_depth(chain.from_iterable(iterable), depth - 1)
    else:
        result = iterable
    for item in result:
        yield item


def main():
    # 
    # ...
    # 
    for kreis in flatten_to_depth(staaten, 4):
        print kreis.gewinn
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

pillmuncher hat geschrieben:Die einfachste Art, Iterierbarkeit zu erreichen, ist enweder von einer Klasse zu erben, die diese Funktionalität bereits hat (Mengen, Listen, Tupel, ...) oder die __iter__()-Methode zu implementieren:

Code: Alles auswählen

>>> class MyIterable:
...     def __iter__(self):
...         for each in 'xyz':
...             yield each
...
>>> mi = MyIterable()
>>> for c in mi:
...     print(c)
...
x
y
z
Die `.__iter__()`-Methode hätte genau so gut einfach `iter('xyz')` zurückliefern können. Es ist nicht nötig, da nochmal einen Generator auszuformulieren.

Das mit dem Erben von Standard-Datentypen halte ich übrigens für problematisch, da man dann - wenn es sauber sein soll - an richtig vielen Methoden des Datentypen herumwerkeln muss, damit sie auch erwartungsgemäß den neuen Datentypen zurückliefern. Soll heißen: `MySpecialSet` müsste dann in all seinen Methoden eine `MySpecialSet`-Instanz zurückliefern, weil es der Nutzer IMHO so erwarten würde. Da hat man eine Menge an zusätzlicher Arbeit durch die Vererbung, die man ja eigentlich vermeiden wollte. Ich ziehe es daher meistens vor, den intern verwendeten Datentypen als (privates) Attribut zu halten und bedarfsweise eine abgespeckte API des Typen für die Nutzung von außen nachzubauen.
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

pillmuncher hat geschrieben: Im Übrigen ist der Code der Klasse A oben nicht äquivalent zur Definition meiner Klasse TypeA. Der Block, der dem Klassendefinitionsheader folgt, bildet einen Namensraum, und zwar den der Klasse, nicht den Namensraum eines Objekts der Klasse. Folglich gehören die dort definierten Namen zur Klasse. In Python funktioniert die Auflösung eines qualifizierten Namens (also einem mit der Struktur <Zielobjekt>.<Name>) so, <Name> zuerst im Namensraum des Zielobjekts gesucht wird, und wenn er da nicht gefunden wurde, in der Klasse von Zielobjekt. Das Ganze wird noch komplizierter, wenn es Zuweisungen gibt, da es davon abhängt, wem was zugewiesen wird:

Code: Alles auswählen

>>> class C:
...     x = 1
...
>>> a = C()
>>> print(C.x, a.x)
1 1
>>> C.x = 2
>>> print(C.x, a.x)
2 2
>>> a.x = 3
>>> print(C.x, a.x)
2 3
>>> del a.x
>>> print(C.x, a.x)
2 2
Wenn man in der Dokumentation nachliest, wie das alles funktioniert, wird einem auch klar, warum das so funktioniert und funktionieren muss. Am Anfang kann es allerdings verwirrend sein. Deswegen schlage ich vor, einfach den von mir gezeigten Weg einzuschlagen, und __init__()-Methoden zu schreiben.
Ich habe gemerkt, dass auch folgendes funktioniert:

Code: Alles auswählen

class A: pass

a = (A(), A())

a[0].b = 15
print a[0].b
Was spricht dagegen?

Merkwürdigerweise funktioniert folgendes nicht:

Code: Alles auswählen

class A: pass

a = (A() for _ in xrange(2))

a[0].b = 15
print a[0].b
pillmuncher hat geschrieben:In Python würde man sowas eher so machen:

Code: Alles auswählen

for staat in staaten:
    for bundesland in staat.bundeslaender:
        for bezirk in bundesland.bezirke:
            for landkreis in bezirk.landkreise:
                landkreis.gewinn = landkreis.einnahmen - landkreis.ausgaben
Vorausgesetzt natürlich, dass staat.bundeslaender, bundesland.bezirke und bezirk.landkreise geeignete Datenstrukturen sind, zB. Mengen oder Listen. Oder man definiert einfach, dass jeder Staat die Menge seiner Bundesländer, jedes Bundesland die seiner Bezirke und jeder Bezirk der seiner Landkreise ist, indem man die zugrunde liegenden Klassen iterierbar macht:

Code: Alles auswählen

for staat in staaten:
    for bundesland in staat:
        for bezirk in bundesland:
            for landkreis in bezirk:
                landkreis.gewinn = landkreis.einnahmen - landkreis.ausgaben
Es so schreiben zu können ist natürlich noch einfacher als mit dem with in Pascal. Sehr schön.
pillmuncher hat geschrieben:Vielleicht noch das: Wenn man anfängt Python zu lernen und schon Sprachen wie Pascal, C/C++ oder Java kennt, ist es wichtig, dass man sich klar macht, dass Python viel mehr Verwandschaft mit Lisp hat, als mit den genannten Sprachen.
Nun, ich kann bisher nur Pascal und habe ansonsten lediglich in Fortran, C/C++ und Prolog mal reingeschnuppert. Trotzdem würde es mich interessieren, worauf Du anspielst, wenn Du sagst, Python hätte mehr Verwandtschaft mit Lisp als mit Pascal/C/Java.
BlackJack hat geschrieben:Im Gegenteil finde ich das Pascal-``with`` verwirrend. `Einnahmen` und `Ausgaben` kommen vom `Kreis` nehme ich an. Und `Gewinn`? Ist das eine Variable die weiter oben definiert ist und in keinem der Typen vorkommt die mit ``with`` verwendet werden? Falls es doch in einem vorkommt was passiert dann? Das sieht mir nach einer fiesen Fehlerquelle aus. So im Sinne von: Man definiert nachträglich ein neues Feld in einem Datentyp und irgendwo im Programm geht dadurch etwas kaputt. Das ist zumindest der Grund warum einem bei ``with`` in JavaScript von jedem abgeraten wird.
In dem Fall sollten die Gewinne auch vom Kreis sein. Wollte man innerhalb desselben "with" sowohl etwas mit den Gewinnen der Kreise als auch mit den Gewinnen höherer Verwaltungsebenen machen, ginge das z. B. so:

Code: Alles auswählen

for Zaehler1:=1 to Staatenanzahl do with Staat[Zaehler1] do
  for Zaehler2:=1 to Bundeslaenderanzahl do with Bundesland[Zaehler2] do
    for Zaehler3:=1 to Regierungsbezirkanzahl do with Regierungsbezirk[Zaehler3] do
      for Zaehler4:=1 to Kreisanzahl do with Kreis[Zaehler4] do begin
        Gewinn := (Einnahmen - Ausgaben) * 0.9; 
        Regierungsbezirk[Zaehler3].Gewinn := Regierungsbezirk[Zaehler3].Gewinn + (Einnahmen - Ausgaben) * 0.1; end;
Zuletzt geändert von Aries am Samstag 24. August 2013, 13:15, insgesamt 1-mal geändert.
BlackJack

@Aries: Gegen die leere Klasse, also nur mit ``pass`` als Klassenkörper, spricht IMHO das man dann alle Attribute von aussen setzen muss. Das ist keine vernünftige objektorientierte Programmierung. Bei mehr als einem Attribut wird der Quelltext unnötig aufgebläht, eben weil man alle Attribute einzeln von aussen setzen muss. Und man wird ja mehr als ein Attribut haben, sonst bräuchte man kein zusätzliches Objekt auf dem man die Daten speichert.

Du scheinst Klassen noch wie ``RECORD``\s behandeln zu wollen in denen nur Daten gespeichert werden. Aber sie sind mehr. Klassen sind dazu da Daten und dazugehörige Funktionen zusammenzufassen, welche an diese Daten gekoppelt sind und Operationen darauf durchführen.

So etwas nach dem Muster ``a[0].b = 15`` halte ich ausserdem schon für einen „code smell”, oder zumindest für ziemlich ungewöhnlichen Quelltext in Python. Den Indexzugriff würde ich nicht erwarten, weder mit einem festen Index, noch mit einer Laufvariablen. Das kann in wenigen Ausnahmefällen nötig sein, aber die Regel ist das nicht.

Bei ``a = (A() for _ in xrange(2))`` kann man nicht auf einen Index von `a` zugreifen, weil der Ausdruck keine Liste ist, sondern ein Generatorausdruck. Über den kann man iterieren, dann erzeugt er in jedem Iterationsschritt ein Element. Im Gegensatz zu einer „list comprehension” (LC), die alle Elemente sofort erzeugt und eine Liste als Ergebnis hat.

Code: Alles auswählen

In [6]: a = (A() for _ in xrange(2))

In [7]: type(a)
Out[7]: generator

In [8]: a
Out[8]: <generator object <genexpr> at 0xb6be534c>

In [9]: for x in a:
   ...:     print x
   ...: 
<__main__.A instance at 0xb6bba32c>
<__main__.A instance at 0xb6bba42c>

In [10]: a = (A() for _ in xrange(2))

In [11]: next(a)
Out[11]: <__main__.A instance at 0xb6beb86c>

In [12]: next(a)
Out[12]: <__main__.A instance at 0xb6beb26c>

In [13]: next(a)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/home/bj/<ipython-input-13-3f6e2eea332d> in <module>()
----> 1 next(a)

StopIteration:
LC und Generatorausdruck unterscheiden sich durch die Art der Klammern. Bei ``a = (A(), A())`` hast Du auch keine Liste erzeugt, sondern ein Tupel. Das funktioniert weil man auf Tupel auch lesend per Index auf die Elemente zugreifen kann. Die Klammern hätte man auch weglassen können, denn bis das leere Tupel (``()``) werden Tupel durch Kommas erzeugt.

Ich weiss nicht ob mein Problem mit Pascal's ``WITH`` klar rübergekommen ist. Angenommen wir haben folgenden Quelltext:

Code: Alles auswählen

type TFoo = record
    a: Word;
    { ... }
  end;

{ ... }

procedure Bar(foo: TFoo);
var x: Word;
begin
    x := 42;
    with foo do
    begin
        x := x * Frobnicate(foo.a);
        { ... }
    end;
    DoSomethingWith(x);
end;
Und wenn man jetzt irgendwann ein Feld zu `TFoo` hinzufügt das zufällig `x` heisst, dann geht irgendwo im Programm die Funktion auf subtile Weise kaputt die zufällig den gleichen lokalen Namen in einem ``with``-Block verwendet. Oder nicht? Selbst wenn nicht, ist es auf jeden Fall verwirrend. Aus Pythonsicht erscheint mir Pascal's ``WITH`` wie ein kleiner Sternchenimport, der alle Namen aus einem ``RECORD`` in den aktuellen Namensraum holt, ohne das man an der Stelle sieht welche das eigentlich sind. Bei mehreren, verschachtelten ``WITH`` wird es IMHO noch unübersichtlicher.

Ich wiederhole es an dieser Stelle noch einmal: `Gewinn` würde man in Python nicht auf diese Weise berechnen, sondern in einem Property in der entsprechenden Klasse. Genau das würde man in ”modernem” Pascal übrigens auch machen. Mal als Vergleich Pascal (mit FreePascal im Delphi-Modus) und Python:

Code: Alles auswählen

program Test;
{$mode delphi}

uses classes, contnrs;

type
  TLandkreis = class(TObject)
    private
      FName : String;
      FEinnahmen : Real;
      FAusgaben : Real;
      function GetGewinn : Real;
    public
      property Name : String read FName;
      property Einnahmen : Real read FEinnahmen write FEinnahmen;
      property Ausgaben: Real read FAusgaben write FAusgaben;
      property Gewinn: Real read GetGewinn;
      constructor Create(name: String; einnahmen, ausgaben: Real);
  end;
  TRegierungsbezirk = class(TObject)
    private
      FName : String;
      FKreise : TObjectList;
      function GetCount : Integer;
    public
      property Name: String read FName;
      property Count: Integer read GetCount;
      function GetEnumerator : TListEnumerator;
      procedure Add(landkreis: TLandkreis);
      constructor Create(name: String);
      destructor Destroy; override;
  end;

var regierungsbezirk: TRegierungsbezirk;
    landkreis: TLandkreis;

constructor TLandkreis.Create(name: String; einnahmen, ausgaben: Real);
begin
  FName := name;
  FEinnahmen := einnahmen;
  FAusgaben := ausgaben;
end;

function TLandkreis.GetGewinn : Real;
begin
  Result := Einnahmen - Ausgaben;
end;

constructor TRegierungsbezirk.Create(name: String);
begin
  FName := name;
  FKreise := TObjectList.Create(true);
end;

destructor TRegierungsbezirk.Destroy;
begin
  FKreise.Destroy;
end;

function TRegierungsbezirk.GetEnumerator : TListEnumerator;
begin
  Result := FKreise.GetEnumerator;
end;

function TRegierungsbezirk.GetCount : Integer;
begin
  Result := FKreise.Count;
end;

procedure TRegierungsbezirk.Add(landkreis: TLandkreis);
begin
  FKreise.Add(landkreis);
end;

begin
  regierungsbezirk := TRegierungsbezirk.Create('Bezirk A');
  regierungsbezirk.Add(TLandkreis.Create('Kreis 1', 42, 23));
  regierungsbezirk.Add(TLandkreis.Create('Kreis 2', 1337, 4711));

  writeln('Der Bezirk "', regierungsbezirk.Name, '" hat ',
    regierungsbezirk.Count, ' Landkreise.');
  for landkreis in regierungsbezirk do
    writeln('Gewinn ', landkreis.Name, ': ', landkreis.Gewinn:1:2);
  regierungsbezirk.Destroy;
end.
Das ganze in Python:

Code: Alles auswählen

#!/usr/bin/python
#-*- coding: utf-8 -*-


class Landkreis(object):
    def __init__(self, name, einnahmen=0, ausgaben=0):
        self.name = name
        self.einnahmen = einnahmen
        self.ausgaben = ausgaben
        
    @property
    def gewinn(self):
        return self.einnahmen - self.ausgaben


class Regierungsbezirk(object):
    def __init__(self, name, kreise=()):
        self.name = name
        self.kreise = list(kreise)

    def __len__(self):
        return len(self.kreise)
    
    def __iter__(self):
        return iter(self.kreise)

    def add(self, landkreis):
        self.kreise.append(landkreis)


def main():
    regierungsbezirk = Regierungsbezirk(
        'Bezirk A',
        [Landkreis('Kreis 1', 42, 23), Landkreis('Kreis 2', 1337, 4711)]
    )
    print 'Der Bezirk "{0}" hat {1} Landkreise.'.format(
        regierungsbezirk.name, len(regierungsbezirk)
    )
    for landkreis in regierungsbezirk:
        print 'Gewinn {0.name}: {0.gewinn}'.format(landkreis)


if __name__ == '__main__':
    main()
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Aries hat geschrieben:
pillmuncher hat geschrieben:Vielleicht noch das: Wenn man anfängt Python zu lernen und schon Sprachen wie Pascal, C/C++ oder Java kennt, ist es wichtig, dass man sich klar macht, dass Python viel mehr Verwandschaft mit Lisp hat, als mit den genannten Sprachen.
Nun, ich kann bisher nur Pascal und habe ansonsten lediglich in Fortran, C/C++ und Prolog mal reingeschnuppert. Trotzdem würde es mich interessieren, worauf Du anspielst, wenn Du sagst, Python hätte mehr Verwandtschaft mit Lisp als mit Pascal/C/Java.
Python hat inzwischen fast alle Eigenschaften, die es zu einem acceptable Lisp machen:

Paul Graham: What Made Lisp Different
Die dort gennanten Eigenschaften besitzt Python zum großen Teil inzwischen auch, und die, die fehlen (zB. Macros), kann man auf anderem Wege simulieren (zB. so wie Sympy das tut, oder mit dem AST-Modul).

Peter Norvig: Python for Lisp Programmers
Zitat: "Python can be seen as either a practical (better libraries) version of Scheme [...]"
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Aries: Hier kann man hören, was der BDFL zu selber zu sagen hat zum Thema Python & Lisp: http://www.python-forum.de/viewtopic.php?f=21&t=6907
In specifications, Murphy's Law supersedes Ohm's.
Antworten