جدول المحتويات

  • الشروع في العمل: طباعة قيمة متغير
  • تعبيرات الطباعة
  • يخطو من خلال التعليمات البرمجية
    إدراج رمز المصدر
  • استخدام نقاط التوقف
  • استمرار التنفيذ
  • عرض التعبيرات
  • معرف المتصل بيثون
  • أوامر pdb الأساسية
  • تصحيح أخطاء بايثون باستخدام PDB: الخاتمة

يمكن أن تكون تطبيقات تصحيح الأخطاء أحيانًا نشاطًا غير مرغوب فيه. أنت مشغول بالعمل في ظل أزمة زمنية وتريدها فقط أن تعمل. ومع ذلك ، في أوقات أخرى ، قد تتعلم ميزة لغة جديدة أو تجرب نهجًا جديدًا وتريد أن تفهم بشكل أعمق كيف يعمل شيء ما.

بغض النظر عن الموقف ، يعد تصحيح الأخطاء أمرًا ضروريًا ، لذلك من الجيد أن تكون مرتاحًا عند العمل في مصحح أخطاء. في هذا البرنامج التعليمي ، سأوضح لك أساسيات استخدام pdb ، مصحح أخطاء شفرة المصدر التفاعلي في Python.

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

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

يعد هذا أمرًا رائعًا لتتبع الأخطاء التي يصعب العثور عليها ويسمح لك بإصلاح التعليمات البرمجية الخاطئة بسرعة وموثوقية أكبر. في بعض الأحيان ، يمكن أن يكون التنقل خلال التعليمات البرمجية في pdb ورؤية كيف يمكن أن يكون تغيير القيم أمرًا مثيرًا للانتباه ويؤدي إلى لحظات “aha” ، جنبًا إلى جنب مع “راحة اليد”.

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

يستخدم رمز المثال في هذا البرنامج التعليمي Python 3.6. يمكنك العثور على الكود المصدري لهذه الأمثلة على GitHub.

في نهاية هذا البرنامج التعليمي ، يوجد مرجع سريع لأوامر pdb الأساسية.

هناك أيضًا مرجع أوامر pdb قابل للطباعة يمكنك استخدامه كورقة غش أثناء تصحيح الأخطاء:

الشروع في العمل: طباعة قيمة متغير

في هذا المثال الأول ، سننظر في استخدام pdb في أبسط صوره: التحقق من قيمة المتغير.

أدخل الكود التالي في المكان الذي تريد اقتحام مصحح الأخطاء فيه:

import pdb; pdb.set_trace()

عندما يتم تنفيذ السطر أعلاه ، تتوقف Python وتنتظر حتى تخبرها بما يجب فعله بعد ذلك. سترى مطالبة (Pdb). هذا يعني أنك الآن متوقف مؤقتًا في مصحح الأخطاء التفاعلي ويمكنك إدخال أمر.

بدءًا من Python 3.7 ، هناك طريقة أخرى للدخول إلى مصحح الأخطاء. يصف PEP 553 نقطة توقف الوظيفة المضمنة () ، مما يجعل إدخال مصحح الأخطاء أمرًا سهلاً ومتسقًا:

breakpoint()

بشكل افتراضي ، ستقوم نقطة التوقف () باستيراد pdb واستدعاء pdb.set_trace () ، كما هو موضح أعلاه. ومع ذلك ، يعد استخدام نقطة التوقف () أكثر مرونة ويسمح لك بالتحكم في سلوك تصحيح الأخطاء عبر واجهة برمجة التطبيقات واستخدام متغير البيئة PYTHONBREAKPOINT. على سبيل المثال ، سيؤدي تعيين PYTHONBREAKPOINT = 0 في بيئتك إلى تعطيل نقطة التوقف () تمامًا ، وبالتالي تعطيل تصحيح الأخطاء. إذا كنت تستخدم Python 3.7 أو إصدارًا أحدث ، فأنا أشجعك على استخدام نقطة التوقف () بدلاً من pdb.set_trace ().

يمكنك أيضًا اقتحام مصحح الأخطاء ، دون تعديل المصدر واستخدام pdb.set_trace () أو نقطة التوقف () ، عن طريق تشغيل Python مباشرةً من سطر الأوامر وتمرير الخيار -m pdb. إذا كان التطبيق الخاص بك يقبل وسيطات سطر الأوامر ، فمررها كما تفعل عادةً بعد اسم الملف. فمثلا:

$ python3 -m pdb app.py arg1 arg2

هناك الكثير من أوامر pdb المتاحة. في نهاية هذا البرنامج التعليمي ، توجد قائمة بأوامر pdb الأساسية. في الوقت الحالي ، دعنا نستخدم الأمر p لطباعة قيمة المتغير. أدخل p Vari_name في موجه (Pdb) لطباعة قيمته.

