لماذا يقوم Python بطباعة أحرف Unicode عندما يكون الترميز الافتراضي ASCII؟

StackOverflow https://stackoverflow.com/questions/2596714

سؤال

من Python 2.6 Shell:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>> 

كنت أتوقع أن يكون لدي إما بعض الرطوبة أو خطأ بعد بيان الطباعة ، لأن شخصية "é" ليست جزءًا من ASCII ولم أحدد ترميزًا. أعتقد أنني لا أفهم ما هو ASCII هو الترميز الافتراضي.

تعديل

لقد نقلت التحرير إلى إجابات القسم وقبله كما هو مقترح.

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

المحلول

بفضل القطع والقطع من ردود مختلفة ، أعتقد أنه يمكننا غرز شرح.

من خلال محاولة طباعة سلسلة Unicode ، u ' xe9' ، تحاول Python ضمنيًا تشفير هذه السلسلة باستخدام مخطط التشفير المخزّن حاليًا في sys.stdout.encoding. Python يلتقط هذا الإعداد من البيئة التي بدأ منها. إذا لم تتمكن من العثور على ترميز مناسب من البيئة ، عندها فقط يعود إلى ذلك إفتراضي, ، ASCII.

على سبيل المثال ، أستخدم قذيفة باش تقوم بترميز الإعدادات الافتراضية على UTF-8. إذا بدأت بيثون منه ، فإنه يلتقط ويستخدم هذا الإعداد:

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

دعونا للحظة الخروج من قذيفة بيثون ونضع بيئة باش مع بعض الترميز الزائفة:

$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.

ثم ابدأ قذيفة Python مرة أخرى وتحقق من أنها تعود بالفعل إلى ترميز ASCII الافتراضي.

$ python

>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968

بنغو!

إذا حاولت الآن إخراج بعض أحرف Unicode خارج ASCII ، فيجب أن تحصل على رسالة خطأ لطيفة

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

دعنا نخرج بيثون وتجاهل قذيفة باش.

سنلاحظ الآن ما يحدث بعد أن يخرج بيثون السلاسل. لهذا ، سنبدأ أولاً قذيفة باش داخل محطة رسومية (أستخدم محطة جنوم) وسنقوم بتعيين المحطة لفك تشفير الإخراج باستخدام ISO-8859-1 AKA LATIN-1 (عادةً تعيين تشفير حرف في واحدة من القوائم المنسدلة). لاحظ أن هذا لا يغير الفعلي بيئة شل الترميز ، فإنه يغير فقط الطريقة الطرفي سوف تفكك إخراجها ، مثل متصفح الويب. لذلك يمكنك تغيير ترميز المحطة ، بشكل مستقل عن بيئة الصدفة. لنبدأ بعد ذلك Python من القشرة والتحقق من أن sys.stdout.encoding يتم ضبطه على ترميز بيئة Shell (UTF-8 بالنسبة لي):

$ python

>>> import sys

>>> print sys.stdout.encoding
UTF-8

>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>

(1) يقوم Python بإخراج السلسلة الثنائية كما هي ، يستقبلها Terminal ويحاول مطابقة قيمتها مع خريطة حرف Latin-1. في Latin-1 أو 0xe9 أو 233 ، تعطي شخصية "é" وهذا ما تعرضه المحطة.

(2) يحاول بيثون بشكل ضمني قم بتشفير سلسلة Unicode مع أي مخطط يتم تعيينه حاليًا في sys.stdout.encoding ، في هذه الحالة هو "UTF-8". بعد ترميز UTF-8 ، تكون السلسلة الثنائية الناتجة هي " xc3 xa9 '(انظر التفسير اللاحق). يستقبل Terminal الدفق على هذا النحو ويحاول فك تشفير 0xc3a9 باستخدام Latin-1 ، لكن اللاتيني 1 ينتقل من 0 إلى 255 ، وهكذا ، تتدفق فقط البايت 1 بايت في وقت واحد. 0xc3a9 هو 2 بايت طويل ، وبالتالي فإن وحدة فك ترميز لاتيني -1 تفسرها على أنها 0xc3 (195) و 0xa9 (169) وهذا ينتج عن شخصين: ã و ©.

