web-dev-qa-db-fra.com

Pourquoi la taille de cette chaîne Python change-t-elle en cas d'échec d'une conversion int

De la Tweet ici :

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

Nous obtenons 74, puis 77 octets pour les deux appels getsizeof.

Il semble que nous ajoutions 3 octets à l'objet, à partir de l'appel int échoué.

Quelques exemples supplémentaires de Twitter (vous devrez peut-être redémarrer python pour réinitialiser la taille à 74):

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

77!

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

74, puis 77.

71
jeremycg

Le code qui convertit les chaînes en entiers dans CPython 3.6 demande une forme UTF-8 de la chaîne avec laquelle travailler :

buffer = PyUnicode_AsUTF8AndSize(asciidig, &buflen);

et la chaîne crée la représentation UTF-8 la première fois qu'elle est demandée et la met en cache sur l'objet chaîne :

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

Les 3 octets supplémentaires sont destinés à la représentation UTF-8.


Vous vous demandez peut-être pourquoi la taille ne change pas lorsque la chaîne est quelque chose comme '40' Ou 'plain ascii text'. En effet, si la chaîne est en représentation "ascii compacte , Python ne crée pas de représentation UTF-8 distincte. Il retourne directement la représentation ASCII , qui est déjà UTF-8 valide:

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

Vous pourriez également vous demander pourquoi la taille ne change pas pour quelque chose comme '1'. C'est U + FF11 FULLWIDTH DIGIT ONE, que int considère comme équivalent à '1'. C'est parce que l'une des étapes précédentes dans le processus de chaîne à int est

asciidig = _PyUnicode_TransformDecimalAndSpaceToASCII(u);

qui convertit tous les espaces en ' ' et convertit tous les chiffres décimaux Unicode en chiffres ASCII correspondants. Cette conversion renvoie la chaîne d'origine si elle ne change rien, mais lorsqu'elle effectue des modifications, elle crée une nouvelle chaîne et la nouvelle chaîne est celle qui obtient une représentation UTF-8.


Quant aux cas où appeler int sur une chaîne semble en affecter une autre, il s'agit en fait du même objet chaîne. Il existe de nombreuses conditions dans lesquelles Python réutilisera les chaînes, toutes aussi fermement dans Weird Implementation Detail Land que tout ce dont nous avons discuté jusqu'à présent. Pour 'ñ', La réutilisation se produit car il s'agit d'une chaîne de caractères unique dans la plage Latin-1 ('\x00' - '\xff'), Et l'implémentation stocke et réutilise ceux-ci .

71
user2357112