• בלוג
  • יוניקוד כל הדרך ב Python2

יוניקוד כל הדרך ב Python2

05/03/2020

אחד הדברים שמבלבלים ב Python2 הוא שמחרוזות אינן Unicode כברירת מחדל. הדבר מייצר המון טעויות של מתכנתים עם המון הודעות שגיאה שונות ומשונות. הנה כמה דוגמאות ופיתרונות שלהן.

1. השוואה בין יוניקוד למחרוזת

ב Python2 יש שני סוגים של אוביקטים לייצוג טקסט: יש את str שמייצג רצף של בתים ואת unicode שמייצג רצף של אותיות. ההבדל ביניהן הוא קריטי, האורך של המילה "שלום" כמחרוזת בתים הוא 8, אבל בתור רצף אותיות הוא 4. או בפייתון:

>>> len('שלום')
8
>>> len(u'שלום')
4

גם השוואה בין יוניקוד למחרוזת לא תעבוד לכם כי מדובר בשני אוביקטים שונים, והנה הניסיון עם הודעת השגיאה:

>>> 'שלום' == u'לשום'
__main__:1: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
False

כשאתם רואים את ההודעה לכו לבדוק מה נמצא בתוך המשתנים שלכם וחפשו איזה מהם הוא מהטיפוס הלא נכון.

2. פורמט בין יוניקוד למחרוזת

עוד הודעה מוזרה מתקבלת כשמנסים להפעיל את format ולערבב בין יוניקוד למחרוזות רגילות. זה נראה כך:

>>> print('{} / {}'.format(u'א', u'ב'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u05d0' in position 0: ordinal not in range(128)

או בכיוון ההפוך:

>>> print(u'{} / {}'.format('א', 'ב'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd7 in position 0: ordinal not in range(128)

בשני המקרים מדובר באותה בעיה - ניסינו לערבב מחרוזות str עם unicode, מה שפשוט לא עובד. הפיתרון? יוניקוד כל הדרך:

>>> print(u'{} / {}'.format(u'א', u'ב'))

3. בלבול בין encode ל decode

טעות שלישית היא בלבול בין encode ל decode בשביל להמיר בין str ל unicode. צריך לזכור שהמעבר מ unicode ל str נקרא Encode, והמעבר ההפוך מ str ל Unicode נקרא decode. אנחנו מפענחים קלט שהגיע מבחוץ ולכן הוא בפורמט str כדי לשמור אותו בזיכרון, ואנחנו מקודדים מידע מהזיכרון כדי שנוכל להעביר אותו לאחרים.

לכן הקוד הזה לא עובד כי צריך לפענח מידע שמגיע מבחוץ (ולא לקודד אותו):

>>> s = codecs.encode('שלום', 'utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd7 in position 0: ordinal not in range(128)

וזה לא עובד כי צריך לקודד מידע מהזיכרון כדי להוציא אותו לפורמט שבחרתם (ולא לפענח אותו):

>>> s = codecs.decode(u'שלום', 'utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

וכאילו שזה לא מספיק מוזר צריך לזכור שה Exceptions האלה הן דווקא הדבר הטוב שקורה. ספריות רבות יחליפו חלק מהאותיות בסימני שאלה במקום לזרוק לכם Exceptions ואז הרבה יותר קשה לגלות איפה הטעות.