Emulieren des Pass-by-Value-Verhaltens in Python

70

Ich möchte das Pass-by-Value-Verhalten in Python emulieren. Mit anderen Worten, ich möchte unbedingt sicherstellen, dass die von mir geschriebene Funktion die vom Benutzer bereitgestellten Daten nicht ändert.

Eine Möglichkeit ist die Verwendung einer tiefen Kopie:

from copy import deepcopy
def f(data):
    data = deepcopy(data)
    #do stuff

Gibt es einen effizienteren oder pythonischeren Weg, um dieses Ziel zu erreichen, indem so wenig Annahmen wie möglich über das übergebene Objekt getroffen werden (z. B. die .clone () -Methode)?

Bearbeiten

Mir ist bewusst, dass technisch alles in Python als Wert übergeben wird. Ich war daran interessiert, das Verhalten zu emulieren , dh sicherzustellen, dass ich nicht mit den Daten herumspiele, die an die Funktion übergeben wurden. Ich denke, der allgemeinste Weg besteht darin, das betreffende Objekt entweder mit einem eigenen Klonmechanismus oder mit Deepcopy zu klonen.

Boris Gorelik
quelle
1
Du machst es schon richtig.
ismail
3
Nur eine Terminologie-Anmerkung: Python verwendet bereits Pass-by-Value. Viele Werte sind zufällig Referenzen, daher übergeben Sie Referenzen nach Wert. Wenn Python die Referenzübergabe verwendet, ändert def f (x): x = 5 tatsächlich den Wert des Ausdrucks, den der Aufrufer an den Anrufer übergeben hat, dies ist jedoch nicht der Fall.
Laurence Gonsalves
1
@ L.Gonsalves Weder "Pass-by-Value" noch "Pass-by-Reference" beschreiben Pythons Semantik. Siehe zum Beispiel mail.python.org/pipermail/python-list/2007-July/621112.html
dF.
3
@dF: Die Seite, auf die Sie verlinkt haben, ist ... "verwirrt". Python verwendet Pass-by-Value. Es ist auch wahr, dass alle Variablen in Python Verweise auf Werte sind, aber das ändert nichts an der Tatsache, dass die Parameterübergabe nach Wert erfolgt. Die Seite selbst erkennt an, dass Java als Wert übergeben wird und nicht-primitive Typen in Java als Referenzen gespeichert werden. Wenn wir primitive Typen aus Java entfernen würden, wäre dies kein Wert mehr? Nein. Ebenso ist die Tatsache, dass Python-Variablen immer Referenzen speichern, orthogonal zu der Tatsache, dass die Parameterübergabe nach Wert erfolgt.
Laurence Gonsalves
7
@ Laurence, das ist ein weit verbreitetes Missverständnis. Python ist eigentlich alles als Referenz übergeben. Die Variablenzuweisung ist eine Zeigerbindung. Sie ändert nicht die Datenstruktur, auf die die Variable zeigt. Dinge wie Zeichenfolgen und Ganzzahlen, die die Ausnahme zu sein scheinen, sind keine wirklichen Ausnahmen. Sie sind unveränderlich, und daher erstellen alle Operationen an ihnen, die einen anderen String / Int ergeben, tatsächlich neue, wenn sie nicht bereits vorhanden sind.
Unbekannt

Antworten:

41

Es gibt keinen pythonischen Weg, dies zu tun.

Python bietet nur sehr wenige Möglichkeiten, um beispielsweise private oder schreibgeschützte Daten durchzusetzen . Die pythonische Philosophie lautet: "Wir sind alle einverstanden mit Erwachsenen": In diesem Fall bedeutet dies, dass "die Funktion die Daten nicht ändern sollte" Teil der Spezifikation ist, aber nicht im Code erzwungen wird.


Wenn Sie eine Kopie der Daten erstellen möchten, ist Ihre Lösung die nächstgelegene. Aber es copy.deepcopyist nicht nur ineffizient, sondern hat auch Vorbehalte wie :

Da Deep Copy alles kopiert, wird möglicherweise zu viel kopiert, z. B. administrative Datenstrukturen, die auch zwischen Kopien gemeinsam genutzt werden sollten.

[...]

Dieses Modul kopiert keine Typen wie Modul, Methode, Stapelverfolgung, Stapelrahmen, Datei, Socket, Fenster, Array oder ähnliche Typen.

Daher würde ich es nur empfehlen, wenn Sie wissen, dass Sie mit integrierten Python-Typen oder Ihren eigenen Objekten arbeiten (wo Sie das Kopierverhalten durch Definieren der __copy__/ __deepcopy__special-Methoden anpassen können, müssen Sie keine eigene clone()Methode definieren ).

dF.
quelle
35

Sie können einen Dekorateur erstellen und das Klonverhalten darin einfügen.

>>> def passbyval(func):
def new(*args):
    cargs = [deepcopy(arg) for arg in args]
    return func(*cargs)
return new

>>> @passbyval
def myfunc(a):
    print a

>>> myfunc(20)
20

Dies ist nicht die robusteste Methode und behandelt keine Schlüsselwertargumente oder Klassenmethoden (fehlendes Selbstargument), aber Sie erhalten das Bild.

Beachten Sie, dass die folgenden Aussagen gleich sind:

@somedecorator
def func1(): pass
# ... same as ...
def func2(): pass
func2 = somedecorator(func2)

Sie können sogar festlegen, dass der Dekorateur eine Funktion übernimmt, die das Klonen ausführt, sodass der Benutzer des Dekorateurs die Klonstrategie festlegen kann. In diesem Fall wird der Dekorateur wahrscheinlich am besten als Klasse mit __call__überschriebenen implementiert .

