2016-06-11 18 views
4

Zum Beispiel:Was macht CPython eigentlich, wenn "=" auf primitiven Variablen ausgeführt wird?

a = some_process_that_generates_integer_result() 
b = a 

Jemand sagte mir, dass b und ein zum gleichen Stück integer Objekt zeigen wird, so b die Referenzzähler des Objekts ändern würde. Der Code wird in Funktion PyObject* ast2obj_expr(void* _o) in Python-ast.c ausgeführt:

static PyObject* ast2obj_object(void *o) 
{ 
    if (!o) 
     o = Py_None; 
    Py_INCREF((PyObject*)o); 
    return (PyObject*)o; 
} 

...... 

case Num_kind: 
    result = PyType_GenericNew(Num_type, NULL, NULL); 
    if (!result) goto failed; 
    value = ast2obj_object(o->v.Num.n); 
    if (!value) goto failed; 
    if (PyObject_SetAttrString(result, "n", value) == -1) 
      goto failed; 
    Py_DECREF(value); 
    break; 

Aber ich denke, zu modifizieren Referenzzählung ohne Eigentümerwechsel wirklich sinnlos ist. Was ich erwarte, ist, dass jede Variable, die primitive Werte (Floats, Integer usw.) enthält, immer ihren eigenen Wert hat, anstatt sich auf ein und dasselbe Objekt zu beziehen.

Und in der Ausführung meines einfachen Test-Code, fand ich die Bruchstelle in dem obigen Num_kind Zweig nie erreicht:

def some_function(x, y): 
    return (x+y)*(x-y) 

a = some_function(666666,66666) 
print a 

b = a 
print a 
print b 

b = a + 999999 
print a 
print b 

b = a 
print a 
print b 

ich das python2.7-dbg Programm zur Verfügung gestellt von Debian verwenden. Ich bin mir sicher, dass das Programm und der Quellcode übereinstimmen, weil viele andere Breakpoints korrekt funktionieren.

Also, was macht CPython eigentlich auf primitive Art Objekte?

+0

Ich bin mir nicht sicher, was Sie hier fragen. Auf welchen Haltepunkt beziehen Sie sich? Welche Art von Verhalten haben Sie mit diesem Code erwartet und wie hat er Ihre Erwartungen nicht erfüllt? – poke

+0

@poke Fehlende Beschreibung hinzugefügt. Der Haltepunkt befindet sich in der Num_kind-Verzweigung im obigen C-Code, da mir jemand gesagt hat, dass dieser Codeabschnitt relativ zum numerisch variablen Kopieren von Laufzeit ist. – jiandingzhe

+1

