PyDA Course
مبتدئ موجّه ⏱ 60 دقيقة ⚡ +100 XP +15 نقطة لكل خطوة

منسق الأكواد

Developer ToolsCLI ToolsUtility

الكود الحقيقي يصل فوضويًا: مسافات بالية في نهايات الأسطر، و#comment دون مسافة، وسطران فارغان حيث يليق سطر واحد، ومسافات بادئة قفزت فوق قاعدة الأربعة. يبني هذا المشروع منسّق أكواد ، أداة طرفية صغيرة تقرأ ملف Python، وتطبّق فقط تطبيعات آمنة للمسافات والتعليقات، وتطوي الأسطر الفارغة المتتالية، وتتحقق من المسافات البادئة، وتطبع تقريرًا عمّا تغيّر بالضبط، وتكتب النسخة النظيفة في formatted.py. تقيّد نفسها عمدًا بالمسافات وتباعد التعليقات (لا تُعيد تسمية الكود أو تعيد ترتيبه أبدًا)، لذا تشغيلها لا يمكن أن يكسر البرنامج. مكتبة قياسية خالصة، وحتمية، وتصبح أمرًا حقيقيًا: python3 code_formatter.py messy.py.

هذا يفترض إلمامًا بإدخال/إخراج الملفات ودوال السلاسل الأساسية. إنه مشروع اختياري غير مُقيَّم ، راجع مشاريع من العالم الحقيقي للاطلاع على القائمة الكاملة والنامية.

🎯 ما ستفعله

  1. اقرأ عيّنة متسخة عمدًا واطبع “علاماتها القذرة”.
  2. شذّب المسافات البالية وطبّع #comment# comment.
  3. اطوِ الأسطر الفارغة المتتالية وافرض سطرًا نهائيًا.
  4. تحقق من المسافات البادئة مقابل خطوات الرباعية واطبع تحذيرات.
  5. اربطها كأمر code_formatter.py <file> مع تقرير قبل/بعد.

أين تُشغّل هذا

محليًا هو الموطن الطبيعي ، الأداة تستهدف ملفًا في دليلك الخاص.

mkdir code-formatter && cd code-formatter
touch code_formatter.py

Google Colab وKaggle Notebooks وBinder تشغّل أيضًا كل كتلة؛ في دفتر ملاحظات كنت ستستدعي الدوال مباشرة (format_source(...)) بدلًا من مسار sys.argv. الثوابت متماثلة في كل مكان.

Open In Colab Open In Kaggle Binder

الإعداد

صفر تبعيات؛ ملف مصدري واحد لتهاجمه.

أنشئ العيّنة المتسخة

mkdir code-formatter && cd code-formatter
touch code_formatter.py

انسخ هذا الملف باسم messy.py. المسافات البالية موجودة عن قصد ، لا تدع محرر نصوصك يزيلها قبل التجربة:

#sum module
def add(a,b):  #add two numbers
    """Add a and b."""

        return a + b   

def greet(name):
  msg = "hello " + name
   return msg   #too much indent

✅ قائمة التحقق

  • ✅ يحتوي messy.py على 10 أسطر، والمسافات البالية بعد #sum module و#add two numbers وreturn a + b ما زالت مرئية في محرر نصوص.
  • ✅ يعمل python3 code_formatter.py ولا يطبع شيئًا بعد (إنه ملف فارغ).

🤔 سؤال (أسئلة) سقراطي(ة)

  • بعض التحويلات آمنة (إزالة المسافات البالية لا تغيّر أبدًا ما يفعله البرنامج) وبعضها ليس كذلك (إعادة ترتيب الكود). لماذا يُعد “الآمن فقط” منسّقًا أوليًا جيدًا ، وما الذي سيكسره ملف مليء بالتعليقات بين الدوال لو أعدت ترتيب الأسطر؟
  • سطر العيّنة return msg #too much indent مذنب بثلاث جرائم دفعة واحدة. قبل كتابة أي كود، سمِّ الثلاث من الذاكرة.

الخطوة 1: اقرأ الملف وافحصه

