پایتون یک زبانی هست که همه از آن استفاده می کنیم و کمتر کسی وجود دارد که آن را بلد نباشد. در 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 ساخته شده است و تا به امروز به عنوان یکی از با ارزش ترین اجزای اشکال زدایی که می باشد که ارائه می دهد و باقی مانده است. در حالی که نمی تواند همیشه جزئیات مورد نیاز برای بازتولید یک استثنا را به شما ارائه دهد، در تجربه ما بسیار نادر است که برای درک زمینه و در نهایت حل مشکل، واقعاً نیاز به ابزار دستی داشته باشیم.
اگر دوست دارید پایتون را به صورت حرفه ای یادبگرید به دوره مقدماتی پایتون و دوره پیشرفته پایتون وب سایت ما استفاده کنید.