(3) Python يشفر نقطة رمز Unicode u ' xe9' (233) مع مخطط Latin-1. يتبين أن نطاق نقاط رمز Latin-1 هو 0-255 ويشير إلى نفس حرف Unicode داخل هذا النطاق. لذلك ، فإن نقاط رمز Unicode في هذا النطاق سوف تسفر عن نفس القيمة عند ترميزها في Latin-1. لذا فإن u ' xe9' (233) المشفرة في Latin-1 سوف تعطي أيضًا السلسلة الثنائية ' xe9'. يتلقى المحطة هذه القيمة ويحاول مطابقةها على خريطة الأحرف اللاتينية -1. تمامًا مثل الحالة (1) ، فإنه ينتج عنه "é" وهذا ما يتم عرضه.

دعونا الآن نغير إعدادات الترميز الخاصة بالمحطة إلى UTF-8 من القائمة المنسدلة (مثلما تقوم بتغيير إعدادات ترميز متصفح الويب الخاصة بك). لا حاجة لوقف بيثون أو إعادة تشغيل القشرة. ترميز المحطة يتطابق الآن مع بيثون. لنحاول الطباعة مرة أخرى:

>>> print '\xe9' # (4)

>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)

>>>

(4) مخرجات بيثون أ الثنائية سلسلة كما هي. تحاول الطرفية فك تشفير هذا الدفق مع UTF-8. لكن UTF-8 لا يفهم القيمة 0xe9 (انظر التفسير اللاحق) وبالتالي لا يمكن تحويلها إلى نقطة رمز Unicode. لم يتم العثور على نقطة رمز ، لا توجد حرف مطبوع.

(5) يحاول بيثون بشكل ضمني قم بتشفير سلسلة Unicode مع كل ما في sys.stdout.encoding. لا يزال "UTF-8". السلسلة الثنائية الناتجة هي " xc3 xa9 '. يستقبل المحطة الدفق ومحاولات فك [0xc3a9 "باستخدام UTF-8. يعطي قيمة الرمز الخلفي 0xe9 (233) ، والتي تشير على خريطة حرف Unicode إلى الرمز "é". تعرض المحطة "é".

(6) يشفر Python سلسلة Unicode مع Latin-1 ، وهي تعطي سلسلة ثنائية بنفس القيمة " xe9". مرة أخرى ، بالنسبة للمحطة ، هذا هو نفس الحالة (4).

الاستنتاجات: - يقوم Python بإخراج السلاسل غير المرتبطة بالمواد الوظيفية كبيانات أولية ، دون النظر في الترميز الافتراضي. يحدث المحطة فقط لعرضها إذا كان ترميزها الحالي يطابق البيانات. - يخرج Python سلاسل Unicode بعد ترميزها باستخدام المخطط المحدد في sys.stdout.encoding. - يحصل بيثون على هذا الإعداد من بيئة الصدفة. - يعرض المحطة الإخراج وفقًا لإعدادات الترميز الخاصة بها. - ترميز المحطة مستقل من القشرة.


مزيد من التفاصيل حول Unicode و UTF-8 و Latin-1:

Unicode هو في الأساس جدول الأحرف حيث تم تعيين بعض المفاتيح (نقاط الرمز) تقليديًا للإشارة إلى بعض الرموز. على سبيل المثال ، تقرر اتفاقية أن المفتاح 0xe9 (233) هو القيمة التي تشير إلى الرمز "é". استخدم ASCII و Unicode نقاط الرمز نفسها من 0 إلى 127 ، كما تفعل اللاتينية -1 و Unicode من 0 إلى 255. أي 0x41 نقاط إلى "A" في ASCII ، LATIN-1 و UNICOD Latin-1 و Unicode ، 0xe9 يشير إلى "é" في اللاتينية -1 و Unicode.