أولًا يجب أن ترى الفوضى. تقرأ الخطوة 1 ملف messy.py وتبلّغ عن مواضع اتساخه.

1.1 اقرأ كل الأسطر

👟 تلميح البداية : open(...).read().splitlines() ، أسطر دون \n البالية، لذا كل مدخل محتوى خالص.

# code_formatter.py
import sys

def read_lines(path):
    with open(path) as f:
        return f.read().splitlines()

lines = read_lines("messy.py")
print("lines:", len(lines))
for i, ln in enumerate(lines, 1):
    print(f"{i:>2} |{ln}|")

يحتفظ splitlines() بـ محتوى كل سطر لكنه يلقي بالسطر الجديد، لذا يمكن للتقرير إظهار الحروف الدقيقة للسطر ، تصبح المسافات البالية مرئية في أغلفة |…|. أشرطة | مهمة: تجعل المسافات غير المرئية مقروءة.

🎯 الناتج المتوقع :

lines: 10
 1 |#sum module   |
 2 |def add(a,b):  #add two numbers   |
 3 |    """Add a and b."""|
 4 ||
 5 |        return a + b   |
 6 ||
 7 |   |
 8 |def greet(name):|
 9 |  msg = "hello " + name|
10 |   return msg   #too much indent|

🩹 إذا لم يعمل : إذا أظهر السطر 1 #sum module دون مسافات بائية، فقد شذّب محررك تلقائيًا ، أعد إنشاء messy.py بلصق/echo عادي. إذا أظهر السطر 7 لا شيء داخل | |، فسطر المسافات-فقط بقي على قيد الحياة ، جيد.

1.2 ابحث عن العلامات القذرة

👟 تلميح البداية : ثلاثة مسوحات: مسافات بائية، تعليقات ملتصقة بالنص، ومسافات بادئة غير رباعية.

# code_formatter.py (continued)
trailing = [i for i, ln in enumerate(lines, 1) if ln != ln.rstrip()]
print("trailing whitespace on:", trailing)

comment_lines = [i for i, ln in enumerate(lines, 1)
                 if "#" in ln and not ln.lstrip().startswith("#!")]
print("comment lines:", comment_lines)

indent_bad = []
for i, ln in enumerate(lines, 1):
    body = ln.lstrip(" ")
    if body and not body.startswith("#"):
        lead = len(ln) - len(ln.lstrip(" "))
        if lead % 4 != 0:
            indent_bad.append((i, lead))
print("indent warnings:", indent_bad)

ln != ln.rstrip() هو اختبار المسافات البالية ، يزيل rstrip المسافات من النهاية فقط، لذا أي اختلاف يعني قمامة بائية. تُقاس المسافات البادئة بعدّ المسافات الافتتاحية: يعطي lstrip(" ") الجسد، وlen(ln) - len(body) هي البادئة ، تُعلَّم عندما لا تكون مضاعفًا للعدد 4.

🎯 الناتج المتوقع :

trailing whitespace on: [1, 2, 5, 7]
comment lines: [1, 2, 10]
indent warnings: [(9, 2), (10, 3)]

🩹 إذا لم يعمل : إذا كان لأي قائمة بائية فهارس مختلفة، فقد فقدت نسختك من messy.py مسافاتها (انظر 1.1). إذا أظهر comment lines مدخلات مختلفة، تحقق من اختبار وجود # في السطر مقابل الملف الفعلي.

1.3 تحقّق من الفحص

✅ قائمة التحقق

  • ✅ قُرئت 10 أسطر؛ مسافات بائية على 1 و2 و5 و7 (السطر 7 مسافات-فقط).
  • ✅ توجد تعليقات على 1 و2 و10 ، الثلاثة ملتصقة بالنص بلا مسافة بعد #.
  • ✅ تحذيرات المسافات البادئة عند (9, 2) و(10, 3) ، تشغيل تحذير دون إعادة كتابة صامتة يُبقي الأداة صادقة.

