احتمالا شما پایتون بلد نیستید - قسمت اول

پیش گفتار

خیلی وقته که تو ذهنم هست همچین چیزی بنویسم. مهم ترین علتش هم اینه که خیلی از اطرفیانم رو میبینم که خودشون رو Python Developer میدونن اما وقتی یک سری سوال ازشون میپرسم، جوابش رو نمیدونند. این سوال‌ها گاهی راجب ورژن های جدید پایتون و چیزهایی هست که میتونه کد مارو تمیز تر و خواناتر بکنه، گاهی راجب ساختمان داده های پایتون و تفاوت هاشونه و گاهی هم راجب بعضی ویژگی های پایتونه که خیلی جاها به کمکمون میاد. پس تصمیم گرفتم این سری مقالات رو شروع کنم که هم خودم موقع نوشتنشون بفهمم که کجا هارو خوب درک نکردم (چیزی رو که نتونی توضیح بدی، نفهمیدی) و هم اینکه خوشحال میشم اگر بتونم بخشی از سواد کم خودم رو به بقیه منتقل کنم.

مقدمه

برای این قسمت میخوام به Type Hints که توی پایتون 3.5 اومد اشاره کنم. طبق تجربه‌ی خود من توی پروژه هایی که خیلی بزرگ میشن و چندین نفر دارن روی اون پروژه به طور همزمان کار میکنن،‌ این ویژگی خیلی کارآمده. با مشخص کردن نوع متغیر، نوع ورودی و خروجی های تابع قدرت های جدیدی به دست میاریم!

  1. کد خوانا تری داریم و اصطلاحا Self-documenting code داریم.
  2. خیلی از ادیتورها میتونن با استفاده از این Type Hint ها، پیشنهادهای دقیق‌تری به شما بدن.
  3. میشه در جاهایی مثل CI/CD Pipeline ها، بر روی کد ها Static Analysis انجام بدیم.
  4. میشه از خیلی از RuntimeError ها پیشگیری کنیم.

تعریف Type Hints

علت اینکه اسم این پروپوزال Type Hints هست اینه که این ویژگی هیچ اجباری به همراه نداره! بزارید با یک مثال بهتر توضیح بدم:

کد بالا همونطور که از اسمش مشخصه، قراره دو تا عدد رو جمع کنه. طبق تعریف تایپ، مقدار های ورودی و خروجی باید از نوع integer باشند. اما اگر به جای یکی از مقادیر ورودی، مقداری از نوع float وارد کنید میبینید که عملیات جمع بدون هیچ مشکلی انجام میشه و خروجی هم float میشه که کاملا برخلاف تعریف ماست.

اگر بخوام واضح تر بگم، این ویژگی به خودی خود قرار نیست کاری انجام بده! و طبق عبارت زیر که از خود پروپوزال برداشته شده، قرار هم نیست که پایتون Static Type بشه.

It should also be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.

پس هدف این پروپوزال چیه؟

  1. سینتکس استاندارد برای type annotations
  2. راحت شدن Static Analysis (آنالیز بدون اجرای کد)
  3. راحت شدن ریفکتور
  4. قابلیت بررسی تایپ ها در زمان اجرا

برای بهره‌مند شدن از این ویژگی‌ها نیاز به استفاده از ابزاری مثل mypy دارید. استفاده ازش میتونه برای کسایی که تا الان فقط با زبان های Dynamic Type کار کردن، شکنجه باشه. اما تاثیر مثبتش رو توی پروژه های بزرگ میتونید ببینید.

خوب! برای ادامه یک مثال میزنم و با استفاده از mypy تستش میکنیم.

همونطور که از روی کد مشخصه، یک کلاس مستطیل داریم که طول و عرض رو به عنوان ورودی میگیره. این دو مقدار باید int باشند. یک تابع هم به نام print_rectangle داریم که کارش، تبدیل کلاس rectangle به string و چاپ کردن اون هست. ورودی این تابع یک شی از نوع کلاس Rectangle هست. در نهایت یک instance از کلاسمون ساختیم و اون رو به تابع print_rectangle دادیم اما همونطوری که میبینید، مقادیر طول و عرض رو به صورت string دادیم که با تایپی که مشخص کردیم همخونی نداره.

قطعه کد رو با استفاده از پایتون اجرا میکنیم. خروجی که مشاهده میکنیم این هست:

Width: 5
Length: 3

خوب،‌ همونطور که میبینید،‌ کد ما بدون هیچ مشکلی اجرا شد! چرا؟ چون هیچ قسمتی از کد نیازی به int بودن این مقادیر نداشت، مقادیر string ما بدون هیچ مشکلی داخل کلاس قرار گرفتند و چاپ شدند. حالا این کد رو با استفاده از mypy اجرا میکنیم. خروجی که مشاهده میکنیم این هست:

01-type_hints.py:14: error: Argument 1 to "Rectangle" has incompatible type "str" expected "int"
01-type_hints.py:14: error: Argument 2 to "Rectangle" has incompatible type "str" expected "int"
Found 2 errors in 1 file (checked 1 source file)

همونطور که دیدید، چون int بودن این مقادیر توی این مثال برای پایتون مهم نبود،‌ موقع اجرا به مشکلی نخوردیم. حالا مثال رو به شکل زیر تغییر میدم:

حالا یه تابع محیط داریم که براش مهمه که مقدار طول و عرض قابل ضرب شدن باشه (هر دو عدد باشند). اگر این کد رو با پایتون اجرا کنیم، TypeError میگیریم. این مشکل رو میتونستیم از اول با یکبار اجرا کردن کد با mypy متوجه بشیم و اصلاحات لازم رو انجام بدیم.

این یک نمونه ساده از Type Hint ها بود. در کد های دیگه ممکنه گاهی برای نوع تایپ گنگ بشیم. برای نمونه، همین مثالی که زدم، تابع print_rectangle خروجی نداشت، برای همین خروجی تابع رو None مشخص کردیم (اصطلاحا تابع void هست).

یک سری از تایپ ها برای تعریف نیاز به import شدن دارند. قطعه کد زیر از داخل پروپوزال برداشته شده و مثال خوبی برای این تایپ هاست.

کلاسی ساخته شده به نام SymbolTable که از Dict ارث بری شده. اما یک Dict خاص که کلید های اون از نوع string و مقادیر اون لیستی از نوع Node هستند. میبینید که این تایپ ها از کتابخونه‌ی typing داخل کد import شدند. تابعی مثل lookup که وظیفه پیدا کردن یک کلید خاص داخل این دیکشنری رو داره، الزاما همیشه اون کلید رو پیدا نمیکنه. پس خروجی این تابع نمیتونه از نوع Node باشه پس در عوض خروجی تابع رو از نوع Optional[Node] قرار میدیم که به این معناست که خروجی این تابع ممکن هست None یا یک Node باشه.

TL;DR

  1. مشخص کردن تایپ به معنای Static Type شدن نیست و پایتون Dynamic Type خواهد ماند.
  2. قدرت واقعی این قابلیت در کد های بزرگ و برای جلوگیری از بسیاری از RuntimeError هاست.
  3. برای static analysis میتونید از mypy استفاده کنید.
  4. برای مشخص کردن تایپ از نوع List یا Dict لازم است از کتابخانه داخلی پایتون به نام typing استفاده کنید.