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.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Hallo Wissende,

kann man irgendwie explizit by value oder by reference zuweisen?
Bzw. wie löst man so etwas (Beispiel zur Illustration, kein direkt praktisches Szenario):

Code: Alles auswählen

class ContainerDingeMischen(object):
  _VieleDinge=[]

  def __init__(self):
    self._VieleDinge=[]
  
  def Dazu(self,Ding)
    self._VieleDinge.append(Dings)

  def Mische(self)
    Shuffle()

  def GibDingZurBearbeitung(self,index):
    if index>-1 and index<len(self._VieleDinge):
      return self._VieleDinge[index] #An dieser Stelle möchte ich explizit by reference übergeben!

class Karte(object):
  ...
  aufgedeckt = False

class Domino(object):
  ...
  orientierung = waagerecht

KartenSpiel = ContainerDingeMischen()
for MeineKarte in KartenBlatt:
  KartenSpiel.Dazu(MeineKarte)

KartenSpiel.Mische()

for i in range(5):
  Kartenspiel.GibDingZurBearbeitung(i).aufgedeckt=True

...

DominoSpiel = ContainerDingeMischen()
for MeinStein in DominoSteine:
  DominoSpiel.Dazu(MeinStein)

DominoSpiel.Mische()

for i in range(5):
  DominoSpiel.GibDingZurBearbeitung(i).orientierung=senkrecht
Also ich möchte nur eine "Metaklasse" zum Thema mischen schreiben, die die daten dann auch vorhält.
In dieser möchte ich verschiedenste unabhängige Dinge reinlegen, die die Metaklasse nicht zu kennen braucht.
Und diese Dinge möchte ich aber bearbeitbar von dieser Metaklasse geliefert bekommen

Was tun?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

In Python wird alles als Referenz übergeben.

Code: Alles auswählen

class Spam(object):
    ham = 42
bedeutet übrigens nicht, dass Objekte vom Typ ``Spam`` ein Attribut ham besitzen, sondern dass das Objekt Spam ein Attribut ham hat. Alle Instanzen der Spam-Klasse teilen sich das Attribut. Du möchtest

Code: Alles auswählen

class Spam(object):
    def __init__(self):
        self.ham = 42
Edit: Und wirf doch bitte einen Blick auf PEP 8. Deine Namensvergabe entspricht nicht den üblichen Konventionen. So implizieren Namen mit einem Großbuchstaben am Anfang, dass es sich um eine Klasse handelt. Alle anderen Namen werden üblicherweise durch_unterstriche_getrennt.
Das Leben ist wie ein Tennisball.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

EyDu hat geschrieben:In Python wird alles als Referenz übergeben.
Allerdings sind einige Datentypen (wie z.B. Strings) unveränderlich.

Edit: EyDus Bitte um Beachtung des Style Guide for Python Code schließe ich mich an. Für erfahrene Python-Entwickler sieht dein Code beinahe bösartig aus. Fast wie Java ... :evil:
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Ist es wirklich so? Sobald ich es verändere, wird es doch kopiert, oder?
Das hat mich mal Stunden der Fehlersuche gekostet, weil ich über die Referenz ja nichts verändern konnte, sondern bei jeder Änderung eine Kopie erzeugt wurde, die dann den neuen Wert barg.

Ich habe das bei dem oben stehenden Szenario nicht ausprobiert, aber es sollte eigentlich inhaltlich meinem Problem von damals entsprechen (ich brauchte eine universelle Klasse, die eine bestimmte Sorte Datenbanktabellen kapselte)
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

NoPy hat geschrieben:Ist es wirklich so? Sobald ich es verändere, wird es doch kopiert, oder?
Schau dir mal das Beispiel mit Strings und Listen an.

Code: Alles auswählen

>>> def work(value):
	value = "bar"
>>> x = "foo"
>>> work(x)
>>> print(x)
foo

>>> def work(value):
	value.append("bar")
>>> x = ["foo"]
>>> work(x)
>>> print(x)
['foo', 'bar']
BlackJack

