Anophel-آنوفل نحوه Debug کردن خطاهای پایتون

نحوه Debug کردن خطاهای پایتون

انتشار:
1

پایتون یک زبانی هست که همه از آن استفاده می کنیم و کمتر کسی وجود دارد که آن را بلد نباشد. در Python، PHP و JVM، ما می‌توانیم زمان اجرا را عمیق‌تر بررسی کنیم و داده‌های اضافی درباره هر فریم در call stack به شما بدهیم. در سطح بالا، این به شما امکان می‌دهد چیزهایی مانند فراخوانی آرگومان‌ها به توابع خود را بدانید و به شما این امکان را می‌دهد که به راحتی یک خطا را بازتولید و درک کنید. بیایید ببینیم این شبیه به چه چیزی است و چگونه کار می کند.

ما با این شروع خواهیم کرد که یک خطای پایتون معمولاً ممکن است در ترمینال شما یا در یک سیستم گزارش استاندارد ظاهر شود:

TypeError: expected string or buffer
  File "sentry/stacktraces.py", line 309, in process_single_stacktrace
    processable_frame, processing_task)
  File "sentry/lang/native/plugin.py", line 196, in process_frame
    in_app = (in_app and not self.sym.is_internal_function(raw_frame.get('function')))
  File "sentry/lang/native/symbolizer.py", line 278, in is_internal_function
    return _internal_function_re.search(function) is not None

در حالی که این به ما ایده ای از نوع و مکان خطا می دهد، متأسفانه به ما کمک نمی کند تا بفهمیم واقعاً چه چیزی باعث آن شده است. این امکان وجود دارد که یک عدد int یا یک NoneType را ارسال کند، اما، در واقع، ممکن است تعداد زیادی چیز باشد. حدس زدن آن فقط ما را تا اینجای کار می‌رساند و ما واقعاً باید بدانیم که function واقعاً چیست.

یک گزینه آسان و اغلب بسیار در دسترس برای انجام این کار، افزودن مقداری گزارش است. چند نقطه ورودی مختلف وجود دارد که می‌توانیم لاگ را در آنها قرار دهیم، که اجرای آن را آسان‌تر می‌کند. همچنین به ما امکان می‌دهد مطمئن شویم که به طور خاص به پاسخی که می‌خواهیم می‌رسیم. به عنوان مثال، ما می خواهیم بفهمیم که type این function چیست:

import logging
# ...
logging.debug("function is of type %s", type(function))

یکی دیگر از مزایای لاگ گرفتن مانند این است که می تواند به production منتقل شود. نتیجه این است که شما عبارات گزارش سطح DEBUG را در production ضبط نمی کنید زیرا حجم آن می تواند قابل توجه و مفید نباشد. همچنین اغلب شما را ملزم می‌کند که از قبل برنامه‌ریزی کنید تا مطمئن شوید که موارد خرابی مختلفی که ممکن است رخ دهد و زمینه مناسب برای هر یک را ثبت کنید.

به خاطر این آموزش، بیایید فرض کنیم که نمی‌توانیم این کار را در production انجام دهیم، از قبل برای آن برنامه‌ریزی نکرده‌ایم، و در عوض، سعی می‌کنیم آن را اشکال‌زدایی و در توسعه بازتولید کنیم.

دیباگر پایتون

Python Debugger (PDB) ابزاری است که به شما امکان می دهد با استفاده از نقاط شکست از call stack خود عبور کنید. خود این ابزار از اشکال‌زدای گنو (GDB) الهام گرفته شده است و اگرچه قدرتمند است، اما اگر با آن آشنا نباشید، می توانید تا حدودی با آن کار کنید. این قطعا تجربه‌ای است که با تکرار آسان‌تر می‌شود، و ما فقط به عنوان مثال به چند مفهوم سطح بالا می‌پردازیم.

بنابراین اولین کاری که می‌خواهیم انجام دهیم این است که کد خود را برای اضافه کردن یک نقطه شکست یا همان breakpoint استفاده کنیم. در مثال بالا، ما در واقع می‌توانیم خودمان را ابزار signizer.py کنیم. همیشه اینطور نیست، زیرا گاهی اوقات استثنا در کد شخص ثالث اتفاق می افتد. مهم نیست کجا آن را استفاده می کنید، همچنان می‌توانید از استک بالا و پایین بپرید. بیایید با تغییر آن کد شروع کنیم:

def is_internal_function(self, function):
    # add a breakpoint for PDB
    try:
        return _internal_function_re.search(function) is not None
    except Exception:
        import pdb; pdb.set_trace()
        raise

ما این را به استثنا محدود می کنیم، زیرا معمول است که شما کدی دارید که اغلب اوقات با موفقیت اجرا می شود، گاهی اوقات در یک حلقه، و نمی خواهید اجرا را در هر تکرار متوقف کنید.

هنگامی که به این نقطه شکست رسیدیم (که همان چیزی است که()set_traceثبت می‌کند)، به یک محیط پوسته مانند هدایت می‌شویم:

# ...
(Pdb)

این کنسول PDB است و مشابه پوسته پایتون کار می کند. علاوه بر اینکه می‌توانیم اکثر کدهای پایتون را اجرا کنیم، در یک زمینه خاص در استک تماس خود نیز اجرا می‌کنیم. آن مکان نقطه ورود است. در عوض، جایی است که شما ()set_trace را فراخوانی کردید. در مثال بالا، ما دقیقاً همان جایی هستیم که باید باشیم، بنابراین می توانیم به راحتی نوع function را انتخاب کنیم:

(Pdb) type(function)
<type 'NoneType'>

البته، ما همچنین می‌توانیم به سادگی همه متغیرهای با محدوده محلی را با استفاده از یکی از builtins پایتون بگیریم:

(Pdb) locals()
{..., 'function': None, ...}

در برخی موارد ممکن است مجبور شویم استک را به سمت up و down حرکت کنیم تا به فریمی که تابع در آن اجرا می شود برسیم. به عنوان مثال، اگر تابع دقیق ()set_trace ما را به بالاترین قسمت استک، احتمالاً در فریم بالایی، انداخته باشد، از پایین برای پرش به درون فریم‌های داخلی استفاده می‌کنیم تا زمانی که به مکانی برخورد کنیم که اطلاعات مورد نیاز را داشت:

(Pdb) down
-> in_app = (in_app and not self.sym.is_internal_function(raw_frame.get('function')))
(Pdb) down
-> return _internal_function_re.search(function) is not None
(Pdb) type(function)
<type 'NoneType'>

بنابراین ما مشکل را شناسایی کردیم: تابع یک NoneType است. در حالی که این واقعاً به ما نمی گوید که چرا چنین است، حداقل اطلاعات ارزشمندی برای سرعت بخشیدن به موارد آزمایشی به ما می دهد.

اشکال زدایی در حالت production

بنابراین PDB در توسعه عالی عمل می کند، اما در مورد production چطور؟ بیایید کمی عمیق تر به آنچه پایتون برای پاسخ به این سوال به ما می دهد نگاه کنیم.

نکته جالب در مورد زمان اجرا CPython ، این زمان اجرای استانداردی است که اکثر مردم از آن استفاده می کنند – این است که امکان دسترسی آسان به استک تماس فعلی را فراهم می کند. در حالی که برخی از زمان اجراهای دیگر (مانند PyPy) اطلاعات مشابهی را ارائه می دهند، تضمینی نیست. هنگامی که یک استثنا را می زنید، استک از طریق ()sys.exc_info در معرض دید قرار می گیرد. بیایید ببینیم چه چیزی برای یک استثنا معمولی به ما می دهد:

>>> try:
...   1 / 0
... except:
...   import sys; sys.exc_info()
...
(<type 'exceptions.ZeroDivisionError'>,
    ZeroDivisionError('integer division or modulo by zero',),
    <traceback object at 0x105da1a28>)

