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

Donnerstag 20. November 2008, 21:58

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

Donnerstag 20. November 2008, 22:04

Exakt :)
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Freitag 21. November 2008, 01:03

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 Modvoice
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Freitag 21. November 2008, 01:11

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

Freitag 21. November 2008, 10:18

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!
BlackJack

Freitag 21. November 2008, 10:20

@Darii: Ich schreib den "Stern" immer zum Namen weil sonst so etwas wie ``int* foo, bar;`` (noch) leichter falsch verstanden werden kann.
Qubit
User
Beiträge: 75
Registriert: Dienstag 7. Oktober 2008, 09:07

Freitag 21. November 2008, 12:41

numerix hat geschrieben: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.
Mal generell:
Man sollte zwischen Bezeichnern, Objekten und deren Referenzen unterscheiden.
Variablen sind Bezeichner für Referenzen, mehr nicht.
"=" Statements sind Definitionen, die einem Bezeichner links vom "=" eine Referenz rechts vom "=" zuweisen. Die Referenz wird hierbei vom Python Environment gemanaged:
Steht zb. rechts auch ein Bezeichner, wird dessen Referenz verwendet, steht dort ein Objekt, wird dort eine Referenz erzeugt, indem ein neues Objekt mit neuer Referenz verwendet wird; dies ist völlig unabhängig davon, ob das Objekt "mutable" ist oder nicht (wobei Referenzen bestehender immutable Objekte hierbei präferiert werden). Für gewisse Objekte wie bei "class" und "def" werden automatisch Bezeichner mit den entsprechenden Referenzen erzeugt, und das zur "Compilezeit" und nicht erst zur Laufzeit! D.h. zB bei Definitionen von Funktionen, dass auch deren Parameter zur Compilzeit schon an Objektreferenzen "gebunden" werden. Sind dies "mutable" Objekte, kann die Vorbelegung der Parameter bei jedem Aufruf ein geändertes Objekt bedeuten!
Die Bezeichner haben eine weitere wichtig Eigenschaft: Namensraum.
Die Sichtbarkeit von Bezeichnern ist "user-managed", d.h. der Programmierer kann Einfluss darauf nehmen, indem er Bezeichner entsprechend klassifiziert (zB mit "global") oder explizit externe Namensräume einbindet (zB über "import"). Dies geschieht ebenfalls zur Compilezeit! Er kann auch Bezeichner aus einem Namensraum entfernen (zB mit "del"); hierbei hat er jedoch keinen Einfluss auf die Speicherallokation der Objekte, diese wird ausschliesslich vom Python Environment gemanaged (GC mittels Referenzcounter).

Okay, was mach dein "global" nun in counter.py:
Es bindet den Bezeichner "n" im Namensraum von "counter" in den lokalen Namensraum von "count" ein. Das zur Compilezeit!
Jede Änderung von "n" in diesem Namensraum ist somit eine Änderung der Referenz von "counter.n".

Was machen nun deine Beispiele:
"import counter" bindet den Bezeichner "counter", der "counter.py" referenziert, in den lokalen Namensraum von "__main__" ein.
Deine "print" Ausgaben sind durch "counter.n" referenziert, also genau die Bezeichner, auf die "counter.count" zugreifft. Alles okay.

"from counter import *" bindet die Bezeichner "n" und "count" in den lokalen Namensraum ein. "n" hält hierbei die gleiche Referenz wie "counter.n".
Deine "print" Ausgaben sind durch "n" referenziert, "count" jedoch operiert im Namensraum "counter.n" mit "n". D.h. die Referenz von "counter.n" ist nicht mehr gleich der von "n", das immer noch seine ursprüngliche Referenz hält (zum Zeitpunkt vom import)

Welche Möglichkeiten hat man, im Namensraum von "__main__" die Referenz von "n" durch "counter.count" zu ändern?

Zwei Beispiele, die jetzt verständlich sein sollten:

Code: Alles auswählen

import counter
from counter import *
print n
count()
n = counter.n
print n
oder

Code: Alles auswählen

### counter.py

n = [0]

def count():
    global n
    n[0] += 1
###


from counter import *
print n[0]
count()
print n[0]
Zuletzt geändert von Qubit am Freitag 21. November 2008, 13:35, insgesamt 1-mal geändert.
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

Freitag 21. November 2008, 13:02

Hi,
gibt es hier vllt. eine Analogie zu dem Linuxbefehl

Code: Alles auswählen

