Explizite Variablenübergabe by Value / by reference

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

NoPy hat geschrieben:Oder anders: Es ist anscheinend nicht möglich, Strings oder Zahlen per Referenz zu übergeben, man müsste also entsprechende Klassen extra schreiben.
Strings und Zahlen funktionieren, was das angeht, nicht anders als alle anderen Typen.

In C-sprech: In Python werden *immer* Zeiger auf die Objekte weitergereicht und *niemals* "Zeiger auf Zeiger". Da gibt es keine Ausnahmen oder Spezialfälle. Es gibt da auch keine Unterscheidung zwischen veränderlichen und unveränderlichen Typen. Ihr Verhalten ist immer gleich. Nur die Wirkung der überladenen Operatoren wie ".attribut =", "+=", "[schlüssel] = " etc. ist verschieden.

Den Unterschied den du scheinbar feststellt gibt es nur, weil du über die Bedeutung und Wirkung dieser Operatoren stolperst.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

@sirius3
C hat da aber nicht nur void**- Spielereien. Man kann da schon explizit angeben, ob Referenz oder Wert genommen werden kann. Wie auch in Delphi/Pascal (wobei ich finde, dass gerade dort eine entsprechende Verschlimmbesserung zur "Vereinfachung" eingeführt wurde.

C: Test(&MeineLokaleVariablePerReferenz) bewirkt, dass die aufgerufene Funktion den Wert verändern kann
Pascal: Test(addr(MeineLokaleVariablePerReferenz)) genauso

Also möglich ist es, m.E. auch übersichtlich

@Darii
Strings und Zahlen funktionieren, was das angeht, nicht anders als alle anderen Typen.

In C-sprech: In Python werden *immer* Zeiger auf die Objekte weitergereicht und *niemals* "Zeiger auf Zeiger". Da gibt es keine Ausnahmen oder Spezialfälle. Es gibt da auch keine Unterscheidung zwischen veränderlichen und unveränderlichen Typen. Ihr Verhalten ist immer gleich. Nur die Wirkung der überladenen Operatoren wie ".attribut =", "+=", "[schlüssel] = " etc. ist verschieden.

Den Unterschied den du scheinbar feststellt gibt es nur, weil du über die Bedeutung und Wirkung dieser Operatoren stolperst.
Dann tut sich die Frage auf, wie ich diesen Zeiger wieder dereferenzieren kann, denn ich will ja genau die verwaltete Speicherstelle verändern. Bei Strings mag das als zu kompliziert erachtet worden sein, da man ja u.U. einen neuen Speicherbereich aquirieren muss und alle Verweise darauf nachziehen. Aber bei gewöhnlichen Integern sollte das doch kein technisches Problem sein, höchstens ein syntaktisches.

Wenn ich also die Adresse kenne und weiß, dass dort ein Integer gespeichert ist, wie kann ich diesen Integer dann verändern?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@NoPy: Zeigerspielereien führen in sehr vielen Fällen zu Fehlern, die schwer zu finden sind (jedenfalls viel schwerer als Schreibfehler ;-). Deshalb sind sie in vielen „modernen“ Sprachen nicht verfügbar. Eine effektive Garbage-Collection ist nicht mehr möglich, wenn man auf Teile eines Objektes verweisen kann. Es gibt kaum einen Fall, wo das explizite Verwenden von Zeigern nicht mehr Probleme macht, als es löst.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Sehe ich etwas anders, gerade Zuweisung per Referenz oder Wert halte ich nach wie vor für sehr sinnvoll (was ich über Garbage- Collektoren nicht zu behaupten wage. Wenn man sauber programmiert, ist das Aufräumen auch kein Problem und sollte auch von dem getan werden, der den Speicher angefordert hat)
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Ich finde NoPy hat völlig recht. Man sollte genau dieselben Konzepte in allen Programmiersprachen verwenden, die man immer schon verwendet hat. Man kann das auch auf natürliche Sprachen übertragen. Der deutsche Satz "Als ich in New York war, habe ich die Freiheitsstatue gesehen" sollte dann auf Englisch heißen: "When I in New York was, have I the libertystatue seen". Da. Gleich viel besser.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@NoPy: In C kann man nur „by value” übergeben, das kennt nichts anderes. Es gibt dort halt den Datentyp „Pointer (of…)”, aber auch der wird als Wert übergeben.

Python und andere moderne, auch statisch typisierte Programmiersprachen, haben einfach keinen „Pointer”-Datentyp. Da gibt es nichts was man derefenzieren könnte.

Die verwaltete Speicherstelle verändern bedeutet ja den Wert zu ändern, und das geht bei nicht veränderbaren Typen per Definition nicht. Aus gutem Grund. Zeichenketten werden als Schlüssel in Abbildungen verwendet, die Fehler die da entstehen möchte ich mir gar nicht ausmalen, und bei Zahlen wurde das Problem ja schon genannt: Wenn man die Verändern könnte würde ``a = 1; <magic mit `a`>; print a + 2`` plötzlich 42 ergeben können wenn man den Wert von dem Objekt das an `a` gebunden ist zu 40 ändern könnte.

Ich vermute Du hast eine falsche Vorstellung von „Variablen” in Python. Variablen bestehen aus Name, Typ, Adresse, und Wert. Und bei Python gehören Typ *und* Adresse zum *Wert* und nicht wie bei Pascal zum Beispiel zum *Namen*. Und alles was man an einen Namen binden kann ist ein Objekt. Auch Zahlen. Die werden von der Spezifikation nicht besonders behandelt. Was Du hier als praktisch ansiehst ist das

Code: Alles auswählen

def f(<magie>x):
    x = 23


def main():
    a = 42
    f(a)
    print a


if __name__ == '__main__':
    main()
jetzt plötzlich 23 ausgibt. Du willst also keinen Wert verändern sondern durch einen Aufruf die Bindung des lokalen Namens beim Aufrufer ändern. Und das geht, IMHO Gott sei Dank, nicht. Denn damit rechnet beim lesen von `main()` niemand· Warum sollte man das auch wollen? Warum kann `f` den Wert nicht zurückgeben und man bindet das in `main()` explizit an `a`. Ist doch viel deutlicher. In C ist das nützlich um mehr als einen Wert zurückgeben zu können, aber in Python kann man einfach ein Tupel mit Werten zurückgeben und die per „unpacking” zuweisen. Was ich viel übersichtlicher finde als sich mit Zeigertypen und (de)referenzieren zu beschäftigen.

Die manuelle Speicherverwaltung frisst meine Zeit. Was bedeutet denn der der angefordert hat muss auch wieder freigeben in einem komplexeren System mit Funktionen die Speicher anfordern und einen Zeiger in einen Container-Datentyp stecken den Container dann als Rückgabewert liefern. Man muss sich bei so etwas immer Gedanken machen ob man nun einen Wert oder einen Zeiger hat, wer möglicherweise noch Zeiger auf oder in den Wertspeicherbereich hat, und ob und wann man den Speicher wieder freigeben kann oder das noch nicht tun darf. Oder man kopiert immer alles was irgend geht als Werte in der Gegend herum, macht sich damit aber den Effizienz-Vorteil zunichte den Verfechter von manueller Speicherverwaltung immer anführen, weil sie dann mehr Kontrolle über den Speicherverbrauch haben. Klar hat man mehr Kontrolle, dummerweise *muss* man die auch gewissenhaft ausüben, also auch wenn man dadurch gar keine Vorteile hat oder die gar nicht braucht.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

BlackJack hat geschrieben:Klar hat man mehr Kontrolle, dummerweise *muss* man die auch gewissenhaft ausüben, also auch wenn man dadurch gar keine Vorteile hat oder die gar nicht braucht.
Insbesondere letzteres manifestiert sich doch im gesamten Alltag und im Fortschritt von Technologie allgemein: Der Regelfall ist ausschlaggebend für viele Designentscheidungen! Ich kann natürlich versuchen, so viele Ausnahmefälle wie möglich zu unterstützen, aber das wird idR. die Zugänglichkeit beeinträchtigen.

Für die meisten Anwendungsfälle kommt man ohne manuelle Speicherverwaltung gut klar - wozu sollte man also in einer Sprache darauf verzichten wollen, knapp unter 100% der Anwender eine leichtere Handhabung zu ermöglichen? Ich habe in C++ noch *nie* einen Vorteil darin gesehen, dass ich manuell Objekte löschem muss; besonders schlimm ist dort ja noch, dass ich das abhängig von der Erzeugung manuell machen muss... mehr Verwirrung kann man ja kaum stiften.

Es sind doch für die JVM und die CLR schon so viele Programme geschrieben worden, dass man ziemlich beruhigt ob des Konzeptes des Garbage Collectors sein kann ;-)

