2012-05-22 11 views
5

Für ein Projekt arbeite ich an, ich bin eine verknüpfte Liste Datenstruktur Implementierung, die auf der Idee eines Paares basiert, die ich als definieren:Wie funktioniert Calling in Python?

class Pair: 
    def __init__(self, name, prefs, score): 
     self.name = name 
     self.score = score 
     self.preferences = prefs 
     self.next_pair = 0 
     self.prev_pair = 0 

wo self.next_pair und self.prev_pair sind Zeiger auf den vorherigen bzw. nächsten Link.

Um die verknüpfte Liste einzurichten, habe ich eine Installationsfunktion, die so aussieht.

def install(i, pair): 
    flag = 0 
    try: 
     old_pair = pair_array[i] 
     while old_pair.next_pair != 0: 
      if old_pair == pair: 
       #if pair in remainders: remainders.remove(pair) 
       return 0 
      if old_pair.score < pair.score: 
       flag = 1 
       if old_pair.prev_pair == 0: # we are at the beginning 
        old_pair.prev_pair = pair 
        pair.next_pair = old_pair 
        pair_array[i] = pair 
        break 
       else: # we are not at the beginning 
        pair.prev_pair = old_pair.prev_pair 
        pair.next_pair = old_pair 
        old_pair.prev_pair = pair 
        pair.prev_pair.next_pair = pair 
        break 
      else: 
       old_pair = old_pair.next_pair 
     if flag==0: 
      if old_pair == pair: 
       #if pair in remainders: remainders.remove(pair) 
       return 0 
      if old_pair.score < pair.score: 
       if old_pair.prev_pair==0: 
        old_pair.prev_pair = pair 
        pair.next_pair = old_pair 
        pair_array[i] = pair 
       else: 
        pair.prev_pair = old_pair.prev_pair 
        pair.next_pair = old_pair 
        old_pair.prev_pair = pair 
        pair.prev_pair.next_pair = pair 
      else: 
       old_pair.next_pair = pair 
       pair.prev_pair = old_pair 
     except KeyError: 
      pair_array[i] = pair 
      pair.prev_pair = 0 
      pair.next_pair = 0 

Im Laufe des Programms, Ich baue ein Wörterbuch dieser verknüpften Listen nach oben und Links Einnahme von einigen aus und das Hinzufügen von ihnen in anderen. Zwischen dem Löschen und der Neuinstallation werden die Verknüpfungen in einem Zwischenfeld gespeichert.

Während des Debuggens dieses Programms habe ich erkannt, dass mein Verständnis der Art und Weise, wie Python Argumente an Funktionen übergibt, fehlerhaft ist. Stellen Sie sich diesen Testfall, den ich schrieb:

def test_install(): 
    p = Pair(20000, [3, 1, 2, 50], 45) 
    print p.next_pair 
    print p.prev_pair 
    parse_and_get(g) 
    first_run() 
    rat = len(juggler_array)/len(circuit_array) 
    pref_size = get_pref_size() 
    print pref_size 
    print install(3, p) 
    print p.next_pair.name 
    print p.prev_pair    

Wenn ich diesen Test ausführen, bekomme ich das folgende Ergebnis.

0 
0 
10 
None 
10108 
0 

Was ich nicht verstehe ist, warum der zweite Aufruf von p.next_pair ein anderes Ergebnis erzeugt (10108) als der erste Anruf (0). install gibt kein Pair Objekt zurück, das den übergebenen überschreiben kann (es gibt None zurück), und es ist nicht so, als ob ich einen Zeiger an install übergebe.

Mein Verständnis von call-by-value ist, dass der Interpreter die Werte kopiert in eine Funktion übergeben, die Variablen des Aufrufers unverändert lassen. wenn ich sage, zum Beispiel

def foo(x): 
    x = x+1 
    return x 

baz = 2 
y = foo(baz) 
print y 
print baz 

