Warum ändert sich die Größe dieses Python-Strings bei einer fehlgeschlagenen int-Konvertierung?

70

Aus dem Tweet hier :

import sys
x = 'ñ'
print(sys.getsizeof(x))
int(x) #throws an error
print(sys.getsizeof(x))

Wir erhalten 74, dann 77 Bytes für die beiden getsizeofAufrufe.

Es sieht so aus, als würden wir dem Objekt 3 Bytes aus dem fehlgeschlagenen int-Aufruf hinzufügen.

Einige weitere Beispiele von Twitter (möglicherweise müssen Sie Python neu starten, um die Größe auf 74 zurückzusetzen):

x = 'ñ'
y = 'ñ'
int(x)
print(sys.getsizeof(y))

77!

print(sys.getsizeof('ñ'))
int('ñ')
print(sys.getsizeof('ñ'))

74, dann 77.

jeremycg
quelle
Muss mit PEP393 Flexible String Representation verwandt sein.
VPfB
14
Bevor ich überhaupt auf den Tweet-Link geklickt habe, wusste ich, dass Beazley ihn getwittert hat.
Dimitris Fasarakis Hilliard
Ja, es wird immer wahrscheinlicher, dass ich sparen und zu einem seiner Kurse gehen muss
jeremycg

Antworten:

70

Der Code, der Zeichenfolgen in CPython 3.6 in Ints konvertiert, fordert eine UTF-8-Form der Zeichenfolge an, mit der gearbeitet werden soll :

buffer = PyUnicode_AsUTF8AndSize(asciidig, &buflen);

und die Zeichenfolge erstellt die UTF-8-Darstellung beim ersten Anfordern und speichert sie im Zeichenfolgenobjekt zwischen :

if (PyUnicode_UTF8(unicode) == NULL) {
    assert(!PyUnicode_IS_COMPACT_ASCII(unicode));
    bytes = _PyUnicode_AsUTF8String(unicode, NULL);
    if (bytes == NULL)
        return NULL;
    _PyUnicode_UTF8(unicode) = PyObject_MALLOC(PyBytes_GET_SIZE(bytes) + 1);
    if (_PyUnicode_UTF8(unicode) == NULL) {
        PyErr_NoMemory();
        Py_DECREF(bytes);
        return NULL;
    }
    _PyUnicode_UTF8_LENGTH(unicode) = PyBytes_GET_SIZE(bytes);
    memcpy(_PyUnicode_UTF8(unicode),
              PyBytes_AS_STRING(bytes),
              _PyUnicode_UTF8_LENGTH(unicode) + 1);
    Py_DECREF(bytes);
}

Die zusätzlichen 3 Bytes sind für die UTF-8-Darstellung.


Sie fragen sich vielleicht, warum sich die Größe nicht ändert, wenn die Zeichenfolge so etwas wie '40'oder ist 'plain ascii text'. Dies liegt daran , dass Python keine separate UTF-8-Darstellung erstellt, wenn sich die Zeichenfolge in einer "kompakten ASCII" -Darstellung befindet. Es gibt die ASCII-Darstellung direkt zurück , die bereits in UTF-8 gültig ist:

#define PyUnicode_UTF8(op)                              \
    (assert(_PyUnicode_CHECK(op)),                      \
     assert(PyUnicode_IS_READY(op)),                    \
     PyUnicode_IS_COMPACT_ASCII(op) ?                   \
         ((char*)((PyASCIIObject*)(op) + 1)) :          \
         _PyUnicode_UTF8(op))

Sie fragen sich vielleicht auch, warum sich die Größe für so etwas nicht ändert '1'. Das ist U + FF11 FULLWIDTH DIGIT ONE, was intals äquivalent zu behandelt wird '1'. Dies liegt daran, dass einer der früheren Schritte im String-to-Int-Prozess ist

asciidig = _PyUnicode_TransformDecimalAndSpaceToASCII(u);

Dadurch werden alle Leerzeichen in ' 'und konvertiert alle Unicode-Dezimalstellen in die entsprechenden ASCII-Stellen. Diese Konvertierung gibt die ursprüngliche Zeichenfolge zurück, wenn am Ende nichts geändert wird. Wenn jedoch Änderungen vorgenommen werden, wird eine neue Zeichenfolge erstellt, und die neue Zeichenfolge wird mit einer UTF-8-Darstellung erstellt.


In den Fällen, in denen das Aufrufen inteiner Zeichenfolge so aussieht, als würde es eine andere betreffen, handelt es sich tatsächlich um dasselbe Zeichenfolgenobjekt. Es gibt viele Bedingungen, unter denen Python Zeichenfolgen wiederverwendet, die alle genauso fest in Weird Implementation Detail Land verankert sind wie alles, was wir bisher besprochen haben. Denn 'ñ', geschieht die Wiederverwendung , da dies in dem Latin-1 Bereich eine Single-Zeichenkette ( '\x00'- '\xff') und die Implementierung gespeichert und wieder verwendet diejenigen .

user2357112 unterstützt Monica
quelle
@jeremycg: Ihr Snippet fordert niemals den UTF-8 an. Die Zeichenfolgenverkettung führt keine UTF-8-Konvertierung durch.
user2357112 unterstützt Monica
1
Das ist großartige Arbeit, aber für mich ist es ein wenig schwer zu verstehen, warum der für int ('ñ') erzeugte Fehler einen Übertrag von 3 erzeugt und nicht der Fehler von int ('[') zum Beispiel. Was ist der Unterschied?
Damián Rafael Lattenero
Ok, und ich denke, wir halten auch das 'ñ' im Speicher und modifizieren es anstelle einer Kopie, wobei wir auch das x / y und das 'ñ', 'ñ' erklären. Ich denke, das ist es, werde aber nicht akzeptieren, ob es noch andere Ideen gibt.
Jeremycg
1
@ DamianLattenero: Meine Antwort spricht das jetzt an.
user2357112 unterstützt Monica
Danke für die Antwort, jetzt verstehe ich es! Tolle Antwort
Damián Rafael Lattenero