🤔 سؤال (أسئلة) سقراطي(ة)

  • يحتوي السطر 7 على ثلاث مسافات ولا شيء آخر. هل isspace() كاشف أفضل لـ “فارغ” من == ""؟ أين يبدو سطر مليء بالتبويبات “فارغًا” تحت == "" لكن ليس تحت isspace()؟
  • يتجاهل indent_bad الأسطر التعليقية-فقط (startswith("#")). لماذا يجب أن يكون تعليق في العمود 3 قانونيًا حتى وإن لم يكن بيان في العمود 3 كذلك؟

الخطوة 2: شذّب وأصلح التعليقات

التنظيف الآمن: أزل المسافات البالية، ثم ضع مسافة بعد كل #.

2.1 الأدوات الطبيعية لكل سطر

👟 تلميح البداية : دالة fix_line تفرّغ الطرف ثم تصلح جزء التعليق بـ partition("#").

# code_formatter.py (continued)
def fix_line(ln):
    fixed = ln.rstrip()
    if "#" not in fixed or fixed.startswith("#!"):
        return fixed
    pre, _, comment = fixed.partition("#")
    comment = comment.strip()
    if comment == "":
        return pre.rstrip()
    if pre.strip() == "":
        return "# " + comment
    return pre.rstrip() + "  # " + comment

تقسيم partition("#") يُبقي الجانب الأيسر (الكود) منفصلًا عن التعليق، لذا كل جانب يُطبَّع بشكل مستقل. تعليق في العمود 0 ← # sum module؛ تعليق داخل سطر ← كود، مسافتان، # comment. يُترك #! (رأس سكربت shebang) وحده ، له اصطلاحه الخاص.

🎯 الناتج المتوقع : دالة، لا مخرجات بعد ، لكن فكّر ماذا تفعل بالسطر 10: return msg #too much indent return msg # too much indent.

2.2 طبّقها على الملف كاملًا

👟 تلميح البداية : اربط fix_line فوق كل الأسطر واطبع النتيجة.

# code_formatter.py (continued)
fixed = [fix_line(ln) for ln in lines]
for i, ln in enumerate(fixed, 1):
    print(f"{i:>2} |{ln}|")

🎯 الناتج المتوقع :

 1 |# sum module|
 2 |def add(a,b):  # add two numbers|
 3 |    """Add a and b."""|
 4 ||
 5 |        return a + b|
 6 ||
 7 ||
 8 |def greet(name):|
 9 |  msg = "hello " + name|
10 |   return msg  # too much indent|

🩹 إذا لم يعمل : إذا أصبح السطر 1 # sum module، فلن يعمل فرع العمود-0 (pre.strip() == "") ، تحقق أنك قسّمت قبل فحص pre. إذا ما زال # يلامس النص، فقد تُخطّي comment.strip() ولم تُدرج المسافة.

2.3 تحقّق من التنظيف

✅ قائمة التحقق

  • ✅ اختفت المسافات البالية من 1 و2 و5 و7.
  • #sum module# sum module؛ #add two numbers# add two numbers؛ #too much indent# too much indent.
  • ✅ يحتفظ الكود والتعليق بمسافتين بالضبط بينهما ، عقد pre.rstrip() + " # ".

🤔 سؤال (أسئلة) سقراطي(ة)

  • تعامل fix_line الجانب الأيسر ككود. ماذا سيحدث لسلسلة Python نصية تحوي # (s = "color #ff00aa")? هل يستحق منسّق مدرك للسلاسل التعقيد لأداة أولى ، وماذا يقول ذلك عن حدود “المجموعة الفرعية الآمنة”؟
  • يُستثنى #! بحالة خاصة. تعليقات سطر باش (#!، ##)، والسلسلة docstrings (""")، والسلاسل النصية المضمّنة كلها تفرط في تحميل #. أي قاعدة إبهام واحدة تُبقي منسّقًا مبتدئًا بعيدًا عن إفساد الملفات الصالحة؟

الخطوة 3: طيّ الأسطر الفارغة المتتالية وفرض سطر نهائي

الأسطر الفضائية-فقط والأسطر الفارغة المتكررة ضجيج تخطيطي. تشدّ الخطوة 3 منها.

