Verständnisproblem mit global und import

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.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Wäre nett, wenn mir jemand folgendes Verhalten erklären könnte.

Code: Alles auswählen

# counter.py

n = 0

def count():
    global n
    n += 1

Code: Alles auswählen

import counter

print counter.n
counter.count()
print counter.n
Liefert wie erwartet erst 0 und dann 1.

Code: Alles auswählen

from counter import *

print n
count()
print n
Liefert - für mich unerwartet - beidesmal 0.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Das hat mit global nichts zu tun. Du kopierst durch den import alles aus counter in den Namespace von __main__ aber die Funktion arbeitet deswegen noch lange nicht in dem Namespace von __main__.
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

DAS Problem kenn' ich,
guck mal hier:
http://www.python-forum.de/topic-16020.html?highlight=
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

@yipyip: Danke für den Link! Insbesondere der Beitrag von BlackJack in diesem Thread hat ein Stück weitergeholfen.

@DasIch: Das mit dem "kopieren" ist so eine Sache. Sieh dir das mal an:

Code: Alles auswählen

# changer.py
d = {}

def change():
    d["key"] = "value"

Code: Alles auswählen

from changer import *

print d
change()
print d
Liefert:

Code: Alles auswählen

{}
{'key': 'value'}
Ich verstehe es so: Beim Import eines Moduls werden Kopien aller Objekte auf Modulebene (des importieren Moduls) angelegt, sofern es sich um unveränderbare Objekte handelt; es existiert dann ein neues Objekt mit diesem Namen.
Sind es hingegegen veränderbare Objekte, wird statt einer Kopie eine Referenz angelegt.

Richtig?
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Es ist immer eine Referenz, nur manche Objekte sind eben nicht veränderbar. Wird im Modul ein anderes Objekt an einen bestimmten Namen gebunden, zeigt die Referenz im anderen Modul, das diesen Namen importiert hat, eben immer noch auf das andere (also "alte") Objekt.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

numerix hat geschrieben:Richtig?
Scheint so.

Auf jedenfall würde ich sagen dass man soetwas einfach vermeiden sollte.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

DasIch hat geschrieben:Auf jedenfall würde ich sagen dass man soetwas einfach vermeiden sollte.
ACK!
BlackJack

@numerix: Python kopiert keine Objekte wenn man das nicht explizit will, und macht da auch keinen Unterschied zwischen veränderbaren und nicht veränderbaren Objekten.

Der Unterschied ist einfach ob man ein Objekt verändert, oder einen Namen an ein neues Objekt bindet.

In dem Sternchenimport-Beispiel wird zuerst `counter.n` an das Objekt 0 gebunden, dann werden durch das ``import`` im aktuellen Modul alle Objekt aus `counter` an gleichlautende Namen im aktuellen Modul gebunden. Das `n` aus dem aktuellen Modul wird ausgegeben. Beim Funktionsaufruf wird `counter.n` an ein neues Objekt (1) gebunden. Was natürlich nichts an der Bindung von `n` im aktuellen Modul an das 0-Objekt ändert. Und das wird dann noch einmal ausgegeben.

Unterschied beim Dictionary ist, dass Du das Dictionary-Objekt veränderst und natürlich diese Änderung überall sichtbar ist, wo dieses Objekt an einen Namen gebunden ist. Nach dem Aufruf der Funktion sind ja weiterhin sowohl `changer.d` als auch das "Lokale" `d` an das *selbe* Objekt gebunden.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

BlackJack hat geschrieben:@numerix: Python kopiert keine Objekte wenn man das nicht explizit will, und macht da auch keinen Unterschied zwischen veränderbaren und nicht veränderbaren Objekten.
Das verstehe ich jetzt nicht bzw. anscheinend habe ich bisher etwas falsch verstanden:

Code: Alles auswählen

a = 5
b = a # int ist immutable -> Kopie von a wird erzeugt
c = ["nix"]
d = c # list ist mutable -> Referenz von c wird erzeugt
Was passiert hier, wenn nicht das, was die Kommentare sagen?

Über den Rest muss ich morgen nochmal in Ruhe nachdenken ... :?
BlackJack

