الترميز الافتراضي لرسائل الاستثناء
-
21-09-2019 - |
سؤال
الكود التالي يبحث في سلوك 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])
إذا كنت بحاجة إلى عرض رسالة الخطأ هذه مع استثناء متتالي.