مؤقت التأمل
للجلوس في تأمل موقوت مشكلة واحدة لا يُسمح لأي تطبيق في العالم بإصلاحها ، اهتزاز الهاتف، والإعلانات، وإلحاح السلسلة قبل أن تغمض عينيك حتى. مؤقت الطرفية لا يملك شيئًا من ذلك: موجه عادي، وعدّ تنازلي، وجرس خفيف، وتكرار. يبني هذا المشروع CLI صغيرًا يعدّ تنازليًا لجلسة، ويُطَنّ في كل فاصلة (بحيث لا تتفحص الساعة)، ويرشد دورة تنفس مربّع 4-4-4-4، ويسجل كل جلسة بهدوء بحيث يمكنك رؤية سلسلتك تنمو دون أي حكم على أيام الراحة.
يفترض هذا إنهاء Python 101 ، الحلقات والدوال وقراءة وكتابة الملفات والتعامل الأساسي مع التواريخ. لا شيء أبعد من ذلك: لا واجهة رسومية، ولا ويب، ولا خدمات خارجية. هذا اختياري وغير مُقيَّم؛ راجع مشاريع من العالم الحقيقي للاطلاع على القائمة الكاملة.
🎯 ما ستفعله
- العدّ تنازليًا لجلسة تأمل من دقائق إلى إشارة لطيفة «شهيق / زفير».
- إطلاق جرس (جرس الطرفية) كل N دقيقة بحيث لا تفتح عينيك للتحقق أبدًا.
- إرشاد دورة تنفس مربّع حيث لكل طور عدّه التنازلي الخاص.
- تسجيل كل جلسة منجزة في CSV بالتاريخ والمدة وملاحظة مزاج بسطر واحد.
- قراءة السجل وإعادة الإبلاغ عن سلسلتك الحالية وإجمالي دقائق التأمل.
- التعامل مع 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 في عشر دقائق تأمل رديء). استخدم الدفتر لتعلم الآلية؛ وشغّل الأمر فعلًا عندما تريد للمؤقت أن يوقّت شيئًا.
الإعداد
كل ما تحتاجه قبل جلستك الأولى: 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:00→04: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… 4 → Hold… 4 → Exhale… 4 → Hold… 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 خارج المتصفح. 🎓
أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.