3.1 سطر فارغ واحد في كل مرة

👟 تلميح البداية : امشِ فوق الأسطر المُصلحة، متجاهلًا أي سطر فارغ ("" أو مسافات-فقط) يلي سطرًا فارغًا آخر مباشرة.

# code_formatter.py (continued)
def collapse_blanks(lines):
    out = []
    for ln in lines:
        blank = ln.strip() == ""
        if blank and out and out[-1].strip() == "":
            continue
        out.append(ln)
    return out

collapsed = collapse_blanks(fixed)
print("lines after collapse:", len(collapsed))
for i, ln in enumerate(collapsed, 1):
    print(f"{i:>2} |{ln}|")

strip() == "" يسمّي سطرًا فارغًا سواء كان سطرًا خاويًا حقًا أو سطرًا مسافات-فقط ( ) ، كلاهما تخطيط، ولا يحمل أيٌّ منهما محتوى. الحارس out and out[-1].strip() == "" يُبقي فقط الأول من المتتالية، لذا يتطابق سطران فارغان+ إلى سطر واحد في كل مكان في مسح واحد.

🎯 الناتج المتوقع :

lines after collapse: 9
 1 |# sum module|
 2 |def add(a,b):  # add two numbers|
 3 |    """Add a and b."""|
 4 ||
 5 |        return a + b|
 6 ||
 7 |def greet(name):|
 8 |  msg = "hello " + name|
 9 |   return msg  # too much indent|

🩹 إذا لم يعمل : إذا ما زال السطر 7 يُطبع فارغًا، فإن سطر المسافات-فقط لم يُفرَّغ بـ strip() == "" ، لقد فُرّغ، ما لم يحمل السطر حروفًا غير مرئية غير مسافات. إذا تركت متتالية من ثلاثة سطرين فارغين، فحص الحارس السطر الخام ln بدلًا من السطر المُضاف أخيرًا.

3.2 السطر النهائي

👟 تلميح البداية : أعد الوصل بـ "\n" وأنهِ النص دائمًا بـ "\n".

# code_formatter.py (continued)
def build_text(lines):
    return "\n".join(lines) + "\n"

text = build_text(collapsed)
print("ends with newline:", text.endswith("\n"))
print("input bytes:", len(open("messy.py").read().encode()),
      "output bytes:", len(text.encode()))

يجب أن ينتهي آخر سطر في ملف بسطر جديد ، اصطلاح POSIX، والشيء الذي يضمنه join + "\n" حتى عندما نسي المصدر ذلك. عدّادات البايت فحص صحة سريع: التنظيف يُصغّر الملف (177 ← 166 بايتًا) لأن المسافات القمامة بايتات حقيقية.

🎯 الناتج المتوقع :

ends with newline: True
input bytes: 177 output bytes: 166

🩹 إذا لم يعمل : إذا كانت ends with newline: False، فقد وقع + "\n" قبل join. إذا كانت بايتات المخرجات أكبر، فقد أضافت تطبيع التعليقات مسافات أسرع مما أزال تشذيب البالية ، قِس بأمانة، هذا حكم الأداة.

3.3 تحقّق من الطيّ

✅ قائمة التحقق

  • ✅ 10 أسطر ← 9: اندمج سطر الفضائي-فقط مع السطر الفارغ فوقه.
  • ✅ بقي سطر فارغ واحد بالضبط بين أجسام الدوال.
  • ✅ ينتهي text بسطر جديد؛ المخرجات (166 بايتًا) أصغر من المدخلات (177).

🤔 سؤال (أسئلة) سقراطي(ة)

  • يضيف build_text \n واحدة للملف كاملًا. لماذا هي السطر الجديد الوحيد الذي يحتاج ذلك العدد ، وماذا سيفعل "\n".join(lines) دون السطر الجديد البالي بـ splitlines() في القراءة التالية؟
  • طيّ الأسطر الفارغة تحويل حيد (تشغيله مرتين لا يغيّر شيئًا في الثانية). لماذا الحيدية خاصية لطيفة لمنسّق ، وأي تحويل في هذا المشروع ليس حيدًا؟