دعونا نلقي نظرة على المثال. إليك مصدر example1.py:

#!/usr/bin/env python3

filename = __file__
import pdb; pdb.set_trace()
print(f'path = {filename}')

إذا قمت بتشغيل هذا من shell الخاص بك ، يجب أن تحصل على الإخراج التالي:

$ ./example1.py 
> /code/example1.py(5)<module>()
-> print(f'path = {filename}')
(Pdb) 

إذا كنت تواجه مشكلة في الحصول على الأمثلة أو التعليمات البرمجية الخاصة بك لتشغيلها من سطر الأوامر ، فاقرأ كيف أقوم بعمل أوامر سطر الأوامر الخاصة بي باستخدام Python؟ إذا كنت تستخدم نظام Windows ، فتحقق من الأسئلة الشائعة حول Python Windows.

أدخل الآن اسم الملف p. يجب أن ترى:

(Pdb) p filename
'./example1.py'
(Pdb)

نظرًا لأنك تعمل في shell وتستخدم CLI (واجهة سطر الأوامر) ، انتبه إلى الأحرف والتنسيق. سيعطونك السياق الذي تحتاجه:

  • > يبدأ السطر الأول ويخبرك بالملف المصدر الذي تستخدمه. بعد اسم الملف ، يوجد رقم السطر الحالي بين قوسين. التالي هو اسم الوظيفة. في هذا المثال ، نظرًا لأننا لم نتوقف مؤقتًا داخل دالة وعلى مستوى الوحدة ، نرى <module> ().
  • -> يبدأ السطر الثاني وهو سطر المصدر الحالي حيث يتم إيقاف Python مؤقتًا. لم يتم تنفيذ هذا الخط حتى الآن. في هذا المثال ، هذا هو السطر 5 في example1.py ، من> السطر أعلاه.
  • (Pdb) هو موجه pdb. إنها تنتظر أمرًا.

استخدم الأمر q لإنهاء التصحيح والخروج.

تعبيرات الطباعة

عند استخدام الأمر print p ، فأنت تقوم بتمرير تعبير ليتم تقييمه بواسطة Python. إذا قمت بتمرير اسم متغير ، فإن pdb يطبع قيمته الحالية. ومع ذلك ، يمكنك فعل الكثير للتحقق من حالة التطبيق قيد التشغيل.

في هذا المثال ، يتم استدعاء الدالة get_path (). لفحص ما يحدث في هذه الوظيفة ، قمت بإدخال استدعاء إلى pdb.set_trace () لإيقاف التنفيذ مؤقتًا قبل أن يعود:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    import pdb; pdb.set_trace()
    return head


filename = __file__
print(f'path = {get_path(filename)}')

إذا قمت بتشغيل هذا من قوقعتك ، فيجب أن تحصل على الإخراج:

$ ./example2.py 
> /code/example2.py(10)get_path()
-> return head
(Pdb) 

اين نحن؟

  • >: نحن في الملف المصدر example2.py في السطر 10 في الوظيفة get_path (). هذا هو الإطار المرجعي الذي سيستخدمه الأمر p لحل أسماء المتغيرات ، أي النطاق أو السياق الحالي.
  • ->: توقف التنفيذ عند رأس العودة. لم يتم تنفيذ هذا الخط حتى الآن. هذا هو السطر 10 في example2.py في الوظيفة get_path () ، من> السطر أعلاه.

دعنا نطبع بعض التعبيرات لإلقاء نظرة على الحالة الحالية للتطبيق. أستخدم الأمر ll (القائمة الطويلة) مبدئيًا لسرد مصدر الوظيفة:

(Pdb) ll
  6     def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         import pdb; pdb.set_trace()
 10  ->     return head
(Pdb) p filename
'./example2.py'
(Pdb) p head, tail
('.', 'example2.py')
(Pdb) p 'filename: ' + filename
'filename: ./example2.py'
(Pdb) p get_path
<function get_path at 0x100760e18>
(Pdb) p getattr(get_path, '__doc__')
"Return file's path or empty string if no path."
(Pdb) p [os.path.split(p)[1] for p in os.path.sys.path]
['pdb-basics', 'python36.zip', 'python3.6', 'lib-dynload', 'site-packages']
(Pdb) 

يمكنك تمرير أي تعبير Python صالح إلى p للتقييم.

هذا مفيد بشكل خاص عند تصحيح الأخطاء وترغب في اختبار تطبيق بديل مباشرة في التطبيق في وقت التشغيل.

