سؤال

الكود التالي يبحث في سلوك float() الطريقة عند تغذية رمز غير ASCII:

import sys

try:
  float(u'\xbd')
except ValueError as e:
  print sys.getdefaultencoding() # in my system, this is 'ascii'
  print e[0].decode('latin-1') # u'invalid literal for float(): ' followed by the 1/2 (one half) character
  print unicode(e[0]) # raises "UnicodeDecodeError: 'ascii' codec can't decode byte 0xbd in position 29: ordinal not in range(128)"

سؤالي: لماذا رسالة الخطأ e[0] مشفرة في اللاتينية 1؟ الترميز الافتراضي هو ASCII ، ويبدو أن هذا هو ما unicode() يتوقع.

المنصة هي Ubuntu 9.04 ، Python 2.6.2

هل كانت مفيدة؟

المحلول

E [0] لا يتم تشفيرها مع اللاتينية 1 ؛ يحدث فقط أن البايت xbd ، عندما يتم فك تشفيره باسم Latin-1 ، هو الحرف u+00bd.

يحدث التحويل في Objects/floatobject.c.

أولاً ، يجب تحويل سلسلة Unicode إلى سلسلة بايت. يتم تنفيذ هذا باستخدام PyUnicode_EncodeDecimal():

if (PyUnicode_EncodeDecimal(PyUnicode_AS_UNICODE(v),
                            PyUnicode_GET_SIZE(v),
                            s_buffer,
                            NULL))
        return NULL;

الذي يتم تنفيذه في unicodeobject.c. لا يؤدي أي نوع من تحويلات مجموعة الأحرف ، فهو يكتب فقط بايت مع قيم تساوي أوامر Unicode للسلسلة. في هذه الحالة ، U+00BD -> 0xBD.

البيان تنسيق الخطأ هو:

PyOS_snprintf(buffer, sizeof(buffer),
              "invalid literal for float(): %.200s", s);

أين s يحتوي على سلسلة البايت التي تم إنشاؤها في وقت سابق. PyOS_snprintf() يكتب سلسلة بايت ، و s هي سلسلة بايت ، لذلك تشملها مباشرة.

نصائح أخرى

سؤال جيد جدا!

أخذت الحرية للحفر في رمز مصدر بيثون ، وهو مجرد أمر بعيد على توزيعات Linux التي تم إعدادها بشكل صحيح (apt-get source python2.5)

اللعنة, ، ضربني جون ميليكين. هذا صحيح، PyUnicode_EncodeDecimal هل الجواب يفعل هذا هنا:

/* (Loop ch in the unicode string) */
    if (Py_UNICODE_ISSPACE(ch)) {
        *output++ = ' ';
        ++p;
        continue;
    }
    decimal = Py_UNICODE_TODECIMAL(ch);
    if (decimal >= 0) {
        *output++ = '0' + decimal;
        ++p;
        continue;
    }
    if (0 < ch && ch < 256) {
        *output++ = (char)ch;
        ++p;
        continue;
    }
    /* All other characters are considered unencodable */
    collstart = p;
    collend = p+1;
    while (collend < end) {
        if ((0 < *collend && *collend < 256) ||
            !Py_UNICODE_ISSPACE(*collend) ||
            Py_UNICODE_TODECIMAL(*collend))
            break;
    }

انظر ، إنه يترك جميع نقاط رمز Unicode <256 في مكانها ، وهي أحرف Latin-1 ، استنادًا إلى توافق Unicode للخلف.


إضافة

مع وجود هذا في مكانه ، يمكنك التحقق من خلال تجربة شخصيات أخرى غير لاتين ، وسوف يلقي استثناء مختلف:

>>> float(u"ħ")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'decimal' codec can't encode character u'\u0127' in position 0: invalid decimal Unicode string

يتضمن ترميز ASCII فقط البايتات مع القيم <= 127. نطاق الأحرف الممثلة في هذه البايتات متطابقة في معظم الترميزات ؛ بمعنى آخر ، "A" هو chr(65) في ASCII ، باللغة اللاتينية 1 ، في UTF-8 ، وهلم جرا.

ومع ذلك ، فإن رمز نصف واحد ، ليس جزءًا من مجموعة أحرف ASCII ، لذلك عندما يحاول Python تشفير هذا الرمز إلى ASCII ، لا يمكن أن يفعل شيئًا سوى الفشل.

تحديث: إليك ما يحدث (أفترض أننا نتحدث عن cpython):

float(u'\xbd') يؤدي إلي PyFloat_FromString في floatobject.c يطلق عليها. هذه الوظيفة ، مع إعطاء كائن Unicode ، بدوره المكالمات PyUnicode_EncodeDecimal في UnicodeObject.C يطلق عليها. من القشط فوق الكود ، أحصل على أن هذه الوظيفة تحول كائن Unicode إلى سلسلة عن طريق استبدال كل حرف بنقاط الترميز Unicode <256 مع بايت تلك القيمة ، أي حرف نصف واحد ، وجود CodePoint 189 ، يتحول إلى chr(89).

ثم، PyFloat_FromString هل عملها كالمعتاد. في هذه اللحظة ، تعمل مع سلسلة منتظمة ، والتي تحتوي على بايت لا يتجاوز ASCII. لا يهتم بهذا ؛ إنه يجد بايت ليس رقمًا أو فترة أو ما شابه ، لذلك يرفع خطأ القيمة.

الوسيطة لهذا الاستثناء هي سلسلة

"invalid literal for float(): " + evil_string

هذا جيّد؛ رسالة استثناء هي ، بعد كل شيء ، سلسلة. فقط عندما تحاول فك تشفير هذه السلسلة ، باستخدام ASCII الافتراضي ، فإن هذا يتحول إلى مشكلة.

من تجربتك ، يبدو أن لدي نفس السلوك على النظام الأساسي الخاص بي (PY2.6 على OS X 10.5).

منذ أن أثبتت أن e [0] مشفرة مع latin-1, ، الطريقة الصحيحة لتحويلها unicode هو أن تفعل .decode('latin-1'), ، و ليس unicode(e[0]).

تحديث: لذلك يبدو أن e [0] لا يحتوي على ترميز صالح. بالتأكيد لا latin-1. بسبب ذلك ، كما هو مذكور في مكان آخر في التعليقات ، عليك الاتصال repr(e[0]) إذا كنت بحاجة إلى عرض رسالة الخطأ هذه مع استثناء متتالي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top