الخطوة 4: تشخيص المسافات البادئة

المسافات البادئة في Python دلالات، لذا المنسّق يشخّص بدلًا من التخمين.

4.1 أصدر تحذيرات

👟 تلميح البداية : أعد تشغيل مسح عدّ البادئات واطبع كل سطر غير مضاعف للعدد 4 مع عدده الحالي من المسافات.

# code_formatter.py (continued)
print("INDENT WARNINGS")
for i, ln in enumerate(collapsed, 1):
    body = ln.lstrip(" ")
    if body and not body.startswith("#"):
        lead = len(ln) - len(ln.lstrip(" "))
        if lead % 4 != 0:
            print(f"  line {i}: {lead} spaces (should be a multiple of 4)")

يرفض المنسّق تخمين الإصلاح ، مسافتان في السطر 8 و3 في السطر 9 غامضتان (الاثنتان تتبعان def، لكن الأداة لا تعرف السياق)، لذا تعرضهما لعين المطوّر.

🎯 الناتج المتوقع :

INDENT WARNINGS
  line 8: 2 spaces (should be a multiple of 4)
  line 9: 3 spaces (should be a multiple of 4)

🩹 إذا لم يعمل : إذا سمّت التحذيرات أسطرًا مختلفة، فللقائمة المطوية مواضع مختلفة عن messy.py ، التقرير عن النص الحالي. إذا لم يُطبع شيء، فإن lstrip(" ") على سطر مُبطّن بتبويب يخفي البادئة (انظر سقراطي أدناه).

4.2 عقد التبويب

👟 تلميح البداية : حوّل أي متتاليات تبويب موجودة إلى كتل رباعية وسجّل هل وُجد أيٌّ منها.

# code_formatter.py (continued)
has_tabs = any("\t" in ln for ln in collapsed)
print("tabs found in source:", has_tabs)

\t محظور في العيّنة (وعادة في مصدر Python حسب PEP 8). الفحص هو any(...) واحد فوق الأسطر؛ إذا وُجد، كان .expandtabs(4) سيعيد كتابتها ، لكن بما أن messy.py لا يحوي أيًّا، فالجواب المطبوع False، وتبقى قصة التبويب عقدًا موثقًا بدلًا من تحويل خفي.

🎯 الناتج المتوقع : tabs found in source: False

🩹 إذا لم يعمل : إذا طُبع True، فقد اكتسبت نسختك تبويبًا في مكان ما ، قرّر: أبقِه تشخيصيًا (أبلغ عن السطر) أو وسّعه بـ .expandtabs(4)، وعدّل محاسبة المسافات لاحقًا.

4.3 تحقّق من التشخيص

✅ قائمة التحقق

  • ✅ تسمّي التحذيرات السطرين 8 (مسافتان) و9 (3 مسافات) ، كلاهما بيانان، لا تعليقان.
  • tabs found in source: False.
  • ✅ لم يُكتب شيء في هذه الخطوة ، التشخيص للقراءة فقط بتصميم.

🤔 سؤال (أسئلة) سقراطي(ة)

  • سطر مُبطَّن بتبويب يفشل lstrip(" ") بصمت (بادئته غير مرئية). ما التغيير الواحد الذي يجعل التشخيص يلتقط التبويبات أيضًا ، وأي عرض تبويب (4 مقابل 8) كانت ستفترضه قاعدة % 4؟
  • def في العمود 0، وجسمه في 4، والأجسام المتداخلة في 8. بهذه الحقائق الثلاث، هل توجد أي قاعدة لا لبس فيها “لإصلاح” المسافات البادئة لسطر مُبطَّن ، أم التحذير هو المنتج الصحيح هنا؟

الخطوة 5: احفظ النتيجة واجعلها أداة CLI

تحتاج الأداة مخرج ملف وبابًا أماميًا لسطر الأوامر.

5.1 اكتب formatted.py

👟 تلميح البداية : اكتب build_text(...) عائدة عبر open(..., "w") وأعد القراءة لإثبات أن الرحلة ذهابًا وإيابًا تكتمل.