يمكنك أيضًا استخدام الأمر pp (طباعة جميلة) لطباعة التعبيرات. هذا مفيد إذا كنت تريد طباعة متغير أو تعبير بكمية كبيرة من الإخراج ، على سبيل المثال القوائم والقواميس. تحافظ الطباعة الجيدة على الكائنات في سطر واحد إذا كان ذلك ممكنًا أو تقسمها إلى أسطر متعددة إذا لم تتناسب مع العرض المسموح به.

يخطو من خلال التعليمات البرمجية

هناك أمران يمكنك استخدامهما للتنقل عبر التعليمات البرمجية عند تصحيح الأخطاء:
وصف الأمر
n (التالي) تابع التنفيذ حتى يتم الوصول إلى السطر التالي في الوظيفة الحالية أو يعود.
s (الخطوة) قم بتنفيذ السطر الحالي وتوقف عند أول مناسبة ممكنة (إما في وظيفة تسمى أو في الوظيفة الحالية).

هناك أمر ثالث يسمى unt (حتى). إنه مرتبط بـ n (التالي). سنلقي نظرة عليها لاحقًا في هذا البرنامج التعليمي في قسم التنفيذ المستمر.

الفرق بين n (التالي) و s (الخطوة) حيث يتوقف pdb.

استخدم n (التالي) لمواصلة التنفيذ حتى السطر التالي والبقاء ضمن الوظيفة الحالية ، أي عدم التوقف في دالة أجنبية إذا تم استدعاء أحدها. فكر في الخطوة التالية على أنها “البقاء محليًا” أو “الخطوة التالية”.

استخدم s (الخطوة) لتنفيذ السطر الحالي والتوقف في وظيفة خارجية إذا تم استدعاء أحدها. فكر في الخطوة على أنها “خطوة إلى”. إذا تم إيقاف التنفيذ في وظيفة أخرى ، فسيتم طباعة – Call -.

سيتوقف كل من n و s عن التنفيذ عند الوصول إلى نهاية الوظيفة الحالية والطباعة –Return – جنبًا إلى جنب مع القيمة المعادة في نهاية السطر التالي بعد ->.

دعونا نلقي نظرة على مثال باستخدام كلا الأمرين. إليك مصدر example3.py:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    return head


filename = __file__
import pdb; pdb.set_trace()
filename_path = get_path(filename)
print(f'path = {filename_path}')

إذا قمت بتشغيل هذا من shell الخاص بك وأدخلت n ، يجب أن تحصل على الإخراج:

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) n
> /code/example3.py(15)<module>()
-> print(f'path = {filename_path}')
(Pdb) 

مع n (التالي) ، توقفنا عند السطر 15 ، السطر التالي. “بقينا محليين” في <module> () و “تخطينا” المكالمة لـ get_path (). الوظيفة هي <module> () نظرًا لأننا حاليًا في مستوى الوحدة ولم نتوقف مؤقتًا داخل وظيفة أخرى.

دعونا نجرب ما يلي:

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) 

باستخدام s (الخطوة) ، توقفنا عند السطر 6 في الوظيفة get_path () حيث تم استدعاؤها في السطر 14. لاحظ السطر – Call – بعد الأمر s.

بشكل ملائم ، يتذكر pdb آخر أمر لك. إذا كنت تتنقل عبر الكثير من التعليمات البرمجية ، يمكنك فقط الضغط على Enter لتكرار الأمر الأخير.

يوجد أدناه مثال على استخدام كل من s و n للتنقل عبر الكود. أقوم بإدخال s في البداية لأنني أريد “الدخول إلى” الوظيفة get_path () والتوقف. ثم أدخل n مرة واحدة “للبقاء محليًا” أو “تخطي” أي استدعاءات وظيفية أخرى واضغط فقط على Enter لتكرار الأمر n حتى أصل إلى آخر سطر مصدر.

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) n
> /code/example3.py(8)get_path()
-> head, tail = os.path.split(filename)
(Pdb) 
> /code/example3.py(9)get_path()
-> return head
(Pdb) 
--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb) 
> /code/example3.py(15)<module>()
-> print(f'path = {filename_path}')
(Pdb) 
path = .
--Return--
> /code/example3.py(15)<module>()->None
-> print(f'path = {filename_path}')
(Pdb) 

لاحظ السطور – Call – and –Return -. هذا هو pdb الذي يتيح لك معرفة سبب إيقاف الإعدام. n (التالي) و s (الخطوة) ستتوقف قبل أن تعود الدالة. لهذا السبب ترى سطور “العودة” أعلاه.

لاحظ أيضًا -> “.” في نهاية السطر بعد أول – رجوع – أعلاه:

--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb) 

عندما يتوقف pdb في نهاية دالة قبل أن تعود ، فإنه يطبع أيضًا قيمة الإرجاع لك. في هذا المثال هو “.”.

