Nachdem ich in Pythons Quellcode eingetaucht bin, stelle ich fest, dass es ein Array von PyInt_Object
s von int(-5)
bis int(256)
(@ src / Objects / intobject.c) enthält.
Ein kleines Experiment beweist es:
>>> a = 1
>>> b = 1
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False
Wenn ich diesen Code jedoch zusammen in einer py-Datei ausführe (oder sie mit Semikolons verbinde), ist das Ergebnis anders:
>>> a = 257; b = 257; a is b
True
Ich bin gespannt, warum sie immer noch dasselbe Objekt sind. Deshalb habe ich mich eingehender mit dem Syntaxbaum und dem Compiler befasst und eine Aufrufhierarchie gefunden, die unten aufgeführt ist:
PyRun_FileExFlags()
mod = PyParser_ASTFromFile()
node *n = PyParser_ParseFileFlagsEx() //source to cst
parsetoke()
ps = PyParser_New()
for (;;)
PyTokenizer_Get()
PyParser_AddToken(ps, ...)
mod = PyAST_FromNode(n, ...) //cst to ast
run_mod(mod, ...)
co = PyAST_Compile(mod, ...) //ast to CFG
PyFuture_FromAST()
PySymtable_Build()
co = compiler_mod()
PyEval_EvalCode(co, ...)
PyEval_EvalCodeEx()
Dann habe ich Debug-Code in PyInt_FromLong
und vor / nach hinzugefügt PyAST_FromNode
und eine test.py ausgeführt:
a = 257
b = 257
print "id(a) = %d, id(b) = %d" % (id(a), id(b))
Die Ausgabe sieht aus wie:
DEBUG: before PyAST_FromNode
name = a
ival = 257, id = 176046536
name = b
ival = 257, id = 176046752
name = a
name = b
DEBUG: after PyAST_FromNode
run_mod
PyAST_Compile ok
id(a) = 176046536, id(b) = 176046536
Eval ok
Es bedeutet , dass während der cst
zu ast
transformieren, zwei verschiedene PyInt_Object
s erstellt werden (eigentlich ist es in der ausgeführt ist ast_for_atom()
Funktion), aber sie später zusammengeführt werden.
Es fällt mir schwer, die Quelle zu verstehen, PyAST_Compile
und PyEval_EvalCode
deshalb bin ich hier, um um Hilfe zu bitten. Ich bin dankbar, wenn jemand einen Hinweis gibt.
quelle
Antworten:
Python speichert Ganzzahlen im Bereich zwischen
[-5, 256]
, sodass erwartet wird, dass auch Ganzzahlen in diesem Bereich identisch sind.Was Sie sehen, ist der Python-Compiler, der identische Literale optimiert, wenn sie Teil desselben Textes sind.
Beim Eingeben der Python-Shell ist jede Zeile eine völlig andere Anweisung, die in einem anderen Moment analysiert wird.
>>> a = 257 >>> b = 257 >>> a is b False
Wenn Sie jedoch denselben Code in eine Datei einfügen:
$ echo 'a = 257 > b = 257 > print a is b' > testing.py $ python testing.py True
Dies geschieht immer dann, wenn der Parser die Möglichkeit hat, zu analysieren, wo die Literale verwendet werden, beispielsweise beim Definieren einer Funktion im interaktiven Interpreter:
>>> def test(): ... a = 257 ... b = 257 ... print a is b ... >>> dis.dis(test) 2 0 LOAD_CONST 1 (257) 3 STORE_FAST 0 (a) 3 6 LOAD_CONST 1 (257) 9 STORE_FAST 1 (b) 4 12 LOAD_FAST 0 (a) 15 LOAD_FAST 1 (b) 18 COMPARE_OP 8 (is) 21 PRINT_ITEM 22 PRINT_NEWLINE 23 LOAD_CONST 0 (None) 26 RETURN_VALUE >>> test() True >>> test.func_code.co_consts (None, 257)
Beachten Sie, wie der kompilierte Code eine einzelne Konstante für die enthält
257
.Zusammenfassend lässt sich sagen, dass der Python-Bytecode-Compiler keine massiven Optimierungen durchführen kann (wie Sprachen mit statischen Typen), aber mehr als Sie denken. Eines dieser Dinge ist, die Verwendung von Literalen zu analysieren und zu vermeiden, sie zu duplizieren.
Beachten Sie, dass dies nicht mit dem Cache zu tun hat, da dies auch für Floats funktioniert, die keinen Cache haben:
>>> a = 5.0 >>> b = 5.0 >>> a is b False >>> a = 5.0; b = 5.0 >>> a is b True
Für komplexere Literale wie Tupel "funktioniert es nicht":
>>> a = (1,2) >>> b = (1,2) >>> a is b False >>> a = (1,2); b = (1,2) >>> a is b False
Aber die Literale im Tupel werden geteilt:
>>> a = (257, 258) >>> b = (257, 258) >>> a[0] is b[0] False >>> a[1] is b[1] False >>> a = (257, 258); b = (257, 258) >>> a[0] is b[0] True >>> a[1] is b[1] True
In Bezug darauf, warum Sie sehen, dass zwei
PyInt_Object
erstellt werden, würde ich vermuten, dass dies getan wird, um einen wörtlichen Vergleich zu vermeiden. Zum Beispiel kann die Zahl257
durch mehrere Literale ausgedrückt werden:>>> 257 257 >>> 0x101 257 >>> 0b100000001 257 >>> 0o401 257
Der Parser hat zwei Möglichkeiten:
Wahrscheinlich verwendet der Python-Parser den zweiten Ansatz, der das Umschreiben des Konvertierungscodes vermeidet und auch einfacher zu erweitern ist (zum Beispiel funktioniert er auch mit Floats).
Beim Lesen der
Python/ast.c
Datei werden alle Zahlen analysiert. Diese Funktionparsenumber
ruftPyOS_strtoul
auf, um den ganzzahligen Wert (für Ganzzahlen) zu erhalten, und ruft schließlich Folgendes aufPyLong_FromString
:x = (long) PyOS_strtoul((char *)s, (char **)&end, 0); if (x < 0 && errno == 0) { return PyLong_FromString((char *)s, (char **)0, 0); }
Wie Sie hier sehen können, prüft der Parser nicht , ob er bereits eine Ganzzahl mit dem angegebenen Wert gefunden hat. Dies erklärt, warum Sie sehen, dass zwei int-Objekte erstellt werden, und dies bedeutet auch, dass meine Vermutung richtig war: Der Parser erstellt zuerst die Konstanten und erst danach wird der Bytecode optimiert, um dasselbe Objekt für gleiche Konstanten zu verwenden.
Der Code, der diese Prüfung durchführt, muss sich irgendwo in
Python/compile.c
oder befindenPython/peephole.c
, da dies die Dateien sind, die den AST in Bytecode umwandeln.Insbesondere
compiler_add_o
scheint die Funktion diejenige zu sein, die dies tut. Es gibt diesen Kommentar incompiler_lambda
:/* Make None the first constant, so the lambda can't have a docstring. */ if (compiler_add_o(c, c->u->u_consts, Py_None) < 0) return 0;
Es scheint also, als würde
compiler_add_o
es verwendet, um Konstanten für Funktionen / Lambdas usw. einzufügen. Diecompiler_add_o
Funktion speichert die Konstanten in einemdict
Objekt, und daraus folgt sofort, dass gleiche Konstanten in denselben Slot fallen, was zu einer einzelnen Konstante im endgültigen Bytecode führt.quelle
False
Ausführunga = 5.0; b = 5.0; print (a is b)
sowohl mit py2 und py3 auf win7True
(gerade überprüft). Optimierungen sind nicht zuverlässig, da sie nur ein Implementierungsdetail sind, sodass der Punkt, den ich in meiner Antwort ansprechen wollte, nicht ungültig wird. Auchcompile('a=5.0;b=5.0', '<stdin>', 'exec')).co_consts
zeigt , dass es nur eine ist5.0
konstant (in python3.3 unter Linux).