deepcopy erzeugt deep confusion

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
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Der folgende Code läuft bei mir glatt durch und liefert korrekte Ergebnisse:

Code: Alles auswählen

class Aufgabe(Aufgabe_schablone):
  # ... ... ... komplexer Code mit vielen Objekten

import mein_algorithmus as ma

die_aufgabe = Beispiel()

ma.loese_Aufgabe(die_aufgabe)
Warum liefert dann folgender Code nichts als Garbage ???

Code: Alles auswählen

class Aufgabe(Aufgabe_schablone):
  # ... ... ... komplexer Code mit vielen Objekten

import mein_algorithmus as ma

from copy import deepcopy
die_aufgabe_proto = Beispiel()
die_aufgabe = deepcopy(die_aufgabe_proto)

ma.loese_Aufgabe(die_aufgabe)
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Garbage in, Garbage out?

Im Ernst, mit so wenigen Informationen, wie du sie lieferst, kann man keine Fehleranalyse betreiben. deepcopy() funktioniert so wie es soll, also gibt es drei Möglichkeiten:

* Dein Beispiel-Objekt ist prinzipiell nicht kopierbar.
* Du hast das Beispiel-Objekt so geschrieben, dass es nicht kopierbar ist.
* Du hast deepcopy() falsch verstanden.
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Vielen Dank, das ist doch schon einmal etwas. Ich dachte bisher naiverweise, dass alle Objekte kopierbar seien, doch offenbar sind einige meiner Objekte nicht kopierbar.

Mein Programm läuft im Arbeitsspeicher, es werden keine Dateien erstellt oder modifiziert (außer natürlich den .pyc-Dateien). Ich benutze array-Objekte aus Numeric, und wahrscheinlich sind diese die Schuldigen; ich weiß nicht, ob sie kopierbar sind oder nicht, und teste das jetzt erst einmal.

Verbleibende Frage: Sollte eine Funktion deepcopy (wenn sie anständig programmiert ist) nicht eine Fehlermeldung ausgeben, wenn sie auf nicht kopierbare Objekte stößt? Bei mir sind die Ergebnisse total falsch, aber es läuft alles glatt durch, ohne Fehlermeldung irgendeiner Art.
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Goswin hat geschrieben:Mein Programm läuft im Arbeitsspeicher, es werden keine Dateien erstellt oder modifiziert (außer natürlich den .pyc-Dateien). Ich benutze array-Objekte aus Numeric, und wahrscheinlich sind diese die Schuldigen; ich weiß nicht, ob sie kopierbar sind oder nicht, und teste das jetzt erst einmal.
Sie sind kopierbar. Ich muß in einem Programm Numeric und numpy nutzen und kann für beide bestätigen, daß deepcopy keinerlei Probleme bereitet (außer einer geringen Geschwindigkeit bei großen Objekten ;-) ). Außerdem habe ich auch mal numarray getestet: Ebenfalls kein Problem mit deepcopy. (Das alles für rel. aktuelle Versionen von Python und numpy. Aber ich habe auch früher keine Probleme festgestellt. Ich weiß aber nicht mehr die Versionsnummern, wenn Du das brauchst, recherchiere ich noch mal in meinem Code.)
BlackJack

@Goswin: Es wäre vielleicht auch hilfreich den Quelltext, der hinter diesem Kommentar steckt:

Code: Alles auswählen

  # ... ... ... komplexer Code mit vielen Objekten
soweit zu vereinfachen, dass man ihn mal anschauen und überblicken kann, aber trotzdem noch das Problem auftritt.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

So, jetzt habe ich Python 2.5 statt Python 2.3 installiert und verwende numpy 1.1 anstelle von Numeric. Ebenfalls habe ich den Code soweit vereinfachen können, dass er nun übersichtlich ist. Nichts Neues im Westen! Wie schon vorher sind die Ergebnisse verschieden, wenn wir nicht direkt mit dem Objekt, sondern mit einer deepcopy arbeiten:

Code: Alles auswählen

#!/usr/bin/python
import numpy as n #Arrays
from copy import deepcopy


class Datenblatt(object):

  def __init__(self,zeilen,spalten):
    self.hintergrund = n.ones((zeilen+2,spalten))
    #
    self.matrix = self.hintergrund[:zeilen,:]
    self.zusatz = self.hintergrund[zeilen:zeilen+2,:]

  def zeige(self):
    print "\n\nStand der Gesamtdaten:"
    print '\n', self.hintergrund
    print '\n\n', self.matrix, '\n\n', self.zusatz

  def bearbeite(self):
    self.hintergrund *= -1


muster = Datenblatt(zeilen=4,spalten=8)
#
blatt = muster #FUNKTIONIERT OK
blatt = deepcopy(muster) #FUNKTIONIERT FALSCH, auskommentieren zum Vergleich

blatt.zeige()
#
blatt.matrix[1,1] = -2
blatt.zusatz *= 2
blatt.matrix += 5
blatt.zeige()
#
blatt.bearbeite()
blatt.zeige()
Ich hätte gern gewusst, ob dieses abweichende Verhalten nun ein Bug oder ein Feature ist ??? (Die erwünschten Ergebnisse sind die, die erzeugt werden, wenn wir oben direkt mit dem Objekt arbeiten)
BlackJack

Ich tippe mal auf die Slices:

Code: Alles auswählen

In [54]: a = numpy.ones(5)

In [55]: b = a[:]

In [56]: b[0] = 42

In [57]: a
Out[57]: array([ 42.,   1.,   1.,   1.,   1.])

In [58]: b
Out[58]: array([ 42.,   1.,   1.,   1.,   1.])

In [59]: import copy

In [60]: a = numpy.ones(5)

In [61]: b = a[:]

In [62]: a, b = copy.deepcopy((a, b))

In [63]: b[0] = 42

In [64]: a
Out[64]: array([ 1.,  1.,  1.,  1.,  1.])

In [65]: b
Out[65]: array([ 42.,   1.,   1.,   1.,   1.])
Das ist halt die Semantik von `deepcopy()`: von allem *Kopien* anlegen. Nicht weniger, aber eben auch nicht mehr. Insbesondere nicht nachschauen, ob irgendwo im Programm Slices auf eine dieser Kopien "umgebogen" werden müssen um das von Dir gewünschte Verhalten zu bekommen.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@blackjack:
OK, gehen wir dann davon aus, dass eine slice-Operation ein neues Objekt erzeugt. Aber warum wird dann bei der Version ohne deepcopy kein neues Objekt erzeugt? Warum setzt die slice-Operation einfach nur einen Pointer auf das alte Objekt, im Gegensatz zu allem, was Python-Handbuecher behaupten?

Man kann es drehen und wenden wie man will: eine anständige deepcopy sollte genau das gleiche Verhalten wie das Original aufweisen. Wie dieses Verhalten zu sein hat, ist ein ganz anderes Thema.
BlackJack

Welche Python-Handbücher behaupten, dass *Numpy-Arrays* bei Slices Kopien liefern? Das Verhalten vom Indexzugriff, mit oder ohne Slices, liegt voll im Verantwortungsbereich desjenigen, der die entsprechende `__getitem__()`-Methode implementiert hat.

Ansonsten kannst Du das Verhalten der Arrays mal auf der entsprechenden Numpy-Mailingliste zur Sprache bringen. Entweder haben die dort eine Erklärung für das Verhalten, oder einen Grund einen Fehler zu beheben. :-)
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

Goswin hat geschrieben: Man kann es drehen und wenden wie man will: eine anständige deepcopy sollte genau das gleiche Verhalten wie das Original aufweisen.
Nein, sollte sie nicht. Triviales Beispiel:

Code: Alles auswählen

>>> from copy import deepcopy
>>> class C(object):
	def show(self):
		print id(self)

			