"jede Variable, die primitive Werte enthält". Python hat nicht wirklich _have_ Variablen, die Werte enthalten. Stattdessen hat es stark typisierte Objekte, die an Namen gebunden werden können. Dies unterscheidet sich ziemlich vom C-style-Datenmodell, und das Python-Datenmodell in C-Termen zu verstehen, ist selten fruchtbar, es sei denn, Sie interagieren mit C-Code. Sie können diesen Artikel hilfreich finden: [Fakten und Mythen über Python-Namen und Werte] (http://nedbatchelder.com/text/names.html), die von SO-Veteran Ned Batchelder geschrieben wurde. –

Antwort

7

Zunächst gibt es in Python keine "primitiven Objekte". Alles ist ein Objekt der gleichen Art, und alle werden auf der Sprachebene auf die gleiche Weise behandelt. Als solche arbeiten folgende Zuordnungen die gleiche Art und Weise unabhängig von den Werten, die zugeordnet sind:

a = some_process_that_generates_integer_result() 
b = a 

In Python-Zuweisungen sind immer Referenz Kopien. Was auch immer die Funktion zurückgibt, ihre Referenz ist kopiert in die Variable a. Und dann in der zweiten Zeile, die Referenz ist wieder kopiert in die Variable b. Daher beziehen sich beide Variablen auf genau das gleiche Objekt.

Sie können dies leicht überprüfen, indem Sie die id()-Funktion, die Ihnen die Identität eines Objekts wird sagen:

print id(a) 
print id(b) 

Dieses zweimal die gleiche Kennnummer gedruckt wird. Beachten Sie jedoch, dass, wenn Sie nur das tun, kopiert Sie die Referenz noch zwei Mal: ​​Es sind keine Variablen, die zu Funktionen, sondern Kopien von Referenzen übergeben werden.

Dies unterscheidet sich von anderen Sprachen, wo man oft zwischen „call by value“ und „call by reference“ unterscheiden. Ersteres bedeutet, dass Sie eine Kopie des Werts erstellen und an eine Funktion übergeben, was bedeutet, dass für diesen Wert neuer Speicher zugewiesen wird. Letzteres bedeutet, dass die tatsächliche Referenz übergeben wird und Änderungen an dieser Referenz auch die ursprüngliche Variable beeinflussen.

Was Python tut, heißt oft "call by assignment": Jeder Funktionsaufruf, bei dem Argumente übergeben werden, ist im Wesentlichen eine Zuweisung in neue Variablen (die dann der Funktion zur Verfügung stehen). Und eine Aufgabe kopiert die Referenz.

Wenn alles ein Objekt ist, ist dies eigentlich eine sehr einfache Strategie. Und wie ich oben sagte, ist das, was mit Ganzzahlen passiert, nicht anders als das, was mit anderen Objekten geschieht. Die einzige "besondere" Sache über Ganzzahlen ist, dass sie unveränderlich sind, so dass Sie ihre Werte nicht ändern können. Dies bedeutet, dass sich ein Integer-Objekt immer auf den exakt gleichen Wert bezieht. Dies macht es einfach, das Objekt (im Speicher) mit mehreren Werten zu teilen. Jede Operation, die ein neues Ergebnis liefert, gibt Ihnen ein anderes Objekt. Wenn Sie also eine Reihe von arithmetischen Operationen durchführen, ändern Sie tatsächlich das Objekt, auf das eine Variable ständig zeigt.

Dasselbe passiert auch mit anderen unveränderlichen Objekten, zum Beispiel Strings. Jede Operation, die eine geänderte Zeichenfolge liefert, gibt Ihnen ein anderes Zeichenfolgenobjekt.

Zuweisungen mit veränderbaren Objekten sind jedoch ebenfalls identisch. Es ist nur möglich, den Wert dieser Objekte zu ändern, sodass sie anders aussehen. Betrachten Sie folgendes Beispiel:

a = [1] # creates a new list object 
b = a # copies the reference to that same list object 
c = [2] # creates a new list object 
b = a + C# concats the two lists and creates a new list object 
d = b 
# at this point, we have *three* list objects 
d.append(3) # mutates the list object 
print(d) 
print(b) # same result since b and d reference the same list object 

Nun zurück zu Ihrer Frage kommen und die C-Code Sie es zitieren, die Sie suchen tatsächlich im falschen Teil CPython es eine Erklärung zu erhalten. AST ist der abstrakte Syntaxbaum, den der Parser beim Parsen einer Datei erstellt. Es spiegelt die Syntaxstruktur eines Programms wider, sagt aber nichts über das tatsächliche Laufzeitverhalten aus.

Der Code, den Sie für die Num_kind angezeigt haben, ist tatsächlich verantwortlich für die Erstellung von Num AST-Objekten. Sie können eine Vorstellung davon bekommen, wenn die ast module mit:

>>> import ast 
>>> doc = ast.parse('foo = 5') 

# the document contains an assignment 
>>> doc.body[0] 
<_ast.Assign object at 0x0000000002322278> 

# the target of that assignment has the id `foo` 
>>> doc.body[0].targets[0].id 
'foo' 

# and the value of that assignment is the `Num` object that was 
# created in that C code, with that `n` property containing the value 
>>> doc.body[0].value 
<_ast.Num object at 0x00000000023224E0> 
>>> doc.body[0].value.n 
5 

Wenn Sie eine Vorstellung von der eigentlichen Auswertung von Python-Code erhalten möchten, müssen Sie zunächst auf Byte-Code aussehen sollte. Der Bytecode ist, was zur Laufzeit von der virtuellen Maschine ausgeführt wird. Sie können die dis module verwenden, um Byte-Code für Python-Code zu sehen:

>>> def test(): 
     foo = 5 

>>> import dis 
>>> dis.dis(test) 
    2   0 LOAD_CONST    1 (5) 
       3 STORE_FAST    0 (foo) 
       6 LOAD_CONST    0 (None) 
       9 RETURN_VALUE 

Wie Sie sehen können, gibt es zwei große Bytecode-Instruktionen hier: LOAD_CONST und STORE_FAST. LOAD_CONST lädt nur einen konstanten Wert auf den Auswertungsstapel. In diesem Beispiel laden wir nur eine konstante Zahl, aber wir könnten stattdessen auch den Wert eines Funktionsaufrufs laden (versuchen Sie es mit dem Modul dis, um herauszufinden, wie es funktioniert).

Die Zuweisung selbst erfolgt mit STORE_FAST. Die Interpreter-Bytecode tut the following für diesen Befehl:

TARGET(STORE_FAST) 
{ 
    v = POP(); 
    SETLOCAL(oparg, v); 
    FAST_DISPATCH(); 
} 

So wird es im wesentlichen den Wert (die Bezugnahme auf das Integer-Objekt) aus dem Stapel und ruft dann SETLOCAL die im wesentlichen wird nur den Wert der lokalen Variablen zuweisen.

Beachten Sie jedoch, dass dadurch die Referenzzahl dieses Werts nicht erhöht wird. Das ist, was mit LOAD_CONST geschieht, oder jede andere Byte-Code-Befehl, der einen Wert von irgendwo bekommt:

TARGET(LOAD_CONST) 
{ 
    x = GETITEM(consts, oparg); 
    Py_INCREF(x); 
    PUSH(x); 
    FAST_DISPATCH(); 
} 

So tl; dr: Zuordnungen in Python werden immer Kopien verweisen.Referenzen werden auch immer dann kopiert, wenn ein Wert verwendet wird (aber in vielen anderen Situationen existiert die kopierte Referenz nur für eine kurze Zeit). Die AST ist verantwortlich für das Erstellen einer Objektdarstellung von geparsten Programmen (nur die Syntax), während der Bytecode-Interpreter den zuvor kompilierten Bytecode ausführt, um zur Laufzeit tatsächliche Dinge zu tun und mit realen Objekten umzugehen.