إدراج رمز المصدر

لا تنس الأمر ll (القائمة الطويلة: سرد شفرة المصدر بالكامل للوظيفة أو الإطار الحالي). إنه مفيد حقًا عندما تتخطى رمزًا غير مألوف أو تريد فقط رؤية الوظيفة الكاملة للسياق.

هذا مثال:

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) ll
  6  -> def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         return head
(Pdb) 

لرؤية مقتطف أقصر من التعليمات البرمجية ، استخدم الأمر l (قائمة). بدون وسيطات ، سيتم طباعة 11 سطرًا حول السطر الحالي أو متابعة القائمة السابقة. مرر الحجة. لإدراج 11 سطرًا دائمًا حول السطر الحالي: l.

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) l
  9         return head
 10     
 11     
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) l
[EOF]
(Pdb) l .
  9         return head
 10     
 11     
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) 

استخدام نقاط التوقف

تعتبر نقاط التوقف مريحة للغاية ويمكن أن توفر لك الكثير من الوقت. بدلاً من المرور عبر عشرات الأسطر التي لا تهتم بها ، قم ببساطة بإنشاء نقطة توقف حيث تريد التحقق منها. اختياريًا ، يمكنك أيضًا إخبار pdb بالكسر فقط عندما يكون شرط معين صحيحًا.

استخدم الأمر ب (فاصل) لتعيين نقطة توقف. يمكنك تحديد رقم سطر أو اسم وظيفة حيث تم إيقاف التنفيذ.

صيغة الاستراحة هي:

b(reak) [ ([filename:]lineno | function) [, condition] ]

إذا لم يتم تحديد filename: قبل lineno رقم السطر ، فسيتم استخدام ملف المصدر الحالي.

لاحظ الوسيطة الثانية الاختيارية لـ b: condition. هذا قوي جدا. تخيل موقفًا تريد كسره فقط في حالة وجود حالة معينة. إذا قمت بتمرير تعبير Python باعتباره الوسيطة الثانية ، فسوف ينكسر pdb عندما يكون التعبير صحيحًا. سنفعل هذا في مثال أدناه.

في هذا المثال ، توجد وحدة الأداة المساعدة util.py. لنقم بتعيين نقطة توقف لإيقاف التنفيذ في دالة get_path ().

في ما يلي مصدر النص الرئيسي example4.py:

#!/usr/bin/env python3

import util

filename = __file__
import pdb; pdb.set_trace()
filename_path = util.get_path(filename)
print(f'path = {filename_path}')

إليك مصدر وحدة الأداة المساعدة util.py:

def get_path(filename):
    """Return file's path or empty string if no path."""
    import os
    head, tail = os.path.split(filename)
    return head

أولاً ، لنقم بتعيين نقطة توقف باستخدام اسم ملف المصدر ورقم السطر:

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p filename, head, tail
('./example4.py', '.', 'example4.py')
(Pdb) 

يستمر الأمر c (متابعة) في التنفيذ حتى يتم العثور على نقطة توقف.

بعد ذلك ، دعنا نضبط نقطة توقف باستخدام اسم الوظيفة:

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) p filename
'./example4.py'
(Pdb) 

أدخل b بدون وسيطات لعرض قائمة بجميع نقاط التوقف:

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb) 

يمكنك تعطيل وإعادة تمكين نقاط التوقف باستخدام الأمر تعطيل bpnumber وتمكين bpnumber. bpnumber هو رقم نقطة التوقف من العمود الأول لقائمة نقاط التوقف Num. لاحظ تغير قيمة عمود Enb:

(Pdb) disable 1
Disabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /code/util.py:1
(Pdb) enable 1
Enabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb) 

لحذف نقطة توقف ، استخدم الأمر cl (مسح):

cl(ear) filename:lineno
cl(ear) [bpnumber [bpnumber...]]

دعنا الآن نستخدم تعبير Python لتعيين نقطة توقف. تخيل موقفًا تريد التوقف فيه فقط إذا تلقت وظيفتك المضطربة مدخلات معينة.

في هذا السيناريو المثال ، تفشل وظيفة get_path () عندما تتلقى مسارًا نسبيًا ، أي أن مسار الملف لا يبدأ بـ /. سأقوم بإنشاء تعبير يتم تقييمه إلى صحيح في هذه الحالة وتمريره إلى b باعتباره الوسيطة الثانية:

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path, not filename.startswith('/')
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) a
filename = './example4.py'
(Pdb) 

بعد إنشاء نقطة التوقف أعلاه وإدخال c لمتابعة التنفيذ ، يتوقف pdb عند تقييم التعبير إلى true. يقوم الأمر a (args) بطباعة قائمة وسيطات الوظيفة الحالية.