# code_formatter.py (continued)
with open("formatted.py", "w") as f:
    f.write(text)

again = open("formatted.py").read()
print("formatted.py lines:", len(again.splitlines()))
print("round-trip identical:", again == text)

إبقاء النتيجة على القرص يجعل الأداة مفيدة ، يبقى messy.py كالعينة، وformatted.py هو النسخة النظيفة. إعادة القراءة والمقارنة == text هي نفس انضباط الرحلة الخالية من الخسارة الذي ستستخدمه في أي خط أنابيب: اكتب، أعد القراءة، أكّد التساوي.

🎯 الناتج المتوقع :

formatted.py lines: 9
round-trip identical: True

🩹 إذا لم يعمل : إذا قالت الرحلة False، فقد تغيّر معالجة "\n" الزائدة أو المسافات البالية ، قارن بـ repr(text) مقابل repr(again).

5.2 الموجّه (المُوزِّع)

👟 تلميح البداية : اقرأ sys.argv[1] كاسم الملف، ونسّقه، واطبع الملخص قبل/بعد.

# code_formatter.py (continued)
def format_file(path):
    lines = open(path).read().splitlines()
    fixed = [fix_line(ln) for ln in lines]
    collapsed = collapse_blanks(fixed)
    text = build_text(collapsed)
    with open("formatted.py", "w") as f:
        f.write(text)
    trailing = [i for i, ln in enumerate(lines, 1) if ln != ln.rstrip()]
    print(f"{path}: {len(lines)} -> {len(collapsed)} lines; "
          f"{len(trailing)} trailing-whitespace fixes; "
          f"see formatted.py")

if __name__ == "__main__":
    format_file(sys.argv[1])

خط الأنابيب كاملًا ، قراءة، إصلاح، طيّ، وصل، كتابة، تلخيص ، أصبح الآن دالة واحدة لمسار ملف. sys.argv[1] يحوّله إلى CLI: اكتب python3 code_formatter.py messy.py والأداة تحرّر من سطر الأوامر.

🎯 لنشغّله :

python3 code_formatter.py messy.py

🎯 الناتج المتوقع :

messy.py: 10 -> 9 lines; 4 trailing-whitespace fixes; see formatted.py

🩹 إذا لم يعمل : إذا ظهر IndexError، فكان sys.argv[1] مفقودًا (شغّله مع اسم الملف). إذا كانت العدادات معطلة عن 10←9 و4، فأعاد format_file قراءة formatted.py موجود مسبقًا ، اعمل دائمًا على ملف العيّنة.

5.3 تحقّق من الـ CLI

✅ قائمة التحقق

  • ✅ يكتب python3 code_formatter.py messy.py ملف formatted.py (9 أسطر) ويطبع الملخص.
  • ✅ تتفق أرقام الملخص مع الخطوات السابقة: 10←9 أسطر، 4 إصلاحات بائية.
  • messy.py بلا مساس (مدخل للقراءة فقط) ، الأداة لا تعيد كتابة المصدر أبدًا.

🤔 سؤال (أسئلة) سقراطي(ة)

  • تكتب format_file إلى اسم ثابت formatted.py. التشغيل الثاني يكتب فوق المخرج الأول. هل تفضّل f"formatted_{path}" أو علمًا --out ، وما الحجة لـ عدم الكتابة فوق ملف المصدر مباشرة؟
  • هذا المنسّق مسافات-فقط اليوم. إذا أضفت تحويلًا آخر (مثلًا سطر فارغ بعد كل def دالة)، ما الاختبار الذي يُثبت أنه لا يكسر أبدًا معنى messy.py ، وماذا تعني “لا تغيّر المعنى أبدًا” حتى لـ Python الحساسة للمسافات؟