link [-s]
?
Also ein Hardlink (link) hat soweit ich weiß ja die selbe Inode Nummer wie
die Datei auf die er zeigt ist jedoch nur ein weiterer Name für die Datei.
Ein Softlink (link -s) hat aber doch eine eigene Inodenummer und zeigt wirklich nur auf die Datei, d.h. wenn man ihn anklickt öffnet man eigentlich die Datei auf die er zeigt.
MfG Jonas
BlackJack

Freitag 21. November 2008, 13:37

Gibt es. Aber ohne das `-s`.
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Freitag 21. November 2008, 14:44

Steht zb. rechts auch ein Bezeichner, wird dessen Referenz verwendet, steht dort ein Objekt, wird dort eine Referenz erzeugt, indem ein neues Objekt mit neuer Referenz verwendet wird
Das stimmt zwar, ist aber leicht verwirrend, da du ja kein Objekt ohne Bezeichner schreiben kannst, es sei denn, es wird durch eine Aktion (Aufrufen, mehrere Objekte bauen irgendwas, was auch immer) erzeugt.

@jonas:

Statt link verwende besser "ln", das hat mehr Funktionen, wird afaik öfter verwendet und kann links zwischen allem kreieren.

Außerdem hat bei mir link überhaupt keine "s" Option, und bekommt wohl nur Hardlinks hin.
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

Freitag 21. November 2008, 15:35

Hi
@Blackjack:
Bei mir in der Schule (opensuse 10) gibt es einen befehl link -s (?)
@str1442:
ln war mir noch nicht bekannt werde ich aber probieren.

MfG Jonas
PS: SCHÖNES WOCHENENDE!
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Freitag 21. November 2008, 15:47

jonas: Frage lesen, welche BlackJack beantwortet hat :wink: Es gibt eine Analogie zu link, nicht zu link -s.
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Freitag 21. November 2008, 16:13

@Qubit: Danke auch an dich für die ausführliche Behandlung des Ausgangsproblems. Um sicher zu gehen, dass ich auch das jetzt verstanden habe, nochmal mit meinen Worten:

Code: Alles auswählen

# counter.py
n = 0
def count():
    global n
    n += 1

Code: Alles auswählen

# test.py
from counter import *
print n
count()
print n
Durch den import wird im globalen Namensraum von test.py eine Referenz auf n und count im Modul counter.py angelegt, d.h. zwei die Namen n und count werden an diese beiden Objekte aus counter.py gebunden. Beim Aufruf von count() wird also das Objekt count aus counter.py aufgerufen. Im lokalen Namensraum von count() wird der Bezeichner n als globaler Bezeichner (des Moduls counter.py) deklariert und damit die Möglichkeit eröffnet, an den globalen Bezeichner n innerhalb des lokalen Namensraums von count() ein neues Objekt zu binden. Genau dies geschieht dann durch die Zuweisung "n+=1": Der Name n wird neu an ein Objekt gebunden (und nicht der Wert des Objekts um 1 erhöht!). Ab diesem Zeitpunkt ist das n in counter.py nicht mehr das n in test.py, welches nämlich nach wie vor an das gleiche Objekt (nämlich die 0) gebunden ist.

Richtig?
Qubit
User
Beiträge: 75
Registriert: Dienstag 7. Oktober 2008, 09:07

Freitag 21. November 2008, 16:21

numerix hat geschrieben: Durch den import wird im globalen Namensraum von test.py eine Referenz auf n und count im Modul counter.py angelegt, d.h. zwei die Namen n und count werden an diese beiden Objekte aus counter.py gebunden. Beim Aufruf von count() wird also das Objekt count aus counter.py aufgerufen. Im lokalen Namensraum von count() wird der Bezeichner n als globaler Bezeichner (des Moduls counter.py) deklariert und damit die Möglichkeit eröffnet, an den globalen Bezeichner n innerhalb des lokalen Namensraums von count() ein neues Objekt zu binden. Genau dies geschieht dann durch die Zuweisung "n+=1": Der Name n wird neu an ein Objekt gebunden (und nicht der Wert des Objekts um 1 erhöht!). Ab diesem Zeitpunkt ist das n in counter.py nicht mehr das n in test.py, welches nämlich nach wie vor an das gleiche Objekt (nämlich die 0) gebunden ist.

Richtig?
Ja! Genau so sehe ich das..
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Samstag 22. November 2008, 14:25

BlackJack hat geschrieben:@Darii: Ich schreib den "Stern" immer zum Namen weil sonst so etwas wie ``int* foo, bar;`` (noch) leichter falsch verstanden werden kann.
Kannst du mir erklären, was für eine Logik dahintersteckt?
Antworten