Warum sollte da eine Kopie von `a` erzeugt werden? In beiden Fällen werden einfach nur je zwei Namen an das selbe Objekt gebunden. Die Situation sieht nach dem Ausführen so aus:

Code: Alles auswählen

 Namen |  Objekte
       |
  a----------->+-------+
       |       |<int> 5|
  b----------->+-------+
       |
  c----------->+--------+            +-----------+
       |       |<list> o+----------->|<str> "nix"|
  d----------->+--------+            +-----------+
       |
       |
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Code: Alles auswählen

In [1]: a = 5

In [2]: b = a

In [3]: a is b
Out[3]: True

In [4]: a += 1

In [5]: a, b
Out[5]: (6, 5)
Es sieht nur so aus, als würde eine Kopie erzeugt. Du operierst ja auf den Referenzen, und wenn du eine neu bindest, berührt das die andere nicht, die dann weiterhin auf das alte Objekt zeigt.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

numerix hat geschrieben:Was passiert hier, wenn nicht das, was die Kommentare sagen?
Um den Sachverhalt mal zu verdeutlichen:

Code: Alles auswählen

a += 1 # und
a["key"] += 1
sind zwei verschiedene Ausdrücke. Das erste ist äquivalent zu:

Code: Alles auswählen

a = a + 1
das zweite zu

Code: Alles auswählen

a.__setitem__("key", a["key"] + 1)
Im ersten Fall bindest du an den Namen a ein Objekt(was meist ein neues Objekt sein wird) während du im zweiten Fall den Namen a in Ruhe lässt.[/code]
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Danke an euch, dass ihr euch so bemüht, mir das verständlich zu machen.
Ich glaube, so richtige gefressen hab ich es noch nicht.

Meine bisherige Vorstellung, was bei der Zuweisung an eine Variable in Abhängigkeit vom Datentyp passiert, halte ich nach wie vor für tragfähig und es schmerzt mich, dass das - trotz der Richtigkeit bezogen auf das Resultat - dennoch anscheinend nicht das ist, was tatsächlich passiert.

Meine Sicht war (und ist es momentan noch, bis ich das richtige Verständnis für mich tragfähig habe):
Beim Binden eines unveränderlichen Objekts an einen neuen Namen wird eine Kopie des Objekts erzeugt; beide Namen verweisen auf identische Objekte, die aber je eine eigene Identität haben (darum passt für mich er Begriff der "Kopie" an dieser Stelle), so dass Änderungen an einem der Objekte sich nicht auf das andere auswirken.

Beim Binden eines veränderlichen Objekts an einen neuen Namen wird nur eine neue Referenz auf das gleiche Objekt angelegt; es gibt nach wie vor nur ein einziges Objekt mit einer einzigen Identität. Änderungen an diesem einen Objekt sind über jeden Namen möglich, an den das eine Objekt gebunden ist.

Auch wenn es - wie BlackJack sagt (und ich gehe einfach mal davon aus, dass das so korrekt ist) - keine Kopie ist, so ist das Verhalten doch so, wie man es von einer Kopie erwarten würde.

Entsprechend habe mir auch die Parameterübergabe bei Funktionen vorgestellt: Als Wertparameter bei unveränderlichen Datentypen und als Referenzparameter bei veränderlichen Datentypen.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Dann lass dir doch einfach einmal die Identitäten der Objekte ausgeben (mit `id()`), dann wirst du sehen, dass es die selben Objekte sind. Außerdem, warum sollten denn von unveränderlichen Objekten überhaupt Kopien erstellt werden? Schließlich sind sie ja unveränderlich. Und wie soll Python feststellen, ob ein Typ unveränderlich ist oder nicht?
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Kannst du C?

Ich kann es zwar nur rudimentär, aber es lässt sich in etwa so veranschaulichen:

Code: Alles auswählen



int eins = 1;
int zwei = 2;
int drei = 3;

...

int *name_a = &eins;
int *name_b = &eins;

// Neu binden bei name_a++

name_a = &zwei

// name_b bleibt weiterhin &eins
(Gibts eigentlich einen Unterschied zwischen int* name und int *name? Müsste doch beides einen Zeiger erzeugen...)

Es ist wie bei einem Bücherregal. Du linkst mit einer Referenz auf ein Buch. Mit einer zweiten wieder zur grade angelegten. Intern wird beides Mal aufs gleiche Buch gelinkt.