في المثال أعلاه ، عندما تقوم بتعيين نقطة التوقف باستخدام اسم دالة بدلاً من رقم سطر ، لاحظ أن التعبير يجب أن يستخدم وسيطات الوظيفة فقط أو المتغيرات العامة المتاحة في وقت إدخال الوظيفة. خلاف ذلك ، ستتوقف نقطة التوقف عن التنفيذ في الوظيفة بغض النظر عن قيمة التعبير.

إذا كنت بحاجة إلى كسر استخدام تعبير باسم متغير موجود داخل دالة ، أي اسم متغير غير موجود في قائمة وسيطات الوظيفة ، فحدد رقم السطر:

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5, not head.startswith('/')
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p head
'.'
(Pdb) a
filename = './example4.py'
(Pdb) 

يمكنك أيضًا تعيين نقطة توقف مؤقتة باستخدام الأمر tbreak. تتم إزالته تلقائيًا عندما يتم ضربه لأول مرة. يستخدم نفس الحجج مثل ب.

استمرار التنفيذ

حتى الآن ، نظرنا في التنقل عبر الشفرة باستخدام n (التالي) و s (الخطوة) واستخدام نقاط التوقف مع b (فاصل) و c (متابعة).

هناك أيضًا أمر مرتبط: unt (until).

استخدم unt لمواصلة التنفيذ مثل c ، لكن توقف عند السطر التالي أكبر من السطر الحالي. أحيانًا يكون unt أكثر ملاءمة وسرعة في الاستخدام وهو بالضبط ما تريده. سأوضح هذا بمثال أدناه.

دعونا نلقي نظرة أولية على بنية ووصف unt:
وصف بناء جملة الأمر
unt unt (il) [lineno] بدون لينينو ، استمر في التنفيذ حتى يتم الوصول إلى السطر الذي يحتوي على رقم أكبر من الرقم الحالي. باستخدام lineno ، استمر في التنفيذ حتى يتم الوصول إلى سطر برقم أكبر أو يساوي ذلك. في كلتا الحالتين ، توقف أيضًا عند عودة الإطار الحالي.

اعتمادًا على ما إذا كنت تمرر خط وسيطة رقم السطر أم لا ، يمكن أن يتصرف unt بطريقتين:

  • بدون لينينو ، استمر في التنفيذ حتى يتم الوصول إلى السطر الذي يحتوي على رقم أكبر من الرقم الحالي. هذا مشابه لـ n (next). إنها طريقة بديلة للتنفيذ و “التنقّل” للشفرة. الفرق بين n و unt هو أن unt يتوقف فقط عند الوصول إلى خط برقم أكبر من الرقم الحالي. سيتوقف n عند السطر التالي المنفذ منطقيًا.
  • باستخدام lineno ، استمر في التنفيذ حتى يتم الوصول إلى سطر برقم أكبر أو يساوي ذلك. هذا مثل c (متابعة) مع وسيطة رقم سطر.

في كلتا الحالتين ، unt يتوقف عند عودة الإطار الحالي (الوظيفة) ، تمامًا مثل n (التالي) و s (الخطوة).

استخدم unt عندما تريد متابعة التنفيذ والتوقف أكثر لأسفل في ملف المصدر الحالي. يمكنك معاملته على أنه مزيج من n (التالي) و b (فاصل) ، اعتمادًا على ما إذا كنت تمرر وسيطة رقم سطر أم لا.

في المثال أدناه ، توجد وظيفة ذات حلقة. هنا ، تريد متابعة تنفيذ الكود والتوقف بعد الحلقة ، دون المرور عبر كل تكرار للحلقة أو تعيين نقطة توقف:

إليك مثال المصدر على سبيل المثال example4unt.py:

#!/usr/bin/env python3

import os


def get_path(fname):
    """Return file's path or empty string if no path."""
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    for char in tail:
        pass  # Check filename char
    return head


filename = __file__
filename_path = get_path(filename)
print(f'path = {filename_path}')

وإخراج وحدة التحكم باستخدام unt:

$ ./example4unt.py 
> /code/example4unt.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) unt
> /code/example4unt.py(10)get_path()
-> for char in tail:
(Pdb) 
> /code/example4unt.py(11)get_path()
-> pass  # Check filename char
(Pdb) 
> /code/example4unt.py(12)get_path()
-> return head
(Pdb) p char, tail
('y', 'example4unt.py')

تم استخدام الأمر ll أولاً لطباعة مصدر الوظيفة ، متبوعًا بـ unt. يتذكر pdb آخر أمر تم إدخاله ، لذلك قمت فقط بالضغط على Enter لتكرار الأمر unt. استمر هذا التنفيذ من خلال الكود حتى تم الوصول إلى سطر مصدر أكبر من السطر الحالي.