عند العمل مع الأجهزة الإلكترونية ، تحتاج نقاط رمز Unicode إلى طريقة فعالة لتمثيلها إلكترونيًا. هذا ما تدور حوله مخططات الترميز. توجد مخططات تشفير مختلفة Unicode (UTF7 ، UTF-8 ، UTF-16 ، UTF-32). سيكون نهج الترميز الأكثر سهولة والمباشرة إلى الأمام هو ببساطة استخدام قيمة نقطة الكود في خريطة Unicode كقيمة لشكلها الإلكتروني ، ولكن لدى Unicode حاليًا أكثر من مليون نقطة رمز ، مما يعني أن بعضها يحتاج إلى 3 بايت ليكون أعربت. للعمل بكفاءة مع النص ، سيكون رسم الخرائط من 1 إلى 1 غير عملي إلى حد ما ، لأنه يتطلب تخزين جميع نقاط الكود بنفس القدر بالضبط من المساحة ، مع ما لا يقل عن 3 بايت لكل حرف ، بغض النظر عن حاجتها الفعلية.

معظم مخططات الترميز لها أوجه قصور فيما يتعلق بمتطلبات المساحة ، فإن أكثرها اقتصاديًا لا تغطي جميع نقاط رمز Unicode ، على سبيل المثال لا يغطي ASCII سوى 128 ، بينما يغطي اللاتيني 1 الأول 256. آخرون يحاولون أن يكونوا أكثر شمولاً في نهاية المطاف أيضًا أن تكون مهزلة ، لأنها تتطلب المزيد من البايتات أكثر من اللازم ، حتى بالنسبة للشخصيات "الرخيصة" الشائعة. UTF-16 على سبيل المثال ، يستخدم ما لا يقل عن 2 بايت لكل حرف ، بما في ذلك تلك الموجودة في نطاق ASCII ('B' الذي هو 65 ، لا يزال يتطلب 2 بايت من التخزين في UTF-16). UTF-32 أكثر إهدارًا لأنه يخزن جميع الأحرف في 4 بايت.

تصادف أن UTF-8 قد حلت بذكاء المعضلة ، مع مخطط قادر على تخزين نقاط الرمز مع كمية متغيرة من مسافات البايت. كجزء من استراتيجية الترميز الخاصة بها ، نقاط رمز UTF-8 Laces مع بتات العلم التي تشير (من المفترض إلى فك تشفير) متطلبات المساحة وحدودها.

UTF-8 ترميز نقاط رمز Unicode في نطاق ASCII (0-127):

0xxx xxxx  (in binary)
  • يُظهر X's المساحة الفعلية المخصصة لـ "تخزين" نقطة الكود أثناء الترميز
  • Leading 0 هو علامة تشير إلى وحدة فك ترميز UTF-8 بأن نقطة الرمز هذه لن تتطلب سوى بايت واحد.
  • عند الترميز ، لا يغير UTF-8 قيمة نقاط الكود في هذا النطاق المحدد (أي 65 المشفرة في UTF-8 هو أيضًا 65). بالنظر إلى أن Unicode و ASCII متوافقان أيضًا في نفس النطاق ، فإنه يجعل UTF-8 و ASCII متوافقان أيضًا في هذا النطاق.

EG Unicode Code Point for "B" هي "0x42" أو 0100 0010 في ثنائي (كما قلنا ، هو نفسه في ASCII). بعد الترميز في UTF-8 يصبح:

0xxx xxxx  <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010  <-- Unicode code point 0x42
0100 0010  <-- UTF-8 encoded (exactly the same)