@NoPy: Vergiss „by reference” und „by value”, denn eigentlich wird „by object sharing” übergeben. Das Problem bei value vs. reference ist, dass die Leute denken sie wüssten was das bedeutet, verschiedene Programmierer aus verschiedenen Sprachen aber etwas anderes unter dieser Unterscheidung verstehen. Und dann kann man prima aneinander vorbei reden.

Wenn Du ein Objekt übergibst, dann hat die Funktion oder Methode genau das selbe Objekt wie der Aufrufer zur Verfügung. Niemals eine Kopie, immer das selbe Objekt welches übergeben wurde. Die teilen sich das selbe Objekt, daher „by object sharing”.
BlackJack

@NoPy: Ich weiss es ist nur ein Beispiel aber der Container macht IMHO keinen Sinn. So wirklich unterscheiden tut ihn von einem beliebigen anderen Sequenz-Typ ja nur die `Mische()`-Methode. Es gibt aber die `random.shuffle()`-Funktion die mit jeder Sequenz funktioniert bei der man die Länge abfragen kann und Elemente über einen Index holen und zuweisen kann.

Mit dem Begriff „Metaklasse” muss man auch vorsichtig sein, denn der hat in Python eine Bedeutung. *So* tief muss man hier aber nicht in die Trickkiste greifen, dass man tatsächlich Metaklassen bräuchte. :-)

Und zeig demnächst mal ausführbare Beispiele die das Problem auch tatsächlich aufzeigen. Denn das was da im ersten Beitrag steht sollte, sofern man es entsprechend ergänzt das es läuft, das von Dir beschriebene Problem gar nicht haben. Du würdest damit bei 5 Objekten von `KartenBlatt` und `DominoSteine` ein Attribut an einen neuen Wert binden. Also am Ende könnte man das hier schreiben und jeweils eine 5 ausgegeben bekommen:

Code: Alles auswählen

print sum(int(karte.aufgedeckt) for karte in KartenBlatt)
print sum(int(stein.orientierung == senkrecht) for stein in DominoSteine)
Das wäre doch genau das was Du haben möchtest, oder?
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Nein, eigentlich nicht. Ich möchte eine Verwaltungsklasse bauen, die ich nachnutzen kann (in angegebenem Fall diese Containerklasse).
Zu diesem Nachnutzen gehört aber eben auch, dass ich die Objekte, die sie beherbergt, IN der Klasse bearbeiten kann und nicht eine Kopie bekomme, sobald ich etwas ändere, die Objekte in der Klasse aber unverändert bleiben.

Wie gesagt, es ging damals um eine spezielle Klasse für Datenbanktabellen. Ich konnte dieser dann brav alles geben, sie hat es auch gefressen. Aber sobald ich mir dann ein Objekt ausliefern ließ und es "bearbeitete", erzeugte python eine Kopie, veränderte diese und das Objekt in der Tabellenklasse war wie vorher.

Mag sein, dass ich etwas falsch gemacht habe, aber ich habe es dann erst mal aufgeben müssen und weiter herumgestümpert, was äußerst unbefriedigend ist.

also kurz (PseudoCode):

Code: Alles auswählen

class MyAbstract(object):
  def NimmDings(self,Ding):
    #nimm
  def GibDings(self):
    return DingReferenz

a=MyAbstract()
a.NimmDings(IrgendeinObjekt)
ReferenzAufDings=a.GibDings()
ReferenzAufDings.Wert=12

NeueReferenz=a.GibDings()
print NeueReferenz.Wert
# und jetzt sollte 12 stehen
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@NoPy: ganz schön viel Mühe, Dein Beispiel standardkonform umzuschreibe, aber das Verhalten ist genau das, welches Du erwartest:

Code: Alles auswählen

class MyAbstract(object):
    def __init__(self):
        self.dings = None
        
    def nimm_dings(self, dings):
        self.dings = dings
    
    def gib_dings(self):
        return self.dings
    
class IrgendeinObjekt(object):
    def __init__(self):
        self.wert = 0
        
a = MyAbstract()
a.nimm_dings(IrgendeinObjekt())
referenz_auf_dings = a.gib_dings()
referenz_auf_dings.wert = 12

neue_referenz = a.gib_dings()
print neue_referenz.wert # hier wird 12 ausgegeben
BlackJack