>>> c = C()
>>> c.show()
33135376
>>> deepcopy(c).show()
33135024
Für wahrscheinlicher halte ich aber, dass es Referenzen auf das Objekt (bzw. darin enthaltene Objekte) gibt. Das kann dann so aussehen:

Code: Alles auswählen

>>> from copy import deepcopy
>>> class A(object):
	def __init__(self, v):
		self.v = v
	def do_something(self):
		d[0]=1

		
>>> d = {}
>>> a = A(d)
>>> b = deepcopy(a)
>>> a.v, b.v
({}, {})
>>> b.do_something()
>>> a.v, b.v
({0: 1}, {})
>>> 
bords
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

bords0 hat geschrieben:
Goswin hat geschrieben: Man kann es drehen und wenden wie man will: eine anständige deepcopy sollte genau das gleiche Verhalten wie das Original aufweisen.
Nein, sollte sie nicht. Triviales Beispiel:

Code: Alles auswählen

>>> from copy import deepcopy
>>> class C(object):
	def show(self):
		print id(self)

			
>>> c = C()
>>> c.show()
33135376
>>> deepcopy(c).show()
33135024
Und was soll dieses Beispiel zeigen? Es spricht doch eindeutig für Goswin, denn das Verhalten ist dasselbe: es wird die ID des Objekts ausgegeben. Dass die Identität nicht dieselbe ist und nicht sein kann, das besagt schon der Name "Kopie" - bei Kopien von Schriftstücken wird auch ein neues Blatt Papier verwendet. Sonst hieße es "Original". ;-)
bords0 hat geschrieben:Für wahrscheinlicher halte ich aber, dass es Referenzen auf das Objekt (bzw. darin enthaltene Objekte) gibt. Das kann dann so aussehen:

Code: Alles auswählen

>>> from copy import deepcopy
>>> class A(object):
	def __init__(self, v):
		self.v = v
	def do_something(self):
		d[0]=1

		
>>> d = {}
>>> a = A(d)
>>> b = deepcopy(a)
>>> a.v, b.v
({}, {})
>>> b.do_something()
>>> a.v, b.v
({0: 1}, {})
>>> 
Auch das habe ich nicht verstanden. Für mich sieht es so aus, als ob Du b veränderst und a unverändert lässt. Da deepcopy auch die referenzierten Objekte kopiert, ist es doch richtig, dass sich nur eines ändert. Was willst Du mit dem Beispiel sagen?

Wo sind die besagten Referenzen auf das Objekt bzw. darin enthaltene Objekte? Ich sehe nur in den Instanzen von A enthaltene Referenzen auf d...

Und schließlich, warum speicherst Du eigentlich v und referenzierst dann d??
Ich hoffe wirklich, dass das nur ein schlechtes Beispiel und nicht Dein Programmierstil ist. ;-)

Gruß,
der Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

Michael Schneider hat geschrieben:
bords0 hat geschrieben:

Code: Alles auswählen

>>> from copy import deepcopy
>>> class C(object):
	def show(self):
		print id(self)

			
>>> c = C()
>>> c.show()
33135376
>>> deepcopy(c).show()
33135024
Und was soll dieses Beispiel zeigen? Es spricht doch eindeutig für Goswin, denn das Verhalten ist dasselbe: es wird die ID des Objekts ausgegeben. Dass die Identität nicht dieselbe ist und nicht sein kann, das besagt schon der Name "Kopie" - bei Kopien von Schriftstücken wird auch ein neues Blatt Papier verwendet. Sonst hieße es "Original". ;-)
Goswin scheint zu glauben, dass man statt dem Original auch eine (tiefe) Kopie verwenden kann, und das gleiche Ergebnis erhält. Das ist aber nicht so. Das Beispiel zeigt dies nicht nur, sondern man kann ob der Simplizität auch sofort erkennen, dass das so sein muss (wie du nochmals ausführlich erläutert hast).