Wenn du jetzt das Nachfolgende Buch bekommen willst, bindest du den Namen neu. Die alte Referenz wird aufgelöst und du bekommst eine neue auf das neue Buch. Die andere Referenz berührt das nicht.

Der Unterschied zum kopieren ist, das den Büchern deine Referenzen egal sind. Die existieren trotzdem weiter, auch wenn niemand auf sie referiert. [1] Bei einem veränderlichen Objekt änderst du ja nicht die Referenz zum eigentlichen Ding; nur die Interna eben dieses Objekts. Bei dem Bücherbeispiel wäre das zum Beispiel ein Regal. Das ändert seinen Ort ja auch nicht, wenn man ein Buch reinstellt.

[1] In Python werden Objekte natürlich vom gc vernichtet, wenn niemand mehr auf sie referiert. Trotzdem bleiben die Objekte theoretisch, einmal instanziert, immer die gleichen.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Trundle hat geschrieben:warum sollten denn von unveränderlichen Objekten überhaupt Kopien erstellt werden? Schließlich sind sie ja unveränderlich. Und wie soll Python feststellen, ob ein Typ unveränderlich ist oder nicht?
Das sind für mich Fragen auf einer Meta-Ebene, die über das hinausgehen, was für mich wichtig ist (meine Ansprüche sind halt bescheiden ...). Für mich genügt es zu wissen, wie es ist, und zu verstehen, wie es ist. Warum dies oder jenes in Python so oder so implementiert ist, warum eine Kopie erstellt werden sollte und wie Python irgendetwas feststellen sollte, das sind Fragen, mit denen ernsthaft mich zu beschäftigen ich keine Zeit habe - ich verdiene mein Geld nicht mit dem Programmieren.

Nochmal zurück zu meiner (bisherigen) Vorstellung von dem, wie es ist: Fakt scheint aber doch zu sein, dass gemessen am "Verhalten" es tatsächlich so sein könnte, wie ich es mir vorstelle, also quasi so eine Art "duck typing": Ist zwar keine Kopie, verhält sich aber so. Alles, was dazu an Kritik bisher gekommen war, lautete im wesentlichen ja: "Nein, es ist eine Kopie und warum sollte Python auch eine Kopie anlegen."

Um nicht falsch verstanden zu werden: Es geht mir nicht darum, auf einer Sichtweise zu beharren, die faktisch falsch ist; zu provozieren oder irgendwelchen Widerspruch herauszufordern, sondern für mich zu klären, was an meiner bisherigen Vorstellung falsch war und was nicht.

Nach eurem weiteren Bemühen glaube ich, dass der Nebel sich langsam lichtet. Ich versuche es nochmal:

Bei der Bindung eines Objektes an einen Namen, wird IMMER eine Referenz erzeugt, ganz unabhängig vom Datentyp. Bei einer weiteren Zuweisung an eine Variable, wird ein weiterer Name an dieses Objekt gebunden (oder muss es heißen: Das Objekt wird an den Namen gebunden? Oder ist das egal?) und es ist nach wie vor nur ein und dasselbe Objekt, unabhängig vom Datentyp.

Wird jetzt über einen der Namen eine Änderung am Objekt vorgenommen, DANN kommt zum Tragen, ob das Objekt veränderlich ist oder nicht:

Ist es veränderlich, dann wird es halt verändert, aber es bleibt das gleiche Objekt, das weiterhin an alle Namen gebunden ist, an die es vorher auch schon gebunden war.

Ist es aber nicht veränderlich, dann erfolgt gar keine Änderung (weil es ja nicht möglich ist), sondern es entsteht ein NEUES Objekt, das per Zuweisung an einen neuen Namen gebunden wird. Ist der neue Name aber gar kein neuer Name, sondern der alte Name, dann sieht es zwar so aus, als wäre eine Änderung an einer Kopie des ursprünglichen Objekts erfolgt, in Wahrheit hat es aber nie eine Kopie gegeben, sondern es ist jetzt gerade ein neues Objekt erzeugt und an den alten Namen gebunden worden. Da das dem ursprünglichen Objekt und dem daran gebundenen Namen egal ist, bleibt es wie es ist.