Skurmedel
quelle
2
Ich denke, ein Dekorateur ist ein wirklich eleganter Ansatz, um dieses Problem zu lösen. Ich bin sicher, Sie könnten sich etwas einfallen lassen, das auch mit Keyword-Argumenten ohne große Probleme zurechtkommt.
JKP
Ja, es geht mehr oder weniger darum, der neuen Methode auch ein ** kwargs-Argument zu geben und sie zu kopieren.
Skurmedel
15

Es gibt nur einige integrierte Typen, die beispielsweise als Referenz dienen list.

Für mich wäre die pythonische Methode zum Ausführen eines Pass-by-Value für die Liste in diesem Beispiel:

list1 = [0,1,2,3,4]
list2 = list1[:]

list1[:] Erstellt eine neue Instanz der Liste1, und Sie können sie einer neuen Variablen zuweisen.

Vielleicht könnten Sie eine Funktion schreiben, die ein Argument empfangen könnte, dann ihren Typ überprüfen und gemäß diesem Ergebnis eine eingebaute Operation ausführen, die eine neue Instanz des übergebenen Arguments zurückgeben könnte.

Wie ich bereits sagte, gibt es nur wenige integrierte Typen, deren Verhalten wie Referenzen ist, Listen in diesem Beispiel.

Wie auch immer ... hoffe es hilft.

tobias_k
quelle
5
Alles in Python wird übergeben und nach Wert zugewiesen. Alle Werte in Python sind Referenzen. Es gibt keine Typen, die anders funktionieren als alle anderen
Newacct
Dein Weg hat naiv getan, was ich wollte. Vielen Dank.
Brian Peterson
4

Wenn Sie Daten an eine externe API übergeben, können Sie normalerweise die Integrität Ihrer Daten sicherstellen, indem Sie sie als unveränderliches Objekt übergeben, z. B. Ihre Daten in ein Tupel einschließen. Dies kann nicht geändert werden, wenn Sie dies durch Ihren Code verhindert haben.

Tom
quelle
3

Ich kann keine andere pythonische Option finden. Aber persönlich würde ich den OO- Weg bevorzugen .

class TheData(object):
  def clone(self):  """return the cloned"""

def f(data):
  #do stuff

def caller():
  d = TheData()
  f(d.clone())
Sake
quelle
Vielen Dank. Das Klonen impliziert jedoch eine angepasste und möglicherweise effizientere Methode zum Erstellen tiefer Kopien. Ich persönlich habe Bibliotheken gesehen, in denen die geklonten Objekte nicht so tief waren wie erwartet :). Ganz zu schweigen von Klassen ohne Klonmethode. Deepcopy ist also im Grunde genommen portabler.
Boris Gorelik
(siehe meine Klarstellung zur Frage)
Boris Gorelik
Deepcopy ist einfach eine andere Möglichkeit, die Daten zu klonen. Der eigentliche Punkt in meinem Code ist, dass "innerhalb des Anrufers" in der Tat der bessere Ort ist, um zu entscheiden, wie das Argument übergeben werden soll, nicht innerhalb der Funktion
Sake
Auch Deepcopy ist absolut nicht "effizienter". Wenn die Entscheidung zum Klonen der Daten beim Anrufer liegt, wo Sie genau wissen, welche Art von Daten Sie übergeben, können Sie die effizienteste Methode verwenden, um dies zu tun. (wie list [:], dict (dic)). Wenn Sie nicht sicher sind, welche Daten übergeben werden, führt die Beibehaltung der Entscheidung für eine effiziente Klonmethode immer zu einer besseren Leistung.
Sake
Verstehe ich Sie richtig, dass der Klon () / deepcopy () in der Datenstruktur liegen sollte. Dann verwenden Sie eine Aufrufermethode, die eine Funktion f übergeben bekommt? (In diesem Fall ffehlt der Parameter caller(f).) Müssen Sie nicht zuerst die Signatur der Funktion kennen? Können Sie es generisch multivariat machen? Oder implementieren Sie für jede Funktion eine neue Datenstruktur? Oder definieren Sie jede Funktion als Elementfunktion für jedes Datenobjekt?
Gschenk
1

Obwohl ich sicher bin, dass es keinen wirklich pythonischen Weg gibt, dies zu tun, erwarte ich, dass das pickleModul Ihnen Kopien von allem gibt, was Sie als Wert behandeln.

import pickle

def f(data):
    data = pickle.loads(pickle.dumps((data)))
    #do stuff
SingleNegationElimination
quelle
0

Weiter user695800 die Antwort, vorbei Wert für Listen möglich mit dem [:] Operator

def listCopy(l):
    l[1] = 5
    for i in l:
        print i

angerufen mit

In [12]: list1 = [1,2,3,4]

In [13]: listCopy(list1[:])
1
5
3
4

list1
Out[14]: [1, 2, 3, 4]
Auswurf
quelle
0

Viele Menschen nutzen die Standard - Bibliothek kopieren . Ich definiere lieber __copy__oder __deepcopy__in meinen Klassen. Die Methoden in copykönnen einige Probleme haben.

  1. Die flache Kopie behält Verweise auf die Objekte im Original bei, anstatt ein neues zu erstellen.
  2. Die Deepcopy wird rekursiv ausgeführt und kann manchmal zu einer Dead-Loop führen. Ohne ausreichende Aufmerksamkeit kann der Speicher explodieren.

Um diese außer Kontrolle geratenen Verhaltensweisen zu vermeiden, definieren Sie Ihre eigenen flachen / tiefen Kopiermethoden durch Überschreiben __copy__und __deepcopy__. Und Alex 'Antwort gibt ein gutes Beispiel.

Qinsheng Zhang
quelle