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

مؤقت التأمل

clitimefile-iogamification

للجلوس في تأمل موقوت مشكلة واحدة لا يُسمح لأي تطبيق في العالم بإصلاحها ، اهتزاز الهاتف، والإعلانات، وإلحاح السلسلة قبل أن تغمض عينيك حتى. مؤقت الطرفية لا يملك شيئًا من ذلك: موجه عادي، وعدّ تنازلي، وجرس خفيف، وتكرار. يبني هذا المشروع CLI صغيرًا يعدّ تنازليًا لجلسة، ويُطَنّ في كل فاصلة (بحيث لا تتفحص الساعة)، ويرشد دورة تنفس مربّع 4-4-4-4، ويسجل كل جلسة بهدوء بحيث يمكنك رؤية سلسلتك تنمو دون أي حكم على أيام الراحة.

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

🎯 ما ستفعله

  1. العدّ تنازليًا لجلسة تأمل من دقائق إلى إشارة لطيفة «شهيق / زفير».
  2. إطلاق جرس (جرس الطرفية) كل N دقيقة بحيث لا تفتح عينيك للتحقق أبدًا.
  3. إرشاد دورة تنفس مربّع حيث لكل طور عدّه التنازلي الخاص.
  4. تسجيل كل جلسة منجزة في CSV بالتاريخ والمدة وملاحظة مزاج بسطر واحد.
  5. قراءة السجل وإعادة الإبلاغ عن سلسلتك الحالية وإجمالي دقائق التأمل.
  6. التعامل مع Ctrl+C بأناقة بحيث يحسب الخروج أثناء الجلسة دقائق اليوم ، تمامًا كتطبيق سلسلة يصفح.

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

محليًا باستخدام uv هو المسار الأساسي ، وبصراحة الوحيد الذي تهبط فيه سحر المؤقت، لأن الأجراس وإشارات التنفس تحتاج إما time.sleep حقيقية مقابل طرفية حية أو على الأقل ساعة حائط حقيقية لتبدو مؤقتًا. كل شيء أدناه يعمل جيدًا أيضًا على أي من المسارات الثلاثة للدفاتر، لكن مؤقت تأمل في دفتر مثل بندول الإيقاع في جدول بيانات: الآلية موجودة، والنقطة ليست.

GitHub Codespaces هو نفسه المحلي ، افتح codespaces.new/abderrahim-lectures/python-data-analysis-course، وuv run python meditate.py --minutes 1 قصيرة تجري جلسة دقيقة واحدة حقيقية صادقة في نافذة الطرفية.

Google Colab وKaggle Notebooks وBinder طريقة معقولة لرؤية الكود يعمل ، منطق العدّ التنازلي ودورة التنفس وسجل CSV وحساب السلسلة كلها تتنفذ فعليًا ، لكن الدفتر يشغّل كل خطوة كلقطة مرئية سريعة بدلًا من تجربة زمن حقيقي منقضٍ (خلية time.sleep في عشر دقائق تأمل رديء). استخدم الدفتر لتعلم الآلية؛ وشغّل الأمر فعلًا عندما تريد للمؤقت أن يوقّت شيئًا.

Open In Colab Open In Kaggle Binder

الإعداد

كل ما تحتاجه قبل جلستك الأولى: uv، ومجلد يحمل المشروع.

ثبّت uv وهيئ المشروع

macOS / Linux (الطرفية):

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

أغلق طرفيتك وأعد فتحها، ثم:

uv --version
mkdir meditation-timer && cd meditation-timer
uv init --bare

صفر حزم إضافية ، هذا المشروع مكتبة قياسية نقية (time وdatetime وcsv).

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

  • uv --version يطبع رقم إصدار.
  • ✅ يوجد meditation-timer/ مع pyproject.toml من uv init --bare.
  • ✅ يمكنك تشغيل uv run python -c "import time, datetime; print('time is real')" ورؤية الرسالة.
  • ✅ تعرف ما هو جرس الطرفية (على معظم الأنظمة هو \a في سلسلة، ويرن أو يومض). سنسمعه في الخطوة 2.