Richtig?
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Exakt :)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

numerix hat geschrieben:Wird jetzt über einen der Namen eine Änderung am Objekt vorgenommen, DANN kommt zum Tragen, ob das Objekt veränderlich ist oder nicht:
Naja. Wenn über den Namen eine Änderung am Objekt vorgenommen wird, kann es ja wohl kaum unveränderlich gewesen sein.
numerix hat geschrieben:Ist es veränderlich, dann wird es halt verändert, aber es bleibt das gleiche Objekt, das weiterhin an alle Namen gebunden ist, an die es vorher auch schon gebunden war.
Das schon, ja.
numerix hat geschrieben:Ist es aber nicht veränderlich, dann erfolgt gar keine Änderung (weil es ja nicht möglich ist), sondern es entsteht ein NEUES Objekt, das per Zuweisung an einen neuen Namen gebunden wird.
Zuweisung erstellt immer neue Bindungen bzw überschreibt alte Bindungen; insofern - ja stimmt soweit.
numerix hat geschrieben:Ist der neue Name aber gar kein neuer Name, sondern der alte Name, dann sieht es zwar so aus, als wäre eine Änderung an einer Kopie des ursprünglichen Objekts erfolgt, in Wahrheit hat es aber nie eine Kopie gegeben, sondern es ist jetzt gerade ein neues Objekt erzeugt und an den alten Namen gebunden worden.
Das ist mir etwas zu kompliziert formuliert. Durch die Zuweisung bindest du das Objekt auf der rechten Seite (egal ob es jetzt frisch erstellt ist oder schon anderswo gebunden ist) an einen Namen auf der linken Seite. Das ist unabhängig davon ob das Objekt mutable ist oder nicht:

Code: Alles auswählen

l = [1, 2, 3]
l = [1, 3, 3, 7]
t = (1, 2, 3)
t = (1, 3, 3, 7)
Es ist wohl klar das hier jeweils t und l auf die gleiche Seite neu gebunden werden. Ob das nun ein immutables Tupel ist oder eine mutable Liste spielt hier ja keine Rolle. Ich glaube auch nicht, dass du erwarten würdest dass hier irgendwas herumkopiert wird.
numerix hat geschrieben:Da das dem ursprünglichen Objekt und dem daran gebundenen Namen egal ist, bleibt es wie es ist.
Den Objekten ist es so oder so egal an was für Namen sie gebunden sind, sie wissen nicht wie sie heißen und es ist egal ob sie 0, 1 oder 23 Namen haben.

Apropos, auf dieser Ebene des Verständnisses sollte man übrigens auch nicht von Variablen sprechen, da der Begriff eher irreführend ist und in Python nicht wirklich zutrifft.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

numerix hat geschrieben:Richtig?
Jein. Es gibt auf Code-Ebene absolut keinen Unterschied zwischen der Behandlung von ver- und unveränderlichen Objekten. Gar keinen. Einfach deswegen, weil die Variablen/Namen untypisiert ist. Python weiß nicht was für Objekte dahinter stecken. Dementsprechend kann man dazwischen auch nicht unterscheiden.

Code: Alles auswählen

a = irgendein_objekt
b = a
a += 1
Was für einen Wert jetzt b hat hängt nur davon ab, wie ``irgendein_objekt`` auf ``+`` reagiert. Völlig unabhängig ob jetzt veränderlich oder nicht. Auch ein veränderliches Objekt darf bei ``+``ein neues Objekt zurückgeben. Ob das sinnvoll ist, hängt vom Objekt ab.
str1442 hat geschrieben:(Gibts eigentlich einen Unterschied zwischen int* name und int *name
Nein, Leerzeichen sind an der Stelle egal. Ich schreibs zum Typ, weil der Punkt nicht zum Namen gehört.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Ich habe das gute Gefühl, dass mir die Sache jetzt klar ist. :D

Dass ich mich damit so schwer tue (nein: getan habe) liegt vermutlich auch daran, dass sich nach vielen Jahren Pascal-Programmierung die Vorstellung von Variablen als Schubladen, in die etwas hineingesteckt wird, ziemlich gefestigt hat.

Danke für eure Geduld und Unterstützung!
Antworten