UTF-8 ترميز نقاط رمز Unicode أعلى من 127 (غير ASCII):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • تشير البتات الرائدة "110" إلى وحدة فك ترميز UTF-8 ببداية نقطة رمز مشفرة في 2 بايت ، في حين تشير "1110" إلى 3 بايت ، 11110 تشير إلى 4 بايت وما إلى ذلك.
  • تُستخدم بتات العلم "10" الداخلية للإشارة إلى بداية بايت داخلي.
  • مرة أخرى ، حدد X's المساحة حيث يتم تخزين قيمة نقطة رمز Unicode بعد الترميز.

على سبيل المثال 'é' code code هي 0xe9 (233).

1110 1001    <-- 0xe9

عندما يقوم UTF-8 بتشفير هذه القيمة ، فإنه يحدد أن القيمة أكبر من 127 وأقل من 2048 ، وبالتالي ينبغي ترميزها في 2 بايت:

110x xxxx 10xx xxxx   <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001   <-- 0xe9
1100 0011 1010 1001   <-- 'é' after UTF-8 encoding
C    3    A    9

يصبح نقاط رمز Unicode 0xE9 بعد ترميز UTF-8 0xc3a9. وهو بالضبط كيف يستقبلها المحطة. إذا تم تعيين محطةك لفك تشفير الأوتار باستخدام Latin-1 (واحدة من الترميزات القديمة غير القديم) ، فسترى © ، لأنه يحدث فقط أن 0xc3 في نقاط Latin-1 إلى ã و 0xa9 إلى ©.

نصائح أخرى

عندما تتم طباعة أحرف Unicode إلى stdout ، sys.stdout.encoding يستخدم. من المفترض أن تكون شخصية غير unicode sys.stdout.encoding ويتم إرسالها للتو إلى المحطة. على نظامي (Python 2):

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding() يستخدم فقط عندما لا يكون للبيثون خيار آخر.

لاحظ أن Python 3.6 أو لاحقًا يتجاهل الترميزات على Windows ويستخدم Unicode APIs لكتابة Unicode إلى المحطة. لا يتم عرض أي تحذيرات UnicodeenCodeerror والعرض الصحيحة إذا كان الخط يدعمه. حتى لو كان الخط لا دعمها ، لا يزال من الممكن قطع الأحرف من المحطة إلى تطبيق مع خط داعم وسيكون صحيحًا. رفع مستوى!

يحاول Python استبداله لالتقاط ما هو الترميز لاستخدامه من بيئتك. إذا وجدت شيئًا عاقلًا ، فكل شيء يعمل فقط. إنه عندما لا يمكن معرفة ما يحدث.

>>> print sys.stdout.encoding
UTF-8

أنت لديك حدد ترميز عن طريق إدخال سلسلة Unicode صريحة. قارن نتائج عدم استخدام u بادئة.

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

في حالة ما اذا \xe9 ثم يفترض Python تشفيرك الافتراضي (ASCII) ، وبالتالي الطباعة ... شيء فارغ.

يعمل بالنسبة لي:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')

حسب بيثون الافتراضية/الترميزات الضمنية والتحويلات :

  • متي printعمل unicode, ، انها encodeد مع <file>.encoding.
    • عندما encoding لم يتم تعيينه ، unicode يتم تحويله ضمنيًا إلى str (منذ برنامج الترميز لذلك sys.getdefaultencoding(), ، بمعنى آخر ascii, ، أي شخصيات وطنية قد تسبب أ UnicodeEncodeError)
    • للتيارات القياسية ، encoding يتم استنتاجه من البيئة. عادة ما يتم تعيين fot tty تدفقات (من إعدادات لغة المحطة) ، ولكن من المحتمل ألا يتم تعيينها للأنابيب
      • لذلك أ print u'\xe9' من المحتمل أن ينجح عندما يكون الإخراج في محطة ، وفشل إذا تم إعادة توجيهه. الحل هو encode() السلسلة مع الترميز المطلوب من قبل printعمل.
  • متي printعمل str, ، يتم إرسال البايتات إلى الدفق كما هو. ما يعتمد عليه الرموز المعروضة على المحطة يعتمد على إعداداتها المحلية.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top