Dann 3 und 2 sollten bzw. ausgedruckt werden. Wenn ich das im Python-Interpreter teste, passiert das tatsächlich.

Ich würde es wirklich schätzen, wenn mir hier jemand in die richtige Richtung zeigen könnte.

+3

Python hat keinen Call-by-Value. Es hat auch keinen Anruf-durch-Verweis. –

+0

FYI, Sie können die Schaltfläche '{}' oben im Bearbeitungsfeld verwenden, um einen ausgewählten Codeblock um 4 Leerzeichen einzurücken - dies ist ein einfacher Weg, den korrekten Einzug beizubehalten. – senderle

+2

Die 'Pair' Klasse sieht aus wie sie sollte wahrscheinlich ein' dict' sein. – Daenyth

Antwort

8

In Python ist alles ein Objekt. Einfache Zuweisung speichert einen Verweis auf das zugewiesene Objekt im zugewiesenen Namen. Daher ist es einfacher, Python-Variablen als Namen zu betrachten, die Objekten zugewiesen sind, und nicht als Objekte, die in benannten Positionen gespeichert sind.

Zum Beispiel:

baz = 2 

... speichert in baz ein Zeiger oder Referenz auf den ganzzahligen Objekt 2 die an anderer Stelle gespeichert sind. (Da der Typ int unveränderlich ist, hat Python tatsächlich einen Pool von kleinen ganzen Zahlen und verwendet dasselbe 2 Objekt überall, aber dies ist ein Implementierungsdetail, das uns nicht viel angeht.

)

Wenn Sie anrufen foo(baz), foo() ‚s lokale Variable x weist auch auf die Integer-Objekt 2 auf den ersten. Das heißt, der foo() -lokale Name x und der globale Name baz sind Namen für das gleiche Objekt, 2. Dann wird x = x + 1 ausgeführt. Dies ändert x, um auf ein anderes Objekt zu zeigen: 3.

Es ist wichtig zu verstehen: x kein Feld ist, das 2 hält, und 2 wird dann auf 3 erhöht. Nein, x zeigt zuerst auf 2 und dieser Zeiger wird dann geändert, um auf 3 zu zeigen. Da wir natürlich nicht geändert haben, welches Objekt baz zeigt, zeigt es immer noch auf 2.

Eine andere Möglichkeit, es zu erklären, ist, dass in Python alle übergebenen Argumente nach Wert sind, aber alle Werte Verweise auf Objekte sind.

Ein kontraintuitives Ergebnis davon ist, dass, wenn ein Objekt veränderbar ist, es durch irgendeine Referenz geändert werden kann und alle Referenzen die Änderung "sehen". Betrachten wir zum Beispiel diese:

baz = [1, 2, 3] 

def foo(x): 
    x[0] = x[0] + 1 

foo(baz) 
print baz 
>>> [2, 2, 3] 

Diese scheint sehr verschieden von unserem ersten Beispiel. Aber in Wirklichkeit wird das Argument auf die gleiche Weise weitergegeben. foo() empfängt einen Zeiger auf baz unter dem Namen x und führt dann eine Operation auf, die es ändert (in diesem Fall wird das erste Element der Liste auf ein anderes Objekt int verwiesen). Der Unterschied ist, dass der Name x niemals auf ein neues Objekt zeigt; Es ist x[0], das geändert wird, um auf ein anderes Objekt zu verweisen. x selbst zeigt immer noch auf das gleiche Objekt wie baz. (Tatsächlich wird unter der Haube die Zuordnung zu x[0] zu einem Methodenaufruf: x.__setitem__().) baz "sieht" also die Änderung der Liste. Wie konnte es nicht?

