2017-11-01 97 views
69

Z tweet here:Dlaczego wielkość tej zmiany Python String na int nieudanej konwersji

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

Dostajemy 74, a następnie 77 bajtów dla dwóch getsizeof połączeń.

Wygląda na to, że dodajemy 3 bajty do obiektu, z nieudanego połączenia int.

Więcej przykładów z Twittera (być może trzeba ponownie uruchomić Pythona, aby zresetować rozmiary powrotem do 74):

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

77!

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

74, potem 77.

+0

Musi być powiązany z PEP393 ** Elastyczny ** Reprezentacja ciągów. – VPfB

+12

Zanim kliknęłam na link z tweetem * wiedziałam * to Beazley właśnie to zrobił. –

+0

yeap, to wygląda na coraz bardziej prawdopodobne, że będę musiał zaoszczędzić i pójść na jeden z jego kursów – jeremycg

Odpowiedz

70

kod, który konwertuje ciągi do wskazówki w CPython 3,6 requests a UTF-8 form of the string to work with:

buffer = PyUnicode_AsUTF8AndSize(asciidig, &buflen); 

i tworzy ciąg reprezentacja UTF-8 po raz pierwszy, to żądany i caches it on the string object:

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); 
} 

Dodatkowe 3 bajty dotyczą reprezentacji UTF-8.


Można się zastanawiać, dlaczego ich wielkość nie zmienia się, gdy ciąg jest coś '40' lub 'plain ascii text'. To dlatego, że jeśli ciąg znaków jest w "compact ascii" representation, Python nie tworzy oddzielnej reprezentacji UTF-8. To returns the ASCII representation directly, który jest już ważny UTF-8:

#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)) 

też może się zastanawiać, dlaczego wielkość nie zmienia się na coś takiego '1'. To jest UFF11 FULLWIDTH DIGIT ONE, który int traktuje jako odpowiednik '1'.To dlatego one of the earlier steps w procesie string-to-int to

asciidig = _PyUnicode_TransformDecimalAndSpaceToASCII(u); 

który konwertuje wszystkie białe znaki do ' ' i konwertuje wszystkie cyfry dziesiętne Unicode do odpowiednich cyfr ASCII. Ta konwersja zwraca oryginalny ciąg znaków, jeśli nie powoduje zmiany niczego, ale gdy robi zmiany, tworzy nowy łańcuch, a nowy łańcuch jest tym, który otrzymuje utworzoną reprezentację UTF-8.


chodzi o przypadkach zawijających int na jeden ciąg wygląda to wpływa na inny, to są rzeczywiście ten sam obiekt ciągiem. Istnieje wiele warunków, w których Python będzie ponownie używał łańcuchów, równie mocno w Weird Implementation Detail Land, jak wszystko, co do tej pory omawialiśmy. W przypadku 'ñ' ponowne użycie ma miejsce, ponieważ jest to łańcuch jednoliterowy w zakresie Latin-1 ('\x00' - '\xff') oraz implementacja stores and reuses those.

+0

@jeremycg: Twój fragment nigdy nie żąda UTF-8. Łączenie ciągów nie wykonuje konwersji w formacie UTF-8. – user2357112

+1

To jest świetna robota, ale dla mnie trochę trudno zrozumieć, dlaczego błąd wywołany dla int ("ñ") powoduje przeniesienie 3, a nie błąd int ("[") na przykład. co to jest różnica? –

+0

OK, i myślę, że trzymamy też "ñ" w pamięci i modyfikujemy to, zamiast kopii, również wyjaśniając x/y, i "ñ", "ñ". Myślę, że to jest to, ale wstrzyma się z akceptacją, aby zobaczyć, czy są jakieś inne pomysły. – jeremycg

-1

Zgodnie z dokumentacją here:

getsizeof() wywołuje sizeof metody obiektu i dodaje dodatkowy Garbage Collector narzut jeśli obiekt jest zarządzany przez moduł do zbierania śmieci .

Ale nie ma nic wspólnego z getsizeof() Jedno jest przez pewną:

To nie ma nic związanego z modułem sys i metody sys.getsizeof(), problem jest z __sizeof__ metody. mogę odtworzyć błąd bez niego getsizeof():

x = 'ñ' 
print(x.__sizeof__()) 
#74 
int('ñ') 
print(x.__sizeof__()) 
#77 

I wyjaśnić, dlaczego to się dzieje, został dostarczony przez @ user2357112 z przyjętą odpowiedzi

+1

Wydaje się rozsądne, ale dlaczego tak się dzieje dla 'ñ', a nie' n'? – Blorgbeard

+1

Zdecydowanie nie jest związany z 'sys.getsizeof', ponieważ wywołanie' x .__ sizeof __() 'daje ten sam wynik. –

+1

'sys.getsizeof (obj)! = Obj .__ sizeof __()'. Spójrz na [kod źródłowy do 'getsizeof'] (https://github.com/python/cpython/blob/master/Python/sysmodule.c) –