Von allen grundsätzlichen Abwägungen mal abgesehen bleibt einfach eines: Wenn Du Dich nicht auf neue Dinge einlassen willst oder kannst, dann ist Python sicherlich nichts für Dich. Du wirst mit Deiner Ansicht die Python-Welt und auch die Verfechter von dynamisch typisierten Sprachen nicht ändern... und genauso wenig wirst Du die Business-Mainstream-Welt ändern, die zu einem verdammt großem Prozentsatz auf Sprachen *mit* automatischer Speicherverwaltung unterwegs ist ;-)

Genauso gut werde ich nicht die Delphi-Welt ändern können; dort werden ja viele Entwickler "versaut", weil der Sinn der Trennung von GUI und Logik mindestens mal verschleiert wird ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

@BlackJack:
jetzt plötzlich 23 ausgibt. Du willst also keinen Wert verändern sondern durch einen Aufruf die Bindung des lokalen Namens beim Aufrufer ändern. Und das geht, IMHO Gott sei Dank, nicht. Denn damit rechnet beim lesen von `main()` niemand· Warum sollte man das auch wollen?
Eben darum, weil man manchmal abstrakte Klassen braucht. Und für solche kann es nötig sein, auch Funktionen zu schreiben a la "VerändereWerteVon(...)"
Mal sehen, wenn ich konkreteres habe, dann melde ich mich konkreter