الخطوة 1: عدّ تنازليًا لجلسة بسيطة

المؤقت مجرد حلقة فوق الثواني المتبقية لديك، تطبع الوقت المتبقي وتنام ثانية واحدة لكل نقرة. النموذج الذهني هنا هو الذي ستعيد استخدامه للاستراحات والفترات ودورة التنفس: قرّر كم تدوم الأشياء، ثم دع الحلقة تستنزف تلك المدة نقرة بنقرة.

1.1 اكتب مؤقتًا يعدّ الدقائق

# meditate.py
import time

def countdown(minutes: int) -> None:
    total = minutes * 60
    print(f"Session: {minutes} min — begin. 🧘")
    for remaining in range(total, 0, -1):
        print(f"\r{remaining // 60:02d}:{remaining % 60:02d} left", end="", flush=True)
        time.sleep(1)
    print("\r00:00 left — done. Enjoy the quiet. 🙏")

if __name__ == "__main__":
    countdown(1)  # start with one real minute

يمشي range(total, 0, -1) نزولًا من الثواني الكاملة إلى 1، وكل تكرار يطبع الوقت المتبقي ثم يغلق time.sleep(1). عودة المحرك \r (مع end="" وflush=True) تعيد كتابة السطر نفسه في مكانه بدلًا من طباعة 60 سطرًا، و// مع % تقسمان الثواني إلى MM:SS لساعة تقرأ كمؤقت حقيقي.

👟 تلميح البداية :

شغّل countdown(1) واجلس عبر الدقيقة الكاملة ، ستشعر لماذا يتفوق \r على 60 سطرًا مطبوعة، ولماذا time.sleep ثانية واحدة داخل الحلقة هي نبضة القلب كلها للمؤقت.

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

سطر واحد يعرض 01:00 left، وينقر نزولًا في مكانه إلى 00:00 left، ثم يطبع رسالة الانتهاء ، بعد 60 ثانية كاملة.

🩹 إذا لم يعمل :

إذا طُبع 60 سطرًا بدلًا من سطر واحد مُعاد كتابته، فإن ثلاثية \r/end=""/flush=True ليست حاضرة كلها ، عودة المحرك وحدها (دون flush=True) غالبًا لا تعيد الرسم داخل المخرجات الملتقطة. إذا انتهى العدّ التنازلي فورًا، فقد وُضع time.sleep(1) خارج الحلقة ، يجب أن تنقر في كل تكرار.

1.2 تحقّق من العدّ التنازلي

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

  • ✅ يستغرق countdown(1) 60 ثانية حقيقية ويرسم سطر MM:SS واحدًا يصل إلى 00:00.
  • ✅ يهيئ countdown(5) الصيغة 05:0004:59 → … دون تخطي ، حساب // 60/% 60 مستقر.
  • ✅ يطبع countdown(0) رسالة الانتهاء فورًا (جلسة صفر صادقة للغة).

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

  • تنام الحلقة ثانية واحدة بالضبط لكل نقرة لكن الطباعة أيضًا تأخذ وقتًا، لذا يتجاوز الزمن المنقضي الفعلي دائمًا minutes * 60 قليلًا. أين يتراكم الانحراف ، وما إعادة الكتابة المبنية على time.monotonic() (احسب الموعد النهائي، ثم sleep حتى يصل) التي تُبقي مؤقت 10 دقائق صادقًا للثانية؟
  • بعد الحلقة، المتبقي 0 ، لكنك لا ترى أبدًا تكرار 00:00، بل رسالة ما بعد الحلقة فقط. أي تغيير واحد يجعل سطر 00:00 حقيقيًا يُطبع جزءًا من تقدم الحلقة بدلًا من أن يكون رسالة الإنهاء؟

الخطوة 2: جرِّس في كل فاصلة