لاحظ في إخراج وحدة التحكم أعلاه أن pdb توقف مرة واحدة فقط في السطرين 10 و 11. نظرًا لاستخدام unt ، تم إيقاف التنفيذ فقط في التكرار الأول للحلقة. ومع ذلك ، تم تنفيذ كل تكرار للحلقة. يمكن التحقق من ذلك في آخر سطر من الإخراج. قيمة “y” لمتغير char تساوي الحرف الأخير في قيمة “example4unt.py”.

عرض التعبيرات

على غرار تعبيرات الطباعة باستخدام p و pp ، يمكنك استخدام عرض الأمر [تعبير] لإخبار pdb بعرض قيمة التعبير تلقائيًا ، إذا تغيرت ، عند توقف التنفيذ. استخدم الأمر undisplay [التعبير] لمسح تعبير العرض.

فيما يلي بناء الجملة والوصف لكلا الأمرين:
وصف بناء جملة الأمر
display display [التعبير] يعرض قيمة التعبير إذا تغير ، في كل مرة يتوقف التنفيذ في الإطار الحالي. بدون تعبير ، قم بسرد كل تعبيرات العرض للإطار الحالي.
undisplay undisplay [التعبير] لا تعرض أي تعبير في الإطار الحالي. بدون تعبير ، امسح كل تعبيرات العرض للإطار الحالي.

يوجد أدناه مثال ، example4display.py ، يوضح استخدامه مع حلقة:

$ ./example4display.py 
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) 
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'a'  [old: 'x']
(Pdb) 
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'm'  [old: 'a']

في الإخراج أعلاه ، يعرض pdb تلقائيًا قيمة متغير char لأنه في كل مرة يتم فيها الوصول إلى نقطة التوقف ، تتغير قيمتها. في بعض الأحيان يكون هذا مفيدًا وهو ما تريده بالضبط ، ولكن هناك طريقة أخرى لاستخدام العرض.

يمكنك إدخال العرض عدة مرات لإنشاء قائمة مراقبة بالتعبيرات. يمكن أن يكون هذا أسهل في الاستخدام من p. بعد إضافة جميع التعبيرات التي تهتم بها ، ما عليك سوى إدخال العرض لرؤية القيم الحالية:

$ ./example4display.py 
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) display fname
display fname: './example4display.py'
(Pdb) display head
display head: '.'
(Pdb) display tail
display tail: 'example4display.py'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) display
Currently displaying:
char: 'x'
fname: './example4display.py'
head: '.'
tail: 'example4display.py'

معرف المتصل بيثون

في هذا القسم الأخير ، سنبني على ما تعلمناه حتى الآن وننتهي بمكافأة جيدة. أستخدم اسم “معرف المتصل” في إشارة إلى ميزة تحديد هوية المتصل في نظام الهاتف. هذا هو بالضبط ما يوضحه هذا المثال ، باستثناء أنه يتم تطبيقه على بايثون.

إليك مصدر example5.py للنص البرمجي الرئيسي:

#!/usr/bin/env python3

import fileutil


def get_file_info(full_fname):
    file_path = fileutil.get_path(full_fname)
    return file_path


filename = __file__
filename_path = get_file_info(filename)
print(f'path = {filename_path}')

إليك ملف وحدة الأداة المساعدة fileutil.py:

def get_path(fname):
    """Return file's path or empty string if no path."""
    import os
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    return head

في هذا السيناريو ، تخيل وجود قاعدة رموز كبيرة مع وظيفة في وحدة الأداة المساعدة ، get_path () ، يتم استدعاؤها بإدخال غير صالح. ومع ذلك ، يتم استدعاؤه من عدة أماكن في حزم مختلفة.

كيف تجد من هو المتصل؟

استخدم الأمر w (حيث) لطباعة تتبع مكدس ، مع وجود أحدث إطار في الأسفل:

$ ./example5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) 

لا تقلق إذا كان هذا يبدو محيرًا أو إذا لم تكن متأكدًا من ماهية تتبع المكدس أو الإطار. سأشرح هذه الشروط أدناه. الأمر ليس بالصعوبة التي قد يبدو عليها.

نظرًا لأن أحدث إطار موجود في الأسفل ، ابدأ من هناك واقرأ من الأسفل إلى الأعلى. انظر إلى الأسطر التي تبدأ بـ -> ، لكن تخطي المثال الأول حيث تم استخدام pdb.set_trace () لإدخال pdb في الدالة get_path (). في هذا المثال ، سطر المصدر الذي يسمى الدالة get_path () هو:

-> file_path = fileutil.get_path(full_fname)

السطر الموجود أعلى كل -> يحتوي على اسم الملف ورقم السطر (بين قوسين) واسم الوظيفة الموجود بها سطر المصدر. لذلك يكون المتصل هو:

/code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

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

الآن نحن نعرف كيفية العثور على المتصل.

ولكن ماذا عن هذا التتبع المكدس وإطار الأشياء؟

تتبع المكدس هو مجرد قائمة بجميع الإطارات التي أنشأتها Python لتتبع استدعاءات الوظائف. الإطار عبارة عن بنية بيانات يتم إنشاؤها بواسطة Python عند استدعاء دالة وحذفها عند إعادتها. المكدس هو ببساطة قائمة مرتبة من الإطارات أو استدعاءات الوظائف في أي وقت. ينمو مكدس (استدعاء الوظيفة) ويتقلص طوال عمر التطبيق حيث يتم استدعاء الوظائف ثم العودة.

عند طباعتها ، تسمى هذه القائمة المرتبة من الإطارات ، المكدس ، تتبع المكدس. يمكنك رؤيته في أي وقت عن طريق إدخال الأمر w ، كما فعلنا أعلاه للعثور على المتصل.

راجع مقالة مكدس المكالمات هذه على ويكيبيديا للحصول على التفاصيل.

لفهم أفضل والاستفادة بشكل أكبر من pdb ، دعنا نلقي نظرة عن كثب على المساعدة الخاصة بـ w:

(Pdb) h w
w(here)
        Print a stack trace, with the most recent frame at the bottom.
        An arrow indicates the "current frame", which determines the
        context of most commands. 'bt' is an alias for this command.

ماذا يعني pdb ب “الإطار الحالي”؟

فكر في الإطار الحالي على أنه الوظيفة الحالية حيث توقف pdb عن التنفيذ. بمعنى آخر ، الإطار الحالي هو المكان الذي يتم فيه إيقاف التطبيق مؤقتًا حاليًا ويتم استخدامه كـ “إطار” مرجعي لأوامر pdb مثل p (طباعة).

ستستخدم p والأوامر الأخرى الإطار الحالي للسياق عند الحاجة. في حالة p ، سيتم استخدام الإطار الحالي للبحث عن المراجع المتغيرة وطباعتها.

عندما يقوم pdb بطباعة تتبع مكدس ، يشير السهم> إلى الإطار الحالي.

كيف هذا مفيد؟

يمكنك استخدام الأمرين u (أعلى) و د (أسفل) لتغيير الإطار الحالي. بالاقتران مع p ، يتيح لك ذلك فحص المتغيرات والحالة في التطبيق الخاص بك في أي نقطة على طول مكدس الاستدعاءات في أي إطار.

فيما يلي بناء الجملة والوصف لكلا الأمرين:

وصف بناء جملة الأمر
u u (p) [عدد] نقل عدد الإطارات الحالي (المستوى الافتراضي) إلى أعلى في تتبع المكدس (إلى إطار أقدم).
d d (خاص) [عدد] نقل مستويات عدد الإطارات الحالية (المستوى الافتراضي) لأسفل في تتبع المكدس (إلى إطار أحدث).

دعونا نلقي نظرة على مثال باستخدام الأمرين u و d. في هذا السيناريو ، نريد فحص المتغير full_fname المحلي للدالة get_file_info () في example5.py. للقيام بذلك ، يتعين علينا تغيير الإطار الحالي إلى مستوى أعلى باستخدام الأمر u:

$ ./example5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
(Pdb) p full_fname
'./example5.py'
(Pdb) d
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) p fname
'./example5.py'
(Pdb) 

الاستدعاء إلى pdb.set_trace () موجود في fileutil.py في الوظيفة get_path () ، لذلك يتم تعيين الإطار الحالي هناك في البداية. يمكنك رؤيته في السطر الأول من الإخراج أعلاه:

> /code/fileutil.py(5)get_path()

للوصول إلى المتغير المحلي full_fname وطباعته في الوظيفة get_file_info () في example5.py ، تم استخدام الأمر u للانتقال إلى مستوى واحد للأعلى:

(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

لاحظ في إخراج u أعلاه أن pdb طبع السهم> في بداية السطر الأول. يتيح لك هذا pdb معرفة أنه تم تغيير الإطار وأن موقع المصدر هذا هو الآن الإطار الحالي. المتغير full_fname يمكن الوصول إليه الآن. أيضًا ، من المهم إدراك أن سطر المصدر الذي يبدأ بـ -> في السطر الثاني قد تم تنفيذه. منذ أن تم نقل الإطار لأعلى المكدس ، تم استدعاء fileutil.get_path (). باستخدام u ، نقلنا المكدس (بمعنى ما ، إلى الوراء في الوقت المناسب) إلى الوظيفة example5.get_file_info () حيث تم استدعاء fileutil.get_path ().

متابعة للمثال ، بعد طباعة full_fname ، تم نقل الإطار الحالي إلى موقعه الأصلي باستخدام d ، وتمت طباعة المتغير المحلي fname في get_path ().

إذا أردنا ذلك ، كان بإمكاننا تحريك إطارات متعددة مرة واحدة بتمرير وسيطة العد إلى u أو d. على سبيل المثال ، كان بإمكاننا الانتقال إلى مستوى الوحدة النمطية في example5.py عن طريق إدخال u 2:

$ ./example5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u 2
> /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
(Pdb) p filename
'./example5.py'
(Pdb) 

من السهل أن تنسى مكانك عند تصحيح الأخطاء والتفكير في العديد من الأشياء المختلفة. فقط تذكر أنه يمكنك دائمًا استخدام الأمر w (حيث) المسمى بشكل مناسب لمعرفة مكان إيقاف التنفيذ مؤقتًا وما هو الإطار الحالي.

أوامر pdb الأساسية

بمجرد قضاء بعض الوقت مع pdb ، ستدرك أن القليل من المعرفة يقطع شوطًا طويلاً. المساعدة متاحة دائمًا مع الأمر h.

ما عليك سوى إدخال h أو مساعدة <topic> للحصول على قائمة بجميع الأوامر أو المساعدة لأمر أو موضوع معين.

للإشارة بسرعة ، إليك قائمة بالأوامر الأساسية:
وصف الأمر
p طباعة قيمة التعبير.
ص ب جميلة – طباعة قيمة التعبير.
n تابع التنفيذ حتى يتم الوصول إلى السطر التالي في الوظيفة الحالية أو يعود.
قم بتنفيذ السطر الحالي وتوقف عند أول مناسبة ممكنة (إما في وظيفة تسمى أو في الوظيفة الحالية).
ج مواصلة التنفيذ والتوقف فقط عند مواجهة نقطة توقف.
استمر في التنفيذ حتى يتم الوصول إلى السطر الذي يحتوي على رقم أكبر من الرقم الحالي. باستخدام وسيطة رقم سطر ، استمر في التنفيذ حتى يتم الوصول إلى سطر برقم أكبر أو يساوي ذلك.
l سرد التعليمات البرمجية المصدر للملف الحالي. بدون وسيطات ، قم بإدراج 11 سطراً حول السطر الحالي أو تابع القائمة السابقة.
ll قائمة التعليمات البرمجية المصدر بالكامل للوظيفة الحالية أو الإطار.
ب دون أي حجج ، قم بإدراج جميع الفواصل. باستخدام وسيطة رقم السطر ، قم بتعيين نقطة توقف عند هذا السطر في الملف الحالي.
w اطبع تتبع مكدس ، بحيث يكون أحدث إطار في الأسفل. يشير السهم إلى الإطار الحالي ، والذي يحدد سياق معظم الأوامر.
u انقل مستويات عدد الإطارات الحالية (المستوى الافتراضي) لأعلى في تتبع المكدس (إلى إطار أقدم).
d انقل مستويات عدد الإطارات الحالية (المستوى الافتراضي) لأسفل في تتبع المكدس (إلى إطار أحدث).
h راجع قائمة بالأوامر المتوفرة.
h <topic> إظهار التعليمات لأمر أو موضوع.
h pdb إظهار وثائق pdb الكاملة.
q قم بإنهاء مصحح الأخطاء والإنهاء.

تصحيح أخطاء بايثون باستخدام PDB: الخاتمة

في هذا البرنامج التعليمي ، غطينا بعض الاستخدامات الأساسية والشائعة لـ pdb:

  • تعبيرات الطباعة
  • يتخطى الكود مع n (التالي) و s (خطوة)
  • باستخدام نقاط التوقف
  • استمرار التنفيذ مع unt (حتى)
  • عرض التعبيرات
  • العثور على متصل دالة

آمل أن تكون مفيدة لك. إذا كنت مهتمًا بمعرفة المزيد ، فراجع:

  • وثائق pdb الكاملة في موجه pdb بالقرب منك: (Pdb) h pdb
  • مستندات pdb من Python

يمكن العثور على الكود المصدري المستخدم في الأمثلة في مستودع GitHub المرتبط. تأكد من إطلاعك على مرجع أوامر pdb القابل للطباعة ، والذي يمكنك استخدامه كورقة غش أثناء تصحيح الأخطاء:

أيضًا ، إذا كنت ترغب في تجربة مصحح أخطاء Python المستند إلى واجهة المستخدم الرسومية ، فاقرأ Python IDEs and Editors Guide لمعرفة الخيارات التي ستناسبك بشكل أفضل. بايثونينج سعيد!