ما از عمیق شدن بیش از حد به این موضوع اجتناب می کنیم، اما مجموعه ای متشکل از سه قطعه اطلاعات داریم: کلاس استثنا، نمونه واقعی استثنا، و یک شی traceback. بیتی که در اینجا به آن اهمیت می دهیم، شیء traceback است. قابل توجه است، شما همچنین می توانید این اطلاعات را خارج از استثناها با استفاده از ماژول traceback دریافت کنید. داکیومت ها در مورد استفاده از این ساختارها وجود دارد، اما بیایید فقط خودمان تا عمق برویم تا آنها را درک کنیم. در داخل شی traceback، ما مجموعه‌ای از اطلاعات در دسترس داریم، هرچند برای دسترسی به آن کمی کار و جادو نیاز است:

>>> exc_type, exc_value, tb = exc_info
>>> tb.tb_frame
<frame object at 0x105dc0e10>

هنگامی که یک فریم به دست آوردیم، CPython راه‌هایی را برای دریافت محلی‌های استک‌ای در معرض دید قرار می‌دهد ، این همه متغیرهای محدوده‌ای برای آن فریم در حال اجرا هستند. برای مثال به کد زیر نگاه کنید:

def foo(bar=None):
    foo = "bar"
    1 / 0

بیایید با آن کد یک استثنا ایجاد کنیم:

try:
    foo()
except:
    exc_type, exc_value, tb = sys.exc_info()

و در نهایت، اجازه دهید از طریق f_locals در شی <frame> به محلی ها دسترسی پیدا کنیم:

>>> from pprint import pprint
>>> pprint(tb.tb_frame.f_locals)
{'__builtins__': <module '__builtin__' (built-in)>,
    '__doc__': None,
    '__name__': '__main__',
    '__package__': None,
    'exc_info': (<type 'exceptions.ZeroDivisionError'>,
                ZeroDivisionError('integer division or modulo by zero',),
                <traceback object at 0x105cd4fc8>),
    'foo': <function foo at 0x105cf50c8>,
    'history': '/Users/dcramer/.pythonhist',
    'os': <module 'os' from 'lib/python2.7/os.py'>,
    'pprint': <function pprint at 0x105cf52a8>,
    'print_function': _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 65536),
    'readline': <module 'readline' from 'lib/python2.7/lib-dynload/readline.so'>,
    'stack': [],
    'sys': <module 'sys' (built-in)>,
    'tb': <traceback object at 0x105da1a28>,
    'tb_next': None,
    'write_history': <function write_history at 0x105cf2c80>}

آنچه در بالا می بینیم در واقع چندان مفید نیست. دلیل آن این است که ما در محدوده خود یک سطح بالاتر هستیم، بنابراین می بینیم که foo تعریف شده است، اما هیچ چیز در واقع مربوط به خود فراخوانی تابع نیست. این همیشه درست نخواهد بود، اما در مثال بی اهمیت ما اینطور است. برای یافتن اطلاعاتی که به دنبال آن هستیم، باید یک سطح عمیق تر برویم:

>>> inner_frame = tb.tb_next.tb_frame
>>> pprint(inner_frame.f_locals)
{'bar': None, 'foo': 'bar'}

وقتی به TypeError اصلی خود برگردیم، می‌توانید به سرعت درک کنید که چگونه این ممکن است مفید باشد. در آن صورت، با درون نگری فوق متوجه می شویم که function که انتظار می رود یک رشته باشد، در واقع روی NoneType تنظیم شده است. ما می دانیم که چون Sentry این خطا را برای ما دریافت کرده است و به طور خودکار محلی های استک را برای هر فریم استخراج می کند:

نتیجه

این یکی از اولین ویژگی هایی بود که در Sentry ساخته شده است و تا به امروز به عنوان یکی از با ارزش ترین اجزای اشکال زدایی که می باشد که ارائه می دهد و باقی مانده است. در حالی که نمی تواند همیشه جزئیات مورد نیاز برای بازتولید یک استثنا را به شما ارائه دهد، در تجربه ما بسیار نادر است که برای درک زمینه و در نهایت حل مشکل، واقعاً نیاز به ابزار دستی داشته باشیم.

اگر دوست دارید پایتون را به صورت حرفه ای یادبگرید به دوره مقدماتی پایتون و دوره پیشرفته پایتون وب سایت ما استفاده کنید.

#python#پایتون#اشکال_زدایی#دیباگ#خطاها#errors
نظرات ارزشمند شما :

در حال دریافت...

مقاله های مشابه

در حال دریافت...