@Hyperion:
Bislang kann ich noch keine Vorteile hinsichtlich schnellerer Code- Entwicklung oder übersichtlicherem Code entdecken und auch das mit dem GC beäuge ich noch etwas, weil ich mir natürlich auch keinen Kopf mehr mache (n kann), wieviele Ressourcen ich gerade verwende. Und bei den Problemen, die ich momentan angehe (geographischer Natur) werden jede Menge Objekte temporär im Speicher herumkullern. Ich hoffe, der GC kommt zurecht. In ein paar Tagen weiß ich mehr ...
BlackJack

@NoPy: Der Wunsch etwas als unveränderlich Entworfenes ändern zu wollen oder Namen im Namensraum des Aufrufers neu binden zu wollen kann sich nicht mit abstrakten Klassen erklären. Bei übergebenen Objekten kann man ja beliebige Änderungen vornehmen sofern es keine Werttypen sind, wodurch Chaos entstehen würde.

Über den Ressourcenverbrauch kann man sich insofern Gedanken machen das man sauberen Code schreibt. Also kurze Funktionen die genau eine definierte Sache machen. Dann kann die Speicherbereinigung sich zeitnah um nicht mehr benötigte Objekte kümmern.

Ein anderer Aspekt sind Iteratoren und Generatoren mit denen man das Programm so gestalten kann, dass Datenmengen sich nicht unnötig im Hauptspeicher ansammeln müssen solange man mit sequentieller Verarbeitung auskommt und keinen wahlfreien Zugriff auf alles braucht.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt wollte ich gerade zeigen, dass man natürlich auch eine Referenz auf eine Variable in Python machen kann, aber die Referenz ist nur read-only :evil:

Code: Alles auswählen

import inspect

