قد يعمل سكربت بايثون جيدًا مع النصوص الإنجليزية، ثم يتوقف فور محاولة حفظ كلمة عربية أو طباعتها برسالة UnicodeEncodeError: 'charmap' codec can't encode characters. هل المشكلة في الحروف العربية نفسها؟ أم في إعدادات Windows؟ الإجابة تعتمد على الترميز المستخدم عند إخراج النص، لا على لغة النص وحدها.
في هذا الدرس من سلسلة مشكلة وحل 35 ستتعلم تحديد الموضع الذي يثير الاستثناء: كتابة ملف TXT، أو تصدير CSV، أو طباعة محتوى في الطرفية. سننتج الخطأ عمدًا، ونصلحه باستخدام UTF-8، ثم نختبر مشروعًا صغيرًا يحفظ نصوصًا عربية ويتأكد من عدم فقدان حرف واحد.
{alertInfo}قبل البدء: يعتمد الدرس على مكتبات بايثون القياسية فقط، ولا يحتاج إلى pip install. اختبارنا نُفذ على Python 3.13.5 في بيئة Linux؛ سنشرح سلوك Windows استنادًا إلى توثيق Python، دون الادعاء أننا اختبرناه على كل أجهزة Windows.أداة التشخيص، وتجارب قبل الإصلاح وبعده، وملفات TXT وCSV عربية، و21 اختبارًا تلقائيًا وتعليمات التشغيل.
تحميل مشروع بايثون ZIP{getToc} $title={محتوى المقال}
ما معنى خطأ UnicodeEncodeError: charmap في بايثون؟
عندما تكتب str إلى ملف أو مخرج نصي، تحتاج بايثون إلى تحويل الأحرف إلى بايتات باستخدام ترميز (Encoding). إذا استُخدم ترميز لا يستطيع تمثيل حرف موجود في النص، وبدأ البرنامج بمعالجة الأخطاء بالنمط الصارم strict، يظهر UnicodeEncodeError.
text = "مرحبا من بايثون"
text.encode("cp1252")
هذه تجربة مقصودة لا تعتمد على نظام التشغيل: طلبنا من بايثون ترميز النص العربي باستخدام cp1252 الذي لا يمثل حروفه. لاحظ أنه قد يظهر اسم الترميز داخل الاستثناء charmap، حتى لو كتبنا cp1252 في الكود.
UnicodeEncodeError: 'charmap' codec can't encode characters ...
حذفنا تفاصيل الموضع من السطر أعلاه لأن مؤشر position يختلف بحسب النص المدخل. في المشروع المرفق نلتقط الاستثناء ونختبر نوعه دون الاعتماد على موضع حرف بعينه.
الفرق بين UnicodeEncodeError وUnicodeDecodeError
| الاستثناء | متى يحدث؟ | مثال |
|---|---|---|
UnicodeEncodeError | تحويل نص str إلى بايتات باستخدام ترميز لا يدعم بعض الأحرف | محاولة حفظ العربية باستخدام cp1252 |
UnicodeDecodeError | تحويل بايتات إلى str بترميز غير مناسب | قراءة بيانات UTF-8 باعتبارها ASCII |
text = "مرحبا"
try:
text.encode("cp1252")
except UnicodeEncodeError:
print("Encoding error")
try:
text.encode("utf-8").decode("ascii")
except UnicodeDecodeError:
print("Decoding error")
Encoding error
Decoding error
إذا ظهر لك UnicodeDecodeError أثناء القراءة فلا تطبق حلًا مخصصًا لمخرجات print() دون تحديد المشكلة. لدينا بالفعل دروس عن التعامل مع أخطاء قراءة الملفات؛ أما هذه الصفحة فتركز على فشل ترميز الإخراج.
قراءة تفاصيل الاستثناء لمعرفة الحرف الذي فشل ترميزه
لا تقفز مباشرةً إلى تغيير إعدادات النظام. تقرأ أولًا اسم الترميز ورسالة Traceback والسطر الذي حدث عنده الفشل. يمكن استكشاف موقع الأحرف غير المدعومة عبر خصائص الاستثناء encoding وstart وend وreason:
try:
"مرحبا".encode("cp1252")
except UnicodeEncodeError as error:
print("Encoding:", error.encoding)
print("Start:", error.start)
print("End:", error.end)
print("Reason:", error.reason)
Encoding: charmap
Start: 0
End: 5
Reason: character maps to <undefined>
القيم start وend تخص مواضع المحارف داخل النص الذي حاولت ترميزه، وليس رقم سطر في الملف. تتغير مع النص؛ والهدف هنا فهم كيفية التشخيص، لا افتراض أن كل خطأ يبدأ عند الحرف الأول.
لماذا يظهر الخطأ مع العربية تحديدًا؟
نصوص بايثون الداخلية تدعم Unicode، فلا تحتاج إلى تحويل العربية إلى رموز غريبة كي تتعامل معها. المشكلة تنشأ عند استخدام ASCII أو بعض ترميزات Windows القديمة، مثل cp1252، التي لا تحتوي أحرفًا عربية. أما UTF-8 فيمكنه تمثيل العربية ومعظم لغات العالم والرموز التعبيرية.
text = "مرحبا من بايثون"
encoded = text.encode("utf-8")
restored = encoded.decode("utf-8")
print(restored == text)
print(restored)
True
مرحبا من بايثون
تذكّر أن UTF-8 لا يصلح مشكلة الخطوط غير الداعمة للعربية أو اتجاه النص في واجهة المستخدم. ظهور حروف منفصلة أو معكوسة قد يكون مشكلة عرض، حتى إن لم يظهر أي استثناء ترميز.
الحل الأول: كتابة نص عربي في ملف TXT باستخدام UTF-8
إذا كان الخطأ عند write()، فابدأ بمراجعة open() أو Path.open(). حدد encoding="utf-8" صراحةً عند الكتابة والقراءة، ولا تعتمد على الترميز الافتراضي للجهاز.
from pathlib import Path
text = "مرحبًا من بايثون — أهلاً بالعالم 👋"
file_path = Path("arabic.txt")
with file_path.open("w", encoding="utf-8") as file:
file.write(text)
with file_path.open("r", encoding="utf-8") as file:
restored = file.read()
print(restored == text)
True
{alertSuccess}يؤكد الاختبار تطابق النص المقروء مع النص الأصلي حرفًا بحرف. إذا كانت طرفيتك لا تدعم طباعة العربية، يمكنك التحقق من المساواة دون طباعة النص العربي نفسه.
كيف نحل UnicodeEncodeError عند كتابة ملفات CSV؟
مكتبة csv لا تختار ترميز الملف بدلًا منك. مرر الترميز إلى open()، واستخدم newline="" كما توصي وثائق Python عند فتح ملف للكتابة أو القراءة بواسطة csv.
import csv
rows = [
["name", "city"],
["أحمد", "عدن"],
["ليلى", "صنعاء"],
]
with open("people.csv", "w", encoding="utf-8", newline="") as file:
csv.writer(file).writerows(rows)
with open("people.csv", "r", encoding="utf-8", newline="") as file:
restored = list(csv.reader(file))
print(restored == rows)
True
الاختبار يثبت سلامة البيانات داخل CSV. أما طريقة ظهورها عند فتح الملف بالنقر المزدوج في Microsoft Excel فتعتمد على إصدار Excel وإعدادات الاستيراد والمنطقة؛ ليست كل مشكلة عرض في Excel من نوع UnicodeEncodeError.
متى نستخدم utf-8-sig بدل utf-8؟
إذا كنت تستهدف سير عمل معيّنًا لفتح CSV في Excel ويحتاج إلى علامة UTF-8 BOM للتعرف على الترميز، استخدم utf-8-sig عند الكتابة. لا تحتاجها كل التطبيقات، وقد تكون غير مرغوبة عند تبادل الملفات مع أدوات تتوقع CSV بلا BOM.
import csv
with open("people-excel.csv", "w", encoding="utf-8-sig", newline="") as file:
csv.writer(file).writerows(["name,city".split(","), ["أحمد", "عدن"]])
with open("people-excel.csv", "r", encoding="utf-8-sig", newline="") as file:
rows = list(csv.reader(file))
print(rows[1][0] == "أحمد")
True
لمن يريد شرحًا أوسع للتعامل مع CSV، ارجع إلى درس قراءة وكتابة CSV في بايثون بدل تكرار أساسيات المكتبة هنا.
لماذا ينجح حفظ الملف بينما يفشل print()؟
الكتابة إلى ملف باستخدام open(..., encoding="utf-8") تختلف عن الطباعة باستخدام print(). الأولى تعتمد على الترميز الذي اخترته للملف، والثانية تكتب إلى sys.stdout، الذي قد يكون Terminal أو ملفًا معاد توجيه الإخراج إليه أو Pipe أو نافذة يقدّمها محرر الأكواد.
في المشروع محاكاة آمنة لخطأ الطباعة داخل تدفق نصي افتراضي بترميز cp1252. لا نغير إعدادات طرفيتك الحقيقية أثناء التجربة:
from io import BytesIO, TextIOWrapper
buffer = BytesIO()
with TextIOWrapper(buffer, encoding="cp1252", errors="strict") as stream:
print("مرحبا", file=stream)
السطر الأخير يثير UnicodeEncodeError. وإذا غيّرنا الترميز إلى utf-8 تنجح الكتابة إلى هذا التدفق الافتراضي. هذا لا يضمن أن كل Terminal خارجي سيعرض الحروف باتجاهها الصحيح؛ يجب التمييز بين ترميز البايتات وعرضها.
تشخيص ترميز stdout وstderr وLocale قبل تغيير أي إعداد
قبل استخدام أي حل خاص بـWindows أو VS Code، افحص البيئة التي يعمل فيها السكربت. انسخ الكود التالي كما هو:
import locale
import sys
print("Python:", sys.version.split()[0])
print("stdout:", sys.stdout.encoding)
print("stderr:", sys.stderr.encoding)
print("locale:", locale.getencoding())
print("UTF-8 mode:", sys.flags.utf8_mode)
print("Is terminal:", sys.stdout.isatty())
مثال من تشغيلنا الفعلي على Python 3.13.5 في بيئة Linux ذات UTF-8 Mode مفعل:
Python: 3.13.5
stdout: utf-8
stderr: utf-8
locale: UTF-8
UTF-8 mode: 1
Is terminal: False
قد تكون قيمك مختلفة. locale.getencoding() يعرض ترميز Locale ولا يمثل دائمًا الترميز الذي يستخدمه open() عند تفعيل UTF-8 Mode. افحص أيضًا إن كانت المخرجات موجهة إلى ملف أو إلى نافذة طرفية.
حل مشكلة الطباعة في Windows وCMD وPowerShell وVS Code
الخطوة الصحيحة هي معرفة أين يعترض الترميز النص. يمكن تجربة أحد الحلول التالية بحسب نتيجة التشخيص، بدل تشغيل مجموعة أوامر دون فهم سبب المشكلة:
- للملفات النصية: استخدم
encoding="utf-8"فيopen()، فهذا الحل لا يتوقف على صفحة ترميز CMD. - لمخرجات
stdout: عند توفر دالةreconfigure()يمكنك تجربةsys.stdout.reconfigure(encoding="utf-8")قبل الطباعة، مع إدراك أن بعض البيئات لا تدعمها وأن جهة استقبال البايتات يجب أن تفهم UTF-8. - لمخرجات البرامج المعاد توجيهها: جرّب إعداد
PYTHONIOENCODING=utf-8قبل تشغيل بايثون، أو فعّل UTF-8 Mode عند الحاجة. تأثير المتغير على Windows console له استثناءات موثقة. - داخل VS Code أو IDE: تأكد هل الإخراج في Terminal فعلي أم نافذة Output تملك إعداداتها الخاصة. لا تفترض أن تغيير
chcpسيغير ترميز الملفات المكتوبة بـopen().
مثال reconfigure عند دعمه
import sys
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
print("مرحبا من بايثون")
{alertWarning}لا تستخدمreconfigure()كإصلاح عام داخل مكتبة يعيد الآخرون استخدامها؛ فقد يكونstdoutمستبدلًا بكائن لا يدعمها، وقد يتوقع المستهلك ترميزًا مختلفًا. وعندما تكون المشكلة في ملف خاص بالبرنامج، عالج الملف نفسه بدل تغيير المخرجات القياسية.
استخدام PYTHONIOENCODING وPython UTF-8 Mode بصورة صحيحة
يمكن تعيين PYTHONIOENCODING قبل تشغيل البرنامج. مثال مؤقت في CMD:
set PYTHONIOENCODING=utf-8
python script.py
ومثال PowerShell:
$env:PYTHONIOENCODING = "utf-8"
python script.py
كما تستطيع تشغيل سكربت باستخدام UTF-8 Mode بصورة صريحة:
python -X utf8 script.py
هذه الأوامر تخص بيئة التشغيل ولا تستبدل encoding="utf-8" عند إنشاء ملفات تريد ترميزها بصورة محددة. وقد لا يؤثر PYTHONIOENCODING على جهاز Windows Console بالطريقة المعتادة، إلا في الظروف الموثقة الخاصة بـPYTHONLEGACYWINDOWSSTDIO. لا ننصح بتفعيله عشوائيًا.
هل تغيّر سلوك الترميز في Python 3.15؟
نعم. قبل Python 3.15، كان ترميز open() الافتراضي يعتمد غالبًا على Locale عندما لا يكون UTF-8 Mode مفعّلًا. أما في Python 3.15 فقد أصبح UTF-8 Mode هو الافتراضي، ولذلك أصبحت open() تستخدم UTF-8 في الوضع الافتراضي. وإذا عُطّل UTF-8 Mode فقد يعود السلوك إلى الترميز الخاص بالمنطقة.
| إصدار Python | الترميز الافتراضي للملفات النصية | الملاحظة |
|---|---|---|
| 3.13 أو 3.14 | يعتمد على Locale ما لم يُفعَّل UTF-8 Mode | لا تفترض أن Windows يختار UTF-8 دائمًا |
| 3.15 | UTF-8 افتراضيًا مع UTF-8 Mode الجديد | قد يظهر الخطأ إذا طُلب ترميز محدود صراحةً أو اختلفت إعدادات الإخراج |
حتى مع الإصدار الحديث، يظل اختيار encoding="utf-8" صراحةً عادة جيدة للملفات التي تتحكم أنت بصيغتها. تفاصيل هذا السلوك موثقة في توثيق io الرسمي وPEP 686. لم نشغّل اختباراتنا على Python 3.15؛ المقارنة بين الإصدارات هنا من التوثيق الرسمي.
عند استخراج نص عربي من Beautiful Soup: أين يقع الخطأ؟
أحيانًا تظهر رسالة الترميز أثناء مشروع Web Scraping. قد تكون Beautiful Soup قد استخرجت النص بصورة صحيحة، بينما يفشل البرنامج في print() أو file.write() بعد ذلك. افحص السطر الذي أشار إليه Traceback قبل إلقاء اللوم على مكتبة الاستخراج.
# افترض أن النص التالي جاء من عنصر HTML:
article_text = "أخبار التقنية باللغة العربية"
with open("article.txt", "w", encoding="utf-8") as file:
file.write(article_text)
with open("article.txt", encoding="utf-8") as file:
print(file.read() == article_text)
True
هذا المثال متعمد بدون تثبيت beautifulsoup4؛ لأن سبب الخطأ هنا هو مرحلة الإخراج وليس تحليل HTML. إذا تعطل print(article_text) بينما نجحت الكتابة والتحقق من الملف، فراجع إعدادات stdout بدل تغيير نص HTML بطريقة قد تفقده أحرفه.
حلول خاطئة ينبغي تجنبها عند ظهور UnicodeEncodeError
{alertWarning}errors="ignore"وerrors="replace"لا يعالجان عدم دعم الترميز للأحرف؛ بل قد يحذفانها أو يستبدلانها بعلامة?. لا تستخدمهما لحفظ معلومات مهمة مثل أسماء العملاء أو نصوص المقالات.
text = "مرحبا"
print(text.encode("cp1252", errors="ignore"))
print(text.encode("cp1252", errors="replace"))
b''
b'?????'
كذلك لا تفترض أن إضافة تعليق # -*- coding: utf-8 -*- أعلى ملف Python ستغيّر ترميز الملف الذي تنشئه بواسطة open()؛ فهو يحدد ترميز الملف المصدري نفسه، لا وجهة الإخراج النصي.
مشروع عملي: حفظ نص عربي في TXT وCSV والتحقق من سلامته
أعددنا مشروعًا صغيرًا اسمه unicode_lab.py يجمع أربع مهام: محاكاة فشل الترميز بصورة آمنة، وتشخيص الإخراج، وحفظ ملف TXT عربي، وإنشاء ملفَي CSV عادي وبصيغة BOM مناسبة لبعض سيناريوهات Excel، ثم قراءتها والتحقق من التطابق.
يحتوي المشروع على دوال simulate_encoding_failure() وsimulate_stdout_failure() وsave_txt() وsave_csv() وrun_demo(). لتنفيذ المشروع من مجلده:
python unicode_lab.py simulate
python unicode_lab.py diagnose
python unicode_lab.py demo --output-dir output
المخرجات الفعلية لأمر simulate:
UnicodeEncodeError: charmap (expected)
UnicodeEncodeError: charmap (expected)
UnicodeDecodeError: ascii (expected)
أول ثلاثة أسطر من أمر demo بعد تشغيله فعليًا:
TXT round-trip: OK
CSV UTF-8 round-trip: OK
CSV UTF-8 BOM round-trip: OK
تظهر بعدها المسارات التي حُفظت فيها ملفات arabic-utf8.txt وarabic-utf8.csv وarabic-excel.csv. هذه المسارات تتغير حسب المكان الذي تشغّل منه السكربت؛ ولهذا لم نثبت مسارات الجهاز داخل المثال.
أهم دالتين في المشروع
from pathlib import Path
import csv
def save_txt(path, text):
path = Path(path)
with path.open("w", encoding="utf-8") as output:
output.write(text)
def save_csv(path, rows, excel_bom=False):
encoding = "utf-8-sig" if excel_bom else "utf-8"
with Path(path).open("w", encoding=encoding, newline="") as output:
csv.writer(output).writerows(rows)
اختصرنا الدالتين هنا لتوضيح الفكرة؛ النسخة المرفقة تتضمن إنشاء المجلدات المستهدفة ودوال قراءة الملفات، إضافةً إلى اختبار عودة النص والبيانات كما كُتبت تمامًا.
تشغيل اختبارات unittest والتأكد من عدم فقدان أحرف
شغّل اختبارات المشروع من مجلده دون تثبيت pytest:
python -m unittest discover -s tests -v
نفّذنا 21 اختبارًا على Python 3.13.5، وشملت حالات الفشل المتعمد، والقراءة والكتابة، وBOM، وإنشاء المجلدات، وتشغيل واجهة الأوامر، وفقدان الأحرف عند استخدام ignore وreplace. انتهت الاختبارات فعليًا بهذه الخلاصة:
Ran 21 tests in 2.119s
OK
أزمنة الاختبارات تتغير بين الأجهزة؛ القيمة أعلاه تخص التشغيل الذي أجريناه. توفر الحزمة ملف test-results.txt للنتائج، ويمكن للقارئ إعادة التنفيذ على نظامه.
جدول سريع: حدد مكان الخطأ ثم اختر العلاج
| المشكلة الظاهرة | ما الذي تفحصه أولًا؟ | العلاج المناسب |
|---|---|---|
الخطأ عند file.write() | قيمة encoding عند فتح الملف | كتابة وقراءة UTF-8 صراحةً |
| الخطأ أثناء كتابة CSV | ترميز open() وnewline | utf-8 مع newline="" |
| العربية تظهر غير صحيحة في Excel | إعدادات استيراد CSV ووجود BOM | جرّب استيراد UTF-8 أو utf-8-sig حسب البرنامج |
الخطأ عند print() | sys.stdout.encoding ونوع المخرج | تشخيص STDOUT أولًا، ثم معالجة ترميزه عند الحاجة |
الخطأ عند decode() | ترميز البايتات الأصلي | استخدام Decoder المناسب؛ هذا UnicodeDecodeError |
| حروف منفصلة دون استثناء | الخط والاتجاه ومحرك العرض | حل مشكلة العرض منفصل عن الترميز |
اتبع رسالة Traceback دائمًا؛ لا تستنتج سبب الخطأ من وجود العربية في المشروع فقط.
مصادر موثوقة ودروس مرتبطة
- Python Docs: Text Encoding — اختيار ترميز الملفات و
reconfigure(). - Python Docs: stdin/stdout/stderr — اختلاف طرفيات Windows وملفات الإخراج.
- Python Docs: csv — ضرورة
newline="". - PEP 686 — اعتماد UTF-8 Mode افتراضيًا في Python 3.15.
- شرح try وexcept | أساسيات بايثون 31 — فهم الاستثناءات.
- التحقق من سلامة ملف باستخدام SHA-256 — موضوع مرتبط بسلامة المحتوى، لكنه مختلف عن الترميز.
الأسئلة الشائعة حول UnicodeEncodeError في بايثون
هل encoding="utf-8" يحل خطأ UnicodeEncodeError دائمًا؟
لا. يصلح غالبًا حالة كتابة ملف نصي تتحكم أنت بترميزه، لكنه لا يغير تلقائيًا ترميز sys.stdout أو متطلبات نظام خارجي يطلب ترميزًا آخر.
ما الفرق بين UTF-8 وUTF-8-SIG عند كتابة CSV؟
utf-8-sig يكتب علامة BOM في بداية الملف؛ وقد تساعد بعض إصدارات أو طرق فتح Excel في التعرف على UTF-8، لكنها ليست مطلوبة دائمًا. يستطيع قارئ utf-8-sig في بايثون التعامل مع الملف سواء وجدت BOM أم لا.
هل بايثون 3.15 يمنع الخطأ تلقائيًا؟
لا. تغيّر الترميز الافتراضي للملفات النصية مع UTF-8 Mode، لكن يمكن أن تستمر المشكلة إذا طلب برنامجك ترميزًا محدودًا صراحةً أو كانت وجهة الإخراج لا تدعم الأحرف.
لماذا يعمل النص العربي داخل المتغير ولا يُحفظ في الملف؟
لأن تمثيل النص داخل str لا يساوي الترميز المطلوب لإخراجه كبايتات. افحص open(..., encoding=...) أو المخرج الذي تكتب إليه.
هل استخدام errors="ignore" آمن؟
لا إذا أردت الحفاظ على النص كاملًا. قد يحذف الأحرف غير القابلة للتمثيل بصمت. اختر ترميزًا يدعم المحتوى بدل إخفاء الاستثناء.
هل المشكلة في Beautiful Soup إذا ظهر الخطأ بعد استخراج نص صفحة عربية؟
ليس بالضرورة. تحقق من موضع الخطأ؛ قد ينجح استخراج النص تمامًا ويفشل برنامجك فقط عند طباعته أو حفظه. افصل بين التحليل والطباعة والكتابة في اختبارك.