مؤقت تأمل لا يستطيع أجراس الفواصل ليس مؤقت تأمل ، النقطة كلها إشارة في نقاط ثابتة بحيث لا تتحقق من الساعة في منتصف الجلسة. في طرفية، «الجرس» هو الحرف المتواضع \a (BEL): في معظم الطرفيات يرن أو يومض بشكل غير مرئي، وحتى حيث لا يفعل، فإن bell() عبر print("\a", end="") هو البدائي نفسه الذي تستمد منه التطبيقات الكبيرة الصوت تحت الغطاء.

2.1 أضف أجراس الفواصل

# meditate.py (continued)

def countdown_with_bells(minutes: int, bell_every: int = 5) -> None:
    total = minutes * 60
    print(f"Session: {minutes} min, bell every {bell_every} min — begin. 🧘")
    for remaining in range(total, 0, -1):
        el = total - remaining                        # seconds elapsed since the start
        if el % (bell_every * 60) == 0 and el > 0:    # exactly on an interval boundary
            print(f"\n   +bell at {el // 60:02d}:00   ({remaining // 60:02d}:{remaining % 60:02d} left)\a",
                  flush=True)
        print(f"\r{remaining // 60:02d}:{remaining % 60:02d} left", end="", flush=True)
        time.sleep(1)

if __name__ == "__main__":
    countdown_with_bells(2, bell_every=1)  # 2 minutes, chime at the 1-minute mark

السطر الذي يجعل الأجراس تعمل هو el % (bell_every * 60) == 0 ، الثواني المنقضية معامل الفاصلة، بحيث يُطلق الجرس بالضبط كل bell_every دقيقة (وأبدًا عند 0 بسبب el > 0). يركب \a على نهاية سطر الجرس بحيث يكون جزءًا من الثانية المطبوعة، لا كتابة غير مرئية منفصلة، وel يُعاد حسابه في كل نقرة من العدّ التنازلي بحيث يبقى الفحص صحيحًا مهما أُعيد ترتيب الحلقة لاحقًا.

👟 تلميح البداية :

ابدأ بـ bell_every=1 على جلسة قصيرة ، دقيقتان، جرس واحد ، وتأكد أن الجرس (رنين أو وميض) يهبط عند علامة 1:00، لا عند 0:59 أو 1:01.

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

جلسة تظهر فيها بالضبط عند دقيقة منقضية واحدة سطر جرس (مع رنين/وميض طرفية)، ويستمر العدّ التنازلي، وتصل رسالة الانتهاء في الأخير.

🩹 إذا لم يعمل :

إذا لم يُطلق الجرس أبدًا، فـ el % (bell_every * 60) == 0 يقارن بالفاصلة الخاطئة ، تحقق أنك ضربت bell_every في 60، لا قارنت بـ bell_every نفسه. إذا رنّ الجرس كل نقرة، فحارس el > 0 مفقود ، بدونه يجعل el == 0 في أول نقرة 0 % anything == 0، بحيث يُطلق جرس «صفر منقضٍ» فورًا.

2.2 تعامل مع مقاطعة بأناقة

# meditate.py (continued)

def countdown_forgiving(minutes: int, bell_every: int = 5) -> None:
    total = minutes * 60
    done = 0.0
    try:
        print(f"Session: {minutes} min — begin. 🧘")
        for remaining in range(total, 0, -1):
            el = total - remaining
            if el % (bell_every * 60) == 0 and el > 0:
                print(f"\n   +bell at {el // 60:02d}:00\a", flush=True)
            print(f"\r{remaining // 60:02d}:{remaining % 60:02d} left", end="", flush=True)
            time.sleep(1)
            done = float(el + 1)
    except KeyboardInterrupt:
        pass
    finally:
        print(f"\n— interrupted or finished after {done:.0f}s ({done / 60:.1f} min) —")

if __name__ == "__main__":
    countdown_forgiving(2, bell_every=1)