Sie sehen dieses Verhalten nicht mit ganzen Zahlen und Strings, weil Sie ganze Zahlen oder Zeichenfolgen nicht ändern können; Sie sind unveränderliche Typen, und wenn Sie sie ändern (z. B. x = x + 1), ändern Sie diese nicht, sondern binden Ihren Variablennamen an ein völlig anderes Objekt. Wenn Sie baz in ein Tupel ändern, z. baz = (1, 2, 3), Sie werden feststellen, dass foo() Ihnen einen Fehler gibt, weil Sie Elementen eines Tupels nicht zuweisen können; Tupel sind ein anderer unveränderlicher Typ. Wenn ein Tupel "geändert" wird, muss ein neues Tupel erstellt werden, und die Zuweisung verweist dann auf das neue Objekt.

Objekte von Klassen, die Sie definieren, sind veränderbar und Ihre Pair -Instanz kann daher durch jede Funktion, an die sie übergeben wird, geändert werden - das heißt, Attribute können anderen Objekten hinzugefügt, gelöscht oder neu zugewiesen werden. Keines dieser Dinge bindet irgendeinen der Namen, die auf Ihr Objekt verweisen, so dass alle Namen, die darauf zeigen, die Änderungen "sehen".

+0

.. und die vollständige Antwort ist _superb_. Vielen Dank! – sarnold

+0

Ausgezeichnete Antwort. Das ist das Beste, was ich je gesehen habe. –

1

Ich werde in einem leicht erschwerender Faktor werfen:

>>> def foo(x): 
... x *= 2 
... return x 
... 

definieren eine etwas andere Funktion unter Verwendung einer Methode, die ich für Zahlen, Listen unterstützt kennen und Streicher.

Zuerst nennen es mit Streichern:

>>> baz = "hello" 
>>> y = foo(baz) 
>>> y 
'hellohello' 
>>> baz 
'hello' 

Als nächstes rufen Sie es mit Listen:

>>> baz=[1,2,2] 
>>> y = foo(baz) 
>>> y 
[1, 2, 2, 1, 2, 2] 
>>> baz 
[1, 2, 2, 1, 2, 2] 
>>> 

Bei Strings wird das Argument nicht geändert. Bei Listen wird das Argument geändert.

Wenn ich es wäre, würde ich vermeiden, die Argumente innerhalb der Methoden zu ändern.

+3

Vermeiden Sie die Verwendung von In-Place-Operatoren für Argumente, die veränderbare Typen sein können, es sei denn, Sie beabsichtigen, sie zu mutieren. – kindall

+0

@Kindall: Das ist viel eleganter und präziser. Nett. – sarnold

8

Python kopiert nichts, wenn Variablen an eine Funktion übergeben werden. Es ist weder Call-by-Value noch Call-by-Reference, aber von diesen beiden ist es Call-by-Reference ähnlicher.Man könnte es sich als "Call-by-Value" vorstellen, aber der Wert ist eine Referenz ".

Wenn Sie ein veränderbares Objekt an eine Funktion übergeben, wirkt sich die Änderung dieses Objekts innerhalb der Funktion auf das Objekt aus, wo immer es angezeigt wird. (Wenn Sie ein unveränderliches Objekt an eine Funktion übergeben, wie eine Zeichenfolge oder eine ganze Zahl, dann können Sie das Objekt per Definition überhaupt nicht ändern.)

Der Grund dafür ist nicht technisch pass-by-reference ist, dass Sie einen Namen binden können, so dass der Name sich auf etwas anderes bezieht. (Für Namen unveränderlicher Objekte ist dies das einzige, was Sie mit ihnen tun können.) Das erneute Binden eines Namens, der nur innerhalb einer Funktion existiert, wirkt sich nicht auf Namen aus, die möglicherweise außerhalb der Funktion existieren.

In Ihrem ersten Beispiel mit den Objekten Pair ändern Sie ein Objekt, so dass Sie die Effekte außerhalb der Funktion sehen.