Wenn das nicht mit Goswins Erwartungen zusammenpasst, liegt das also nicht daran, dass deepcopy etwas falsch macht. Letzteres scheint Goswin aber zu denken.
Michael Schneider hat geschrieben:
bords0 hat geschrieben:

Code: Alles auswählen

>>> from copy import deepcopy
>>> class A(object):
	def __init__(self, v):
		self.v = v
	def do_something(self):
		d[0]=1

		
>>> d = {}
>>> a = A(d)
>>> b = deepcopy(a)
>>> a.v, b.v
({}, {})
>>> b.do_something()
>>> a.v, b.v
({0: 1}, {})
>>> 
Auch das habe ich nicht verstanden. Für mich sieht es so aus, als ob Du b veränderst und a unverändert lässt. Da deepcopy auch die referenzierten Objekte kopiert, ist es doch richtig, dass sich nur eines ändert. Was willst Du mit dem Beispiel sagen?
Naja, wenn a.do_something a.v verändert, könnte man denken, dass bei der tiefen Kopie b ein aufruf von d.do_something b.v verändert und nicht a.v.
Michael Schneider hat geschrieben: Wo sind die besagten Referenzen auf das Objekt bzw. darin enthaltene Objekte? Ich sehe nur in den Instanzen von A enthaltene Referenzen auf d...
Und d ist was? Ein Name für a.v.
Michael Schneider hat geschrieben: Und schließlich, warum speicherst Du eigentlich v und referenzierst dann d??
Ich hoffe wirklich, dass das nur ein schlechtes Beispiel und nicht Dein Programmierstil ist. ;-)
Weil das die einfachste Möglichkeit ist, eine Referenz auf ein enthaltenes Objekt zu haben, die bei einem harmlos aussehenden Methodenaufruf verwendet wird, aber nicht einer tiefen Kopie mitkopiert wird.

bords
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Goswin hat geschrieben:Man kann es drehen und wenden wie man will: eine anständige deepcopy sollte genau das gleiche Verhalten wie das Original aufweisen. Wie dieses Verhalten zu sein hat, ist ein ganz anderes Thema.
Das Problem ist: Das ist so allgemein nicht möglich(deswegen gibt es __copy__, s.u.).

Wie soll sich denn ein numpy.Array verhalten, wenn es kopiert wird?
  1. sollen einfach die Werte kopiert werden (aktuell anscheinend der Fall)
  2. soll eine ggf beim slicen erzeugte Referenz kopiert werden
  3. soll die ggf beim slicen erzeugte Referenz bei der N-ten Kopie auf die N-te Kopie (die entweder schon erzeugt wurde oder erzeugt wird) der Ursprungsmatrix umgebogen werden (von dir favorisiert)
Alle drei Möglichkeiten sind Kopien. Alle drei Möglichkeiten würden in einem bestimmten Problem funktionieren. Alle drei Möglichkeiten führen woanders zu Problemen. Und speziell die 3. scheint mir für einen allgemeinen Fall schwer implementierbar.

Lösung des Problems: du implementierst dir __copy__/__deepcopy__ für deine Klasse selber
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@Darii, eine allgemeine Nebenbemerkung zu deinem Vorschlag:
In meinem Handbuch (Alex Martelli, Python in a Nutshell, 2003) heißt es auf Seite 142: <copies of arrays are not supported>. Traurig, aber wahr.
In welchem Modul befindet sich denn __copy__? Oder bräuchte ich __deepcopy__? Wie wird es verwendet und von wo aus wird es aufgerufen?

@bords0:
Deine Argumente kann ich nicht nachvollziehen:
(1)
Wenn dein d nicht kopiert werden würde, dann wäre die sogenannte deepcopy keine solche. Das Handbuch von Martelli erkläert ausdrücklich, dass bei einer deepcopy sämtliche Objekte, auf die referenziert wird, mitkopiert werden.
(2)
In deinem Beispiel wird das d natürlich mitkopiert; nur so kannst du b verändern, ohne dass sich a gleich mitverändert. Für normale Pythonobjekte scheint deepcopy durchaus zu funktionieren; nur bei numeric- oder numpy-Objekten ist das anscheinent nicht der Fall.
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Das Handbuch ist von 2003...
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Folgendes scheint zu funktionieren:

Code: Alles auswählen

#!/usr/bin/python
import numpy as n #Arrays
from copy import deepcopy


class Datenblatt(object):

  def __init__(self,zeilen,spalten):
    self.hintergrund = n.ones((zeilen+2,spalten))
    #
    self.zeilen = zeilen
    self.matrix = self.hintergrund[:zeilen,:]
    self.zusatz = self.hintergrund[zeilen:zeilen+2,:]

  def __deepcopy__(self,memo):
    _kopie = deepcopy(super(Datenblatt,self),memo)
    _kopie.matrix = _kopie.hintergrund[:_kopie.zeilen,:]
    _kopie.zusatz = _kopie.hintergrund[_kopie.zeilen:_kopie.zeilen+2,:]
    return _kopie

  def bearbeite(self):
    self.hintergrund *= -1

  def zeige(self):
    print "\n\nStand der Gesamtdaten:"
    print '\n', self.hintergrund
    print '\n\n', self.matrix, '\n\n', self.zusatz



muster = Datenblatt(zeilen=4,spalten=8)
#
#blatt = muster #funktioniert
blatt = deepcopy(muster) #funktioniert jetzt auch, aber nur wegen __deepcopy__

blatt.zeige()
#
blatt.matrix[1,1] = -2
blatt.zusatz *= 2
blatt.matrix += 5
blatt.zeige()
#
blatt.bearbeite()
blatt.zeige()
Natürlich ist das IMHO keineswegs zufriedenstellend, da wir bei jeder Klasse raten/testen/nachforschen müssen, ob deepcopy nun funktioniert oder nicht. Aber vielleicht können wir voraussetzen, dass derlei Undinge nur bei Numeric oder NumPy geschehen.

Bei NumPy selber konnte ich nichts melden, das ist eine 99% geschlossene Gesellschaft, da kommen nur Hacker oder Insider durch (bitte als Lob für dieses Python-Forum auslegen).
BlackJack

Ich hatte von der NumPy Mailingliste bis jetzt nur positives gehört. Geschlossene Gesellschaft war da nicht bei!?
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

Goswin hat geschrieben:In deinem Beispiel wird das d natürlich mitkopiert; nur so kannst du b verändern, ohne dass sich a gleich mitverändert.
So ist es aber gar nicht. a wird verändert, b nicht.

Und meinst du wirklich, in A.do_something wird ein anderes globales d verwendet, je nachdem, ob man a.do_something oder b.do_something ausührt?
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Goswin hat geschrieben:Natürlich ist das IMHO keineswegs zufriedenstellend, da wir bei jeder Klasse raten/testen/nachforschen müssen, ob deepcopy nun funktioniert oder nicht. Aber vielleicht können wir voraussetzen, dass derlei Undinge nur bei Numeric oder NumPy geschehen.
Ich habs doch eben schon versucht zu erklären: Das liegt nicht an numpy. Das liegt an deinem Problem. Das ist eben nicht-trivial. Jede der von mir aufgezählen drei Kopien hat seine Daseinsberechtigung und das Numpy-Team musste sich nunmal für eine entscheiden(und wenns sie es nicht getan haben ist gegen das derzeitige Verhalten trotzdem nix einzuwenden). Genau aus diesem Grund gibt es die __copy__ Methode. Damit du eine speziell auf dein Problem zugeschnittene Kopien erstellen kannst.

Das Problem kann dir bei jeder x-beliebigen Bibliothek begegnen.

Das einzige, was man vielleicht machen könnte, ist eine kleine Warnung in die Python-Anleitung zu schreiben, dass deepcopy eben nicht "einfach so" funktioniert.
Antworten