class Reference(object):
    def __init__(self, name):
        self.name = name
        self.frame = inspect.stack()[1][0]

    def get(self):
        return self.frame.f_locals[self.name]
    
    def set(self, value):
        self.frame.f_locals[self.name] = value

def test(ptr):
    print ptr.get()
    ptr.set(99)

def main():
    a = 7
    test(Reference('a'))
    print a
    
if __name__ == '__main__':
    main()
BlackJack

Ist auch so dokumentiert: http://docs.python.org/2.7/reference/da ... l#index-65 :-)

Schwierig wird's auch `Reference`-Objekte von C-Code aus zu verwenden. Wenn man die `main()` zum Beispiel in C hat, bekommt man einen `ValueError: call stack is not deep enough` und ich schätze mal wenn so eine in C implementierte `main()` von einer anderen Funktion aufgerufen würde, dann würde die `Reference` auf den Rahmen der falschen Funktion zugreifen.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

NoPy hat geschrieben:Dann tut sich die Frage auf, wie ich diesen Zeiger wieder dereferenzieren kann, denn ich will ja genau die verwaltete Speicherstelle verändern. [...] Aber bei gewöhnlichen Integern sollte das doch kein technisches Problem sein, höchstens ein syntaktisches.
Gar nicht, Python kennt keine Dereferenzierung. Das ist allerdings nicht nur ein Syntaktisches sondern auch ein semantisches Problem. Python macht keine Garantien wie die Objekte intern Verwaltet werden, würdest du per C-Erweiterung eine Dereferenzierungsoperation "reinhacken" hätte das u.U. schlimme Konsequenzen. So optimiert Python kleine Zahlen indem z.B. alle 2en auf dasselbe Objekt verweisen. Würdest du nun eine 2 dereferenzieren und überschreiben, würdest du nicht nur diese 2 ändern sondern alle 2en im gesamten Programm.
NoPy hat geschrieben:Und bei den Problemen, die ich momentan angehe (geographischer Natur) werden jede Menge Objekte temporär im Speicher herumkullern. Ich hoffe, der GC kommt zurecht. In ein paar Tagen weiß ich mehr ...
Vermeide Zyklen (Objekte die aufeinander verweisen), die sind u.U. problematisch, ansonsten sollte der GC kein Problem machen.
NoPy hat geschrieben:Sehe ich etwas anders, gerade Zuweisung per Referenz oder Wert halte ich nach wie vor für sehr sinnvoll (was ich über Garbage- Collektoren nicht zu behaupten wage. Wenn man sauber programmiert, ist das Aufräumen auch kein Problem und sollte auch von dem getan werden, der den Speicher angefordert hat)
Referenzen die nicht konstant (C++: const &) sind, würde ich nicht als „sauber programmiert“ bezeichnen. Und selbst wenn: Lieber ein GC als eine hängende Referenz/pointer…
Zuletzt geändert von Darii am Sonntag 29. Dezember 2013, 10:45, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Dass es sich um read-only Attribute handelt, sagt ja noch nicht, dass das Dictionary auch read-only Einträge hat. Aber hier bei »locals()« steht, dass man die Einträge nicht ändern soll; ist ja nett von den Pythonentwicklern, dass man sie inzwischen nicht einmal mehr ändern kann.

Und ich weiß ich darf so etwas nicht in die Öffentlichkeit entlassen, weil es sonst irgendjemand auch anwendet: Referenzen auf Attribute, Arrayeinträge und alles was keine lokale Variable ist:

Code: Alles auswählen

import inspect

class Reference(object):
    def __init__(self, name):
        self.name = name
        self.frame = inspect.stack()[1][0]

    def get(self):
        return eval(self.name, {}, self.frame.f_locals)
   
    def set(self, value):
        exec('%s = _v_a_l_u_e_'%self.name, {'_v_a_l_u_e_':value}, self.frame.f_locals)
 
def test(ptr):
    print ptr.get()
    ptr.set(99)
 
def main():
    a = [1,2,4]
    test(Reference('a[1]'))
    print a
   
if __name__ == '__main__':
    main()
Antworten