In Ihrem zweiten Beispiel ändern Sie keine Objekte, Sie binden nur Namen an andere Objekte (in diesem Fall andere Ganzzahlen). baz ist ein Name, der auf ein Integer-Objekt (in Python ist alles ein Objekt, sogar ganze Zahlen) mit dem Wert 2 zeigt. Wenn Sie baz an foo(x) übergeben, wird der Name x lokal in der foo-Funktion auf dem Stapel erstellt und x wird auf den Zeiger gesetzt, der in die Funktion übergeben wurde - derselbe Zeiger wie baz. Aber x und baz sind nicht das Gleiche, sie enthalten nur Zeiger auf das gleiche Objekt. In der Zeile x = x+1 wird x rebound, um auf ein Integer-Objekt mit dem Wert 3 zu zeigen, und dieser Zeiger wird von der Funktion zurückgegeben und zum Binden des Integer-Objekts an y verwendet.

Wenn Sie Ihr erstes Beispiel umschreiben, um explizit ein neues Paarobjekt innerhalb Ihrer Funktion zu erstellen, basierend auf den Informationen aus dem übergebenen Paarobjekt (ob es sich um eine Kopie handelt, die Sie dann ändern oder wenn Sie einen Konstruktor machen, der das ändert) Daten zur Konstruktion), dann hätte Ihre Funktion nicht den Nebeneffekt, das übergebene Objekt zu modifizieren.

Edit: Übrigens sollten Sie in Python nicht 0 als Platzhalter verwenden, um "I don ' t habe einen Wert "- verwende None. Und ebenso sollten Sie nicht 0 zu False, wie Sie scheinen, in flag zu tun scheinen. Aber alle von 0, None und False bewerten zu False in booleschen Ausdrücken, also egal welche von denen Sie verwenden, können Sie Dinge wie if not flag anstelle von if flag == 0 sagen.

+0

Danke für die nachdenkliche Antwort, ich denke, ich habe ein besseres Gefühl dafür, was passiert, wenn Python eine Prozedur auswertet. In Bezug auf deine letzte Bearbeitung, warum wird None als mehr Pythonic angesehen? Ist es nur lesbarer, oder könnte es einen Fehler geben, der aus der Verwendung von 0 resultieren könnte? –

+0

Nur weil es besser lesbar ist. Es gibt kein offensichtliches Problem bei der Verwendung von 0 für None, es sei denn, es könnte jemals mit einem gültigen Wert verwechselt werden (zum Beispiel, wenn Sie jemals eine ganze Zahl an dieser Stelle speichern oder versuchen würden, damit zu rechnen). –

2

Ich schlage vor, dass Sie vergessen, eine verknüpfte Liste zu implementieren, und verwenden Sie einfach eine Instanz von Python list. Wenn Sie etwas anderes als das Standard-Python list benötigen, können Sie möglicherweise etwas aus einem Python-Modul wie collections verwenden.

Eine Python-Schleife, die den Links in einer verknüpften Liste folgt, wird mit Python-Interpreter-Geschwindigkeit ausgeführt, dh langsam.Wenn Sie einfach die integrierte Klasse list verwenden, werden Ihre Listenoperationen in Pythons C-Code ausgeführt, und Sie werden schneller.

Wenn Sie so etwas wie eine Liste aber mit schnellem Einfügen und schnellem Löschen benötigen, können Sie eine dict arbeiten lassen? Wenn es eine Art von ID-Wert (String oder Integer oder was auch immer) gibt, der verwendet werden kann, um Ihren Werten eine Reihenfolge aufzuerlegen, können Sie dies als Schlüsselwert verwenden und blitzschnell Werte einfügen und löschen. Wenn Sie dann die Werte in der richtigen Reihenfolge extrahieren müssen, können Sie mit der Methodenfunktion dict.keys() eine Liste der Schlüsselwerte abrufen und diese verwenden.

Aber wenn Sie wirklich verknüpfte Listen benötigen, schlage ich vor, dass Sie von jemand anderem geschriebenen und debuggten Code finden, und passen Sie ihn an Ihre Bedürfnisse an. Google Suche nach "Python Linked List Rezept" oder "Python Linked List-Modul".