⚠️ المآزق الشائعة

  • splitlines مقابل read().split("\n"). يتجاهل splitlines() العنصر الفارغ الأخير الذي يُنتجه تقسيم "\n" ساذج ، لتنتهي بسطر فارغ زائف.
  • lstrip() يجرّد التبويبات أيضًا. عدّ المسافات البادئة بـ len(ln) - len(ln.lstrip()) يعدّ المسافات والتبويبات كحرف واحد؛ استخدم lstrip(" ") أو مسحًا مدركًا للتبويبات. (يفحص هذا المشروع التبويبات بشكل منفصل.)
  • طيّ الفراغات على القائمة الخاطئة. الطيّ قبل التشذيب يعني أن السطر الفضائي-فقط ( ) يتصرف كـ محتوى ولا يندمج أبدًا مع الفراغ فوقه. الترتيب: شذّب ← أصلح ← اطوِ.
  • partition مقابل split. يُبقي partition("#") القطع الثلاث كلها (pre, ”#”, post)؛ كان split("#") ليعامل تعليقًا يحوي # بشكل خاطئ أو يدمر الحد عند أول فاصل.
  • تباعد التعليقات على السلاسل النصية. s = "#ff00aa" يحوي # داخل سلسلة نصية حرفية ، منسّق مسافات-فقط يعيد كتابتها بسعادة. حدود “المجموعة الفرعية الآمنة” درعك؛ وثّقها.
  • الكتابة فوق العيّنة. إعادة كتابة messy.py تدمر الشيء الذي تقيسه. أخرج إلى formatted.py؛ أبقِ المدخل للقراءة فقط.

ما بنيته للتو

منسّق أكواد عامل بواجهة CLI حقيقية: فحص أسطر بأشرطة |…| مرئية، وتشذيب مسافات بائية وتباعد تعليقات # عبر أدوات السلاسل المدمجة، وكسح أحادي حيد لطي الفراغات، وضمان سطر نهائي بدليل عدّ بايتات، وتشخيص مسافات بادئة للقراءة فقط بخطوات رباعية، وفحص عقد التبويبات، وكاتب formatted.py يعيد الرحلة مطابقة بايت-بمثيل. تحت السطح، الأنماط قابلة لإعادة الاستخدام في أي مكان: قِس المواضع القذرة قبل التطبيع، طبّق تحويلات آمنة قابلة للعكس فقط، اجعل كشف الفراغ مدركًا للمسافات (strip() == "")، شخّص بدلًا من التخمين حين يكون الإصلاح غامضًا، وأبقِ المدخل للقراءة فقط بينما تُخرِج النتيجة منفصلة.

شغّل نسخة أكمل دون أي إعداد محلي

يحوي examples/code-formatter/ في مستودع الدورة المنسّق كاملًا كدفتر ملاحظات ، فحص، وإصلاحات، وطيّ، وتشخيص، وCLI، قابل للتشغيل في Colab/Kaggle/Binder. استنسخ المستودع أو افتحه في Codespace.

إلى أين تذهب من هنا

  • شغّله على ملف حقيقي خاص بك: python3 code_formatter.py some_script.py واقرأ ما يبلّغ عنه.
  • أضف معالجة .expandtabs(4) حتى تتحول الملفات المُبطّنة بتبويبات في نفس التشغيل، مع سطر Tabs converted: N.
  • اجعل تسمية المخرج ذكية: formatted_<basename> بدلًا من اسم ثابت، أو علمًا --check يطبع التقرير فقط دون كتابة ملف (صديق للـ CI).
  • قارن مع الشيء الحقيقي: شغّل Black (pip install black) على نفس العيّنة وقارن formatted.py بمخرجات Black ، درس متواضع في كم أعمق يذهب منسّق حقيقي.

شارك مشروعك مع الصف

بنيت شيئًا فخورًا به؟ examples/student-projects/ معرض لمشاريع طلاب آخرين قدَّموها ، وملف README الخاص به يحتوي شرحًا كاملًا وودودًا للمبتدئين لإضافة مشروعك عبر pull request، حتى لو لم تستخدم git من قبل قط: عمل fork للمستودع، وإنشاء فرع، وتثبيت ملفاتك، وفتح الـ PR، خطوة بخطوة. لا يُفترَض أي خبرة سابقة بـ git.

مرحبًا بك في كتابة Python خارج المتصفح. 🎓

أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.