@NoPy: Wenn Du da nicht 5 als Ausgabe sehen willst, dann verstehe ich nicht was Du eigentlich willst. Insbesondere weil Du sagst Du willst *keine* Kopie. Wenn dort *nicht* 5 heraus kommen soll, dann *müssten* aber die Objekte kopiert werden. Was Python wie gesagt nicht macht. Wenn Du das willst musst Du das explizit selber machen. Und wenn Du das *wirklich* willst, dann ist das für Python (und eigentlich auch in anderen objektorientierten Sprachen) ungewöhnlich was Du haben willst.

Speziell den zweiten Satz im ersten Absatz verstehe ich nicht. Du willst das die Objekte sowohl bearbeitet werden und keine Kopie herausgegeben wird (was in Python auch genau so ist), aber gleichzeitig die nicht kopierten, bearbeiteten Objekte unverändert bleiben‽ Das widerspricht sich.

Nochmal: Python erzeugt niemals von sich aus Kopien von Objekten! Das muss man immer als Programmierer explizit machen wenn man das will. Entweder in dem man selber spezifischen Quelltext dafür schreibt, oder die Funktion(en) aus dem `copy`-Modul verwendet.

War die DB-Tabellen-Klasse denn von Dir geschrieben? Kann es vielleicht sein, dass *die* expliziten Code enthielt um eine Kopie zu erstellen? Das hat dann aber nichts mit Python an sich zu tun, sondern was jemand in einer Methode geschrieben hat. Bei einer DB-Tabellen-Klasse wäre ich sogar nicht überrascht wenn die gar keine Objekte enthält sondern immer frische Objekte aus aus der DB abgefragten Werte erstellt und man eine Änderung nur herbeiführen kann wenn man ein verändertes Objekt auch explizit wieder an das Tabellen-Objekt übergibt, damit die Daten in die DB geschrieben werden. Da muss man die Dokumentation zur Klasse lesen. :-)

Du hast da jetzt schon wieder keinen lauffähigen Code gezeigt. Solange nicht in mindestens einer der beiden Methoden Code steht der das Dings explizit kopiert, entspricht das Verhalten dem im Kommentar gewünschten. Und jetzt verstehe ich immer noch nicht warum Du bei den ``print``-Anweisungen aus dem letzten Beispiel *nicht* 5 als Ausgabe erwartet hast.

Ich denke Du fragst hier nach einer Lösung eines Problems was es so überhaupt gar nicht gibt.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

@Sirius3

Tatsächlich! Es funktioniert. Dann hab ich damals also offensichtlich irgend etwas falsch gemacht.

Ich hab das jetzt auch mal mit Listen und Strings ausprobiert, mit Listen klappt es, mit Strings anscheinend nicht.
Möglicherweise hatte ich damals die Klasse mit Strings getestet und bin damit auf die Nase gefallen.
Witzigerweise klappt es nicht nur mit str nicht, sondern auch nicht mit von str abgeleiten Klassen (also class MyString(str) )

Daher: Viel Dank für Deine Mühe. Kaum macht man es richtig, schon gehts. Wenn ich also alles in Klassen packe, die nicht von String erben, wird es funktionieren.

erledigt :D
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@NoPy: das mit den Strings verstehe ich nicht. Strings sind wie Zahlen unveränderlich. Wäre auch blöd, wenn man der Zahl 4 den Wert 3 zuweisen könnte. Die ganze Mathematik wäre auf den Kopf gestellt. Veränderbare Strings würden wahrscheinlich genauso viel Verwirrung stiften. Abgeleitete Klassen von String können, wie jede andere „normale“ Klasse, Attribute haben, die sich dann genauso verhalten.
BlackJack

@NoPy: Es gibt keinen Unterschied zwischen Zeichenketten und Listen bei der Übergabe. Du stolperst hier wohl eher darüber das man Zeichenketten nicht verändern kann, Listen aber schon.

Und beim vererben sollte man weder von Zeichenketten noch von Listen erben. Sinnvolle Anwendungen davon sind eher selten. Erbe bitte nicht davon nur weil Du denkst das muss irgendwie wegen einer Typhierarchie so sein, muss es in Python ja gerade nicht.