شكل try/finally هو نمط الخروج الأنيق: يرفع Ctrl+C خطأ KeyboardInterrupt، ويلتهمه except، وتعمل كتلة finally دائمًا للإبلاغ عن الثواني المكتملة فعلًا. لا يُحدَّث done إلا بعد اكتمال كل ثانية حقيقية، بحيث يحسب المقاطع أثناء النوم النقرات المكتملة ولا يكذب أبدًا عن «0 دقيقة منجزة» عندما فعلت 42 فعلًا.

👟 تلميح البداية :

شغّل countdown_forgiving(5, bell_every=2)، وانتظر نحو 15 ثانية، ثم اضغط Ctrl+C ، يجب أن تبلغ الطرفية عن واقعية بأسلوب 158s / 2.6 min، لا تعقّب ولا 0s` مزيفًا.

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

ضغط Ctrl+C في منتصف الجلسة يطبع سطرًا نظيفًا واحدًا يخبرك بعدد الثواني التي فعلتها فعلًا ، لا تعقّب ولا نصف إعادة رسم ، ويضمن finally طباعة ذلك السطر حتى لو هبطت المقاطعة بالضبط على نقرة جرس.

🩹 إذا لم يعمل :

إذا أظهر Ctrl+C تعقّبًا، فإما except KeyboardInterrupt مفقود أو موضوع على try الخاطئ ، يجب أن يغلف الحلقة، لا sleep فقط. إذا كانت الثواني المُبلَّغ عنها خاطئة، فdone يُحدَّث قبل اكتمال النوم ، ارفعه فقط بعد مرور ثانية كاملة.

2.3 تحقّق من الأجراس

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

  • ✅ يطلق countdown_with_bells(2, 1) سطر جرس مسموعًا واحدًا بالضبط عند علامة الدقيقة.
  • ✅ يبلغ countdown_forgiving عن ثوانٍ مكتملة حقيقية عند مقاطعته ، أبدًا تعقّب، أبدًا كذبة مقرّبة لأعلى.
  • ✅ لا يُطلق أي جرس إطلاقًا في النقرة الأولى جدًا (يثبت حارس el > 0).
  • ✅ يمكنك شرح لماذا el منقضٍ ، محسوب كـ total - remaining ، لا remaining فقط.

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

  • باستخدام time.sleep(1) لكل نقرة، يعمل فحص الجرس مرة واحدة في الثانية كحد أقصى. حسن ، لكن ما الانحراف الواقعي الذي سيُدخله sleep من 30 ثانية لو «حسّنت» الحلقة بالطريقة تلك، وما الذي سيظل el يلتقطه صحيحًا؟
  • يطبع الجرس سطره الخاص، ممسحًا \r العدّ التنازلي لإطار واحد. ما خلل ترتيب العرض بين «اطبع سطر الجرس» و«أعد رسم العدّ التنازلي» الذي قد تكشفه رسالة أطول ، وكيف سيبقى مخرجاتك ناجية منه؟

الخطوة 3: أرشد دورة تنفس مربّع

التنفس المربّع إيقاع ثابت ، شهيق 4ث، تثبيت 4ث، زفير 4ث، تثبيت 4ث، تكرار ، وهو نفس حلقة العدّ التنازلي من الخطوة 1 بفكرة إضافية واحدة: بدلًا من مؤقت واحد يستنزف مرة، تكرر الدورة تسلسلًا ثابتًا من الأطوار، مطبوعة إشارة منطوقة لكل طور أثناء تشغيله.

3.1 شغّل دورة الأطوار الأربعة

# breathe.py
import time

BOX = [("Inhale", 4), ("Hold", 4), ("Exhale", 4), ("Hold", 4)]

def breath_cycle(rounds: int = 3, phase_seconds: int = 4) -> None:
    print("Box breathing: In 4 - Hold 4 - Out 4 - Hold 4. Begin. 🌬️")
    for r in range(rounds):
        for name, secs in BOX:
            for left in range(secs, 0, -1):
                print(f"\r{' ' * 20}  {name}{left}", end="", flush=True)
                time.sleep(1)
    print(f"\r{' ' * 20}  Complete — {rounds} rounds. 👌")

if __name__ == "__main__":
    breath_cycle(rounds=2)

قائمة الأزواج BOX هي التقنية: الحلقة الداخلية عبر (name, secs) تحوّل أربعة أطوار مشفرة إلى بيانات، بحيث يكون أسلوب 4-4-8 (تنفس الرياضيين بالزفير الطويل) تغيير بيانات بسطر واحد بدلًا من تغيير كود. الحشوة ذات الثماني مسافات ' ' * 20 في كل \r تمنع أسماء الأطوار القصيرة («Inhale») من ترك أحرف شبح من أطول منها («Complete»).

👟 تلميح البداية :

شغّل breath_cycle(rounds=1) ، جولة 16 ثانية واحدة ، وأدِّ التنفس فعلًا؛ ستلاحظ أن الإشارة تغيّر الطور بالضبط على شبكة الثانية، وهي التجربة كلها التي أتاحتها حلقة الخطوة 1.

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

جلستان من Inhale… 4Hold… 4Exhale… 4Hold… 4، كل طور يعدّ رقمًا واحدًا في كل ثانية، منتهيًا بـ Complete ، 2 rounds.

🩹 إذا لم يعمل :

إذا نزفت أسماء الأطوار بعضها في بعض (Exhale… 3Exhale… 2)، فحشوة \r قصيرة جدًا أو مفقودة ، احشِ إلى طول أطول رسالة على الأقل. إذا تخطّت الأنفاس رقمًا، فالحلقة الداخلية range(secs, 0, -1) معكوسة (جرّب -1 مقابل 1) أو time.sleep(1) يتخطاه continue شارِد.

3.2 تحقّق من مرشد التنفس

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

  • ✅ يستغرق breath_cycle(1, 4) 16 ثانية ويعرض أربعة أطوار متميزة، كلٌّ يعدّ 4→1 في مكانه.
  • ✅ يطابق التسلسل BOX: شهيق → تثبيت → زفير → تثبيت، أبدًا زفير → شهيق.
  • ✅ تشغيل breath_cycle(2) يضاعف الزمن الإجمالي دون تكرار نص الإعداد ، الإعداد يُطبع مرة، والأطوار تتكرر.

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

  • BOX بيانات والحلقات كود. إذا أردت دورة مبنية على نسب مثل 4-7-8، فما أصغر تعديل على BOX ، وماذا يقول ذلك عن ترميز التقنية كبيانات مقابل إعادة تجميع المنطق؟
  • تُطبع الإشارة قبل الثانية التي تمثلها («Inhale… 4» تعني «اسكن في الثواني الأربع القادمة»). أين يتسع sleep في تلك الصياغة ، وهل ستقرأ طباعة 0 في نهاية كل طور أفضل أم أسوأ لنفَس فعلي؟

الخطوة 4: سجّل جلسة في CSV

متعقب عادات ينسى هو لعبة؛ قيمة السلسلة كلها في تأمل تأتي من ملف ينمو. تجعل الخطوة 4 كل جلسة منجزة (أو مقاطعة بأناقة) صفًا: التاريخ والدقائق ومزاج كلمة واحدة. اختير CSV لأنه ملف يمكن لإنسان فتحه في أي جدول بيانات ودقّقه ، كود تسجيل لا يمكنك قراءته بالعكس أسرع أنواع فقدان الثقة.

4.1 ألصق صف جلسة

# log.py
from datetime import date
import csv
from pathlib import Path

LOG = Path("sessions.csv")

def log_session(minutes: int, mood: str = "ok") -> None:
    header = ["date", "minutes", "mood"]
    exists = LOG.exists()
    with open(LOG, "a", newline="") as f:
        writer = csv.writer(f)
        if not exists:
            writer.writerow(header)
        writer.writerow([date.today().isoformat(), minutes, mood])

def show_log() -> None:
    with open(LOG, newline="") as f:
        for row in csv.reader(f):
            print(f"{row[0]}{row[1]:>3} min — {row[2]}")

if __name__ == "__main__":
    log_session(5, "calm")
    log_session(2, "restless")
    show_log()

القراران اللذان يحملان هذه الخطوة: يُكتب الرأس فقط عندما يكون الملف جديدًا (إعادة تشغيل السكربت تلحق الصفوف بدلًا من مضاعفة الرأس)، وdate.today().isoformat() يخزن التواريخ كـ YYYY-MM-DD ، صيغة تُفرز صحيحًا كسلسلة عادية، وهو ما ستعتمد عليه حسابات سلسلة الخطوة 5 بشدة.

👟 تلميح البداية :

شغّل log.py مرتين. التشغيل الأول: صفّان زائد الرأس جديدان. التشغيل الثاني: الصفان نفسيهما مُلحَقان مجددًا، بلا رأس ثانٍ ، ذلك معنى اجتياز اختبار الإلحاق مقابل الرأس.

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

تشغيل log.py مرتين يطبع في التشغيل الثاني سجلًا نظيفًا من صفّي بيانات (4 أسطر في التشغيل الأول، و4 أسطر مجددًا في الثاني ، لا 6)، كل صف YYYY-MM-DD ، N min ، mood.

🩹 إذا لم يعمل :

إذا عاد الرأس للظهور في التشغيل الثاني، فقد حُسب exists بعد فتح الملف (الذي ينشئه) ، احسبه قبل open(LOG, "a"). إذا أظهرت الصفوف قمامة بأسلوب manual override، فسطر جديد ختامي شارِد في CSV يقسم صفًا واحدًا إلى صفين ، تحقق أن الملف ينتهي بسطر جديد واحد، لا صف فارغ.

4.2 تحقّق من التسجيل

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

  • ✅ الإلحاق مرتين يضيف صفين ولا رأسًا مكررًا أبدًا.
  • ✅ كل صف date,minutes,mood بتواريخ ISO ، يمكنك فتح sessions.csv في جدول بيانات وقراءته.
  • ✅ يعيد show_log عرض الملف سطرًا فسطرًا، حتى بعد تشغيل جديد.

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

  • نسجل التاريخ فقط، لا وقت اليوم، بحيث لا يستبدل جلستان في يوم واحد شيئًا (صفّان، نفس التاريخ). هل تريد الأداة دمج دقائق نفس اليوم؟ وماذا ستُفضل حسابات السلسلة في الخطوة 5 ، وماذا يكشف إجابتك عن «عميل» نموذج البيانات؟
  • وضع append يكتب سطرًا جديدًا في النهاية. إذا شغّلت الأداة على جهازين (حاسوب محمول وهاتف)، فما الكارثة على مستوى نظام الملفات التي يخاطر بها سجل إلحاقي مشترك ، وما الإصلاح الرخيص (تلميح: اقرأ الملف، وادمج، وأعد كتابته)؟

الخطوة 5: احسب سلسلتك

السجل موجود ليُقرأ بالعكس؛ والسلسلة هي القراءة المعكوسة التي تجعلك تحضر غدًا. تحلل هذه الخطوة الـ CSV وترتب التواريخ وتجد أطول امتداد من الأيام المتتالية وأيضًا الامتداد الحالي المنتهي اليوم ، الرقمان اللذان يعرضهما كل متعقب عادات. الفكرة الأساسية أن «المتتالية» مجرد حساب تواريخ: كل تاريخ هو السابق زائد يومًا واحدًا.

5.1 حلّل واحسب السلسلة

# streak.py
from datetime import date, timedelta
import csv
from pathlib import Path

LOG = Path("sessions.csv")

def session_dates(path: Path = LOG) -> list[date]:
    days = set()
    with open(path, newline="") as f:
        for row in csv.reader(f):
            if row and row[0].lower() != "date":
                days.add(date.fromisoformat(row[0]))
    return sorted(days)

def best_streak(days: list[date]) -> tuple[int, date]:
    best = 0
    run = 0
    end = None
    prev = None
    for d in days:
        run = run + 1 if prev is None or (d - prev).days == 1 else 1
        if run > best:
            best, end = run, d
        prev = d
    return best, end

if __name__ == "__main__":
    days = session_dates()
    print(f"{len(days)} meditated day(s) on record")
    print(f"longest streak: {best_streak(days)[0]} days")

تعريف السلسلة يعيش في سطر واحد: (d - prev).days == 1 ، يستمر اليوم في امتداد فقط عندما يكون بالضبط اليوم التقويمي التالي بعد السابق. set() في البداية يزيل ازدواج جلستي اليوم نفسه، وsorted() يضمن أن الحلقة تمشي التواريخ دائمًا بترتيب متزايد بغض النظر عن ترتيب إلحاق الـ CSV.

👟 تلميح البداية :

اكتب بطارية سريعة من قوائم التواريخ الصغيرة (["2026-08-03","2026-08-04","2026-08-05"] يجب أن تكون best == 3) قبل توجيه الدالة إلى جلسات حقيقية ، دالة السلسلة هي حيث تختبئ منزاحات الواحد، وثوابت مفحوصة باليد تجدها أسرع.

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

مع عينة الخطوة 4 (08-03، 08-05 ، فجوة يوم واحد)، المخرج 2 meditated day(s) on record وlongest streak: 1 days، لأن التاريخين غير متتاليين.

🩹 إذا لم يعمل :

إذا أبلغ امتداد ثلاثة أيام عن 2، ففحص == 1 محوَّل خطأً (مثلًا (d - prev).days >= 1 يشمل الفجوات ، يجب أن يكون بالضبط 1). إذا لوّث سطر رأس الـ CSV المجموعة، فحارس row[0].lower() != "date" مفقود أو أن الرأس ليس date؛ اطبع أول تواريخ محللة قليلة لترى.

5.2 تحقّق من السلسلة

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

  • ✅ يُرجع best_streak على قوائم مبنية يدويًا أطول امتداد صحيح (3 لثلاثة أيام متتالية، و1 لتاريخين متباعدين يومًا).
  • ✅ أسطر الرأس لا تصبح أبدًا تواريخ جلسات.
  • ✅ جلستان في نفس التاريخ تعدّان يومًا واحدًا ، احتفاظ set بالتصفية يثبت.
  • ✅ يمكنك شرح ما إذا كان هذا يعدّ سلسلة انتهت أمس مساويةً لتلك ما زالت جارية اليوم.

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

  • يُرجع best_streak أطول امتداد إطلاقًا لكن ليس الامتداد «هل أنا في سلسلة الآن؟» الحالي. ما الفحص الإضافي، مقارنةً بآخر تاريخ مع date.today()، الذي يحوّل best إلى «الحالي» ، وماذا يجب أن يبلغ عندما يكون اليوم يوم راحة لكن أمس تأملت؟
  • يعامل تعريف السلسلة أي فجوة كإعادة تعيين. تطبيقات العادات الحقيقية تصفح يومًا مفتقدًا واحدًا (أي «زلّة» مقابل «انتكاسة»). كيف سيتغير الفحص ليصفح فجوة واحدة ، وماذا يفعل ذلك بمعنى الرقم؟

⚠️ مآزق شائعة

  • نسيان التفويت (flush). يعمل العدّ التنازلي محليًا لكنه يبدو مجمدًا في سجلات ملتقطة أو دفاتر لأن print(..., end="") يخزّن مؤقتًا. flush=True على كل كتابة \r ليس اختياريًا عندما يكون المخرج مبثوثًا أو ملتقطًا.
  • المنزاح بواحد في فحص الجرس. يُطلق el % interval == 0 عند 0، ما يُنزل «الجرس الأول» نقرة مبكرة ، حارس el > 0 هو الإصلاح كله، وحذفه يجعل جلسة دقيقة تصفق بغرابة في الثانية صفر.
  • تخطي try/finally عند المقاطعات. مؤقت يموت بتعقّب على Ctrl+C يبلّغ كذبًا («فعلت 0 دقيقة») ويفسد قصة سجل الجلسات. finally الذي يطبع دائمًا الثواني المكتملة هو ما يجعل الخروج ليس فشلًا.
  • العدّ المزدوج لنفس التاريخ في السلاسل. تشغيل المؤقت مرتين في يوم يلحق صفين، ومسح ساذج يعدّ «يومين» ، منفوخ السلسلة باثنين لصفر أيام إضافية. إزالة ازدواج التواريخ (set) قبل أي حساب أيام متتالية.
  • سطر رأس يصبح تاريخًا. يبدأ الـ CSV بـ date,minutes,mood، ويرفع date.fromisoformat("date") خطأً ، محوّلًا لقطة سجل تافهة إلى انهيار. إما تخطَّ الرأس في القارئ (حارس != "date") أو افصل صفوف البيانات عن الرؤوس؛ اختر واحدًا وكن متسقًا.
  • انحراف time.sleep في الجلسات الطويلة. كل نقرة ثانية زائد حمولة الطباعة يدفع جلسة 30 دقيقة ما بعد 30:00. للتأمل هذا غير ذي صلة عمليًا؛ إذا وقّتت يومًا ما بشكل أدق، فحلقة موعد نهائي مطلق بـ time.monotonic() تزيل الانحراف ، وتبقى sleep(1) سعيدة في هذه الأثناء.

ما بنيته للتو

مؤقت تأمل حقيقي: عدّ تنازلي يرسم سطرًا نظيفًا واحدًا، وأجراس فاصلة لا تحتاج للتحقق منها أبدًا، ومرشد تنفس مربّع، وسجل جلسات CSV إلحاقي، وحساب سلسلة صادق يمجّد. المهارة القابلة للنقل هي مجموعة أدوات حلقة الأحداث في طرفية كلها: دفاتر المنقضي مقابل المتبقي، ونقر time.sleep التعاوني، والمقاطعة الأنيقة، والحالة المدعومة بملفات ، البدائيات بالضبط خلف كل مؤقت فاصلة ومراقب إنجاز وتطبيق عدّ تنازلي ومتعقب عادات ستكتبه من هنا فصاعدًا. جلسة صديقك من 10 دقائق هي 600 ثانية صادقة، معاد رسمها بالطباعة، ورنّها الجرس حين يجب، بعيدًا.

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

examples/meditation-timer/ في مستودع الدورة يحزم المؤقت ومرشد التنفس ووحدتي السجل والسلسلة زائد دفتر يشغّل كل قطعة كصورة حضرة مرئية. استنسخه، أو افتح المستودع كله في GitHub Codespace، وشغّل الخلايا في نافذة متصفح.

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

  • أضف سطر السلسلة الحالية: تحقق من آخر تاريخ مقابل date.today() وأبلغ «أنت في يوم N» ، متغير best_streak إضافي واحد، وهو الرقم الذي ستتحقق منه فعلًا يوميًا.
  • أضف تقريرًا أسبوعيًا: اجمع الدقائق حسب أسبوع ISO واطبع مخطط أعمدة صغيرًا بـ ascii ▁▃▅▇ ، عادة التجميع نفسها من السجل، الآن عبر 8 أسابيع دُرفلية.
  • أضف أوامر تاريخ (--last 7، --since 2026-08-01) تصفّي الـ CSV قبل حساب السلسلة، مبنية على قراءة csv نفسها التي تثق بها بالفعل.
  • استبدل BOX الثابت بعلم --technique ، box و478 وlong-exhale ، كلٌّ قائمة أزواج مختلفة؛ الحلقات لا تتغير، البيانات تتغير.

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

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

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

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