Подтвердить что ты не робот

Почему размер этой строки Python изменяется при неудачном преобразовании int

Из tweet here:

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

Мы получаем 74, затем 77 байтов для двух вызовов getsizeof.

Похоже, мы добавляем 3 байта к объекту из отказавшего вызова int.

Еще несколько примеров из twitter (вам может потребоваться перезапустить python на reset размер до 74):

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

77

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

74, то 77.

4b9b3361

Ответ 1

Код, преобразующий строки в int в CPython 3.6 запрашивает форму строки UTF-8 для работы с:

buffer = PyUnicode_AsUTF8AndSize(asciidig, &buflen);

и строка создает представление UTF-8 при первом запросе и кэширует его на строковый объект:

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

Дополнительные 3 байта для представления UTF-8.


Возможно, вам интересно, почему размер не изменяется, когда строка похожа на '40' или 'plain ascii text'. Это потому, что если строка находится в "компактном ascii" представлении, Python не создает отдельное представление UTF-8. Он возвращает ASCII-представление напрямую, которое уже действует 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))

Вы также можете задаться вопросом, почему размер не изменяется для чего-то вроде '1'. Этот U + FF11 FULLWIDTH DIGIT ONE, который int рассматривает как эквивалент '1'. Это потому, что один из предыдущих шагов в процессе "строка-в-ин"

asciidig = _PyUnicode_TransformDecimalAndSpaceToASCII(u);

который преобразует все пробельные символы в ' ' и преобразует все десятичные разряды Unicode в соответствующие цифры ASCII. Это преобразование возвращает исходную строку, если она ничего не меняет, но когда она вносит изменения, она создает новую строку, а новая строка - та, которая получает созданное представление UTF-8.


Что касается случаев, когда вызов int в одной строке выглядит так, как будто он влияет на другой, это фактически один и тот же строковый объект. Есть много условий, при которых Python будет использовать строки, все так же твердо в Weird Implementation Detail Land, как все, что мы обсуждали до сих пор. Для 'ñ' повторное использование происходит потому, что это односимвольная строка в диапазоне Latin-1 ('\x00' - '\xff'), а реализация хранит и повторно использует те.

Ответ 2

В соответствии с документацией здесь:

getizeof() вызывает метод sizeof объектов и добавляет дополнительные служебные данные сборщика мусора, если объект управляется сборщик мусора.

Но не имеет ничего общего с getizeof() Уверенно:

Он не имеет ничего общего с модулем sys и методом sys.getsizeof(), проблема заключается в методе __sizeof__. Я могу воспроизвести ошибку без него getsizeof():

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

И объяснение, почему это происходит, было предоставлено @user2357112 с принятым ответом