Um sinnvoll von einer Liste erben zu können muss man a) nicht allzuviel vom Standardverhalten überschreiben müssen, und b) müssen auf dem abgeleiteten Typ alle Operationen funktionieren die eine Liste hat. Wenn a) nicht zutrifft, braucht man die Liste als Basisobjekt nicht wirklich, und wenn man bei b) nicht aufpasst endet man mit Werten die Methoden haben die man nicht aufrufen darf weil der innere Zustand sonst kaputt gehen kann. Da dann hinterher zu patchen und dafür zu sorgen das Methoden funktionieren die man aber eigentlich gar nicht braucht, ist auch nicht sinnvoll.

Bei unveränderlichen Typen wie `str` ist das fast noch schlimmer, zumindest wenn man will das bei den Operationen die wieder ein Objekt von ihrem Typ liefern.

Edit: Vererbung ist in Python-Programmen eigentlich relativ selten, mal von `object` in Python 2 abgesehen. Das nutzt man nur wenn man es tatsächlich braucht, also wenn tatsächlich gemeinsames Verhalten von mindestens zwei Typen in einen Basistyp heraus gezogen werden kann. Und dann mache ich persönlich das auch immer in dieser Reihenfolge: Bevor ich nicht konkret zwei Typen habe, schreibe ich auch keine Basisklassen weil die *vielleicht* mal nützlich sein könnten. Sehr oft sind sie das nämlich nicht und dann hat man einfach nur das Programm unnötig kompliziert gemacht.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Wie gesagt: Der Hintergrund ist das Schaffen einer abstrakten Klasse zum Verwalten von Dingen. Und meine Klasse könnte also damals funktioniert haben, da ich sie aber mit Strings testete, konnte ich das nicht feststellen.
Oder anders: Es ist anscheinend nicht möglich, Strings oder Zahlen per Referenz zu übergeben, man müsste also entsprechende Klassen extra schreiben. Außerdem ist es demnach unmöglich, abstrakte Klassen zu schreiben, die für Zahlen, Zeichenketten, Objekte und Listen gemeinsam funktionieren.
Ist nicht schön, aber jetzt, da ich es weiß, ist es handhabbar.

Code: Alles auswählen

class MyAbstract(object):

    def __init__(self):
        self.dings = None

    def nimm_dings(self, dings):
        self.dings = dings

    def gib_dings(self):
        return self.dings

class IrgendeinObjekt(object):
    def __init__(self):
        self.wert = 0

def TestListe():        
    Liste = [1,2,3] 
    
    TestAbstract=MyAbstract()
    TestAbstract.nimm_dings(Liste)
    Referenz=TestAbstract.gib_dings()
    
    Referenz.append(99)
    
    print TestAbstract.gib_dings()

def TestInteger():
    Zahl = 3
    
    TestAbstract=MyAbstract()
    TestAbstract.nimm_dings(Zahl)
    Referenz=TestAbstract.gib_dings()
    
    Referenz+=3
    
    print TestAbstract.gib_dings()
    
def TestObjekt():
    Objekt = IrgendeinObjekt()
    
    TestAbstract=MyAbstract()
    TestAbstract.nimm_dings(Objekt)
    Referenz=TestAbstract.gib_dings()
    
    Referenz.wert=12
    
    print TestAbstract.gib_dings().wert

def TestStr():
    Text='Hallo '
    
    TestAbstract=MyAbstract()
    TestAbstract.nimm_dings(Text)
    Referenz=TestAbstract.gib_dings()
    
    Referenz+='Welt'
    
    print TestAbstract.gib_dings()

print "klappt erwartungsgemaess"
TestListe()
TestObjekt()

print "kann offensichtlich nicht per Referenz übergeben werden"
TestInteger()
TestStr()
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei unveränderlichen Typen ist »+=« auch nur eine Abkürzung für »a = a + ...«. Das Übergeben eines Pointers auf den Speicherplatz, an dem der Zeiger »self.dings« liegt, ist auch in streng typisierten Sprachen wie Java nicht möglich. Und (void**)-Spielereien in C machen ein Programm auch nicht gerade übersichtlicher.
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: 17737
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.
Antworten