Hacker News

مقایسه جستجوگر نوع پایتون: استنتاج ظرف خالی

نظرات

1 min read Via pyrefly.org

Mewayz Team

Editorial Team

Hacker News

چرا ظروف خالی چک کننده های نوع پایتون را می شکند — و چه کاری می توانید در مورد آن انجام دهید

سیستم تایپ تدریجی پایتون از زمانی که PEP 484 نکات نوع را در سال 2015 معرفی کرد، به طور قابل توجهی رشد کرده است. امروزه، میلیون‌ها توسعه‌دهنده به چک‌کننده‌های نوع ثابت تکیه می‌کنند تا باگ‌ها را قبل از شروع تولید پیدا کنند. اما گوشه ای ظریف و خسته کننده از سیستم نوع وجود دارد که هنوز حتی مهندسان باتجربه را به خود مشغول می کند: یک ظرف خالی چه نوع دارد؟ وقتی x = [] را بدون حاشیه‌نویسی می‌نویسید، جستجوگر نوع شما باید حدس بزند - و چک‌کننده‌های مختلف به طور متفاوتی حدس می‌زنند. این واگرایی مشکلات واقعی را برای تیم‌هایی ایجاد می‌کند که از پایگاه‌های کد بزرگ نگهداری می‌کنند، جایی که تعویض یا ترکیب چک‌کننده‌های نوع می‌تواند صدها خطای غیرمنتظره را یک شبه نشان دهد.

این مقاله به بررسی این موضوع می‌پردازد که چگونه چهار چک‌کننده اصلی Python - mypy، pyright، pytype، و pyre - استنتاج محفظه خالی را مدیریت می‌کنند، چرا مخالف هستند و چه استراتژی‌های عملی را می‌توانید برای نوشتن Python ایمن بدون توجه به انتخاب ابزار خود اتخاذ کنید.

مشکل اصلی: ظروف خالی ذاتا مبهم هستند

این خط بی ضرر پایتون را در نظر بگیرید: نتایج = []. آیا نتایج یک لیست[int] است؟ یک لیست[str]؟ یک لیست[dict[str, Any]]؟ بدون زمینه اضافی، واقعاً هیچ راهی برای دانستن وجود ندارد. زمان اجرای پایتون اهمیتی نمی‌دهد - لیست‌ها طبیعتاً ناهمگن هستند - اما بررسی‌کننده‌های نوع استاتیک برای انجام کارشان باید یک نوع مشخص به هر متغیر اختصاص دهند. این یک تنش اساسی بین انعطاف پذیری پویا پایتون و تضمین هایی که تجزیه و تحلیل استاتیک سعی در ارائه آن دارد ایجاد می کند.

مشکل با دیکشنری ها و مجموعه ها ترکیب می شود. یک {} خالی در واقع به عنوان یک دیکت تجزیه می‌شود، نه یک مجموعه، که ابهام نحوی را در بالای ابهام سطح نوع اضافه می‌کند. و ظروف تو در تو - به پیش‌فرض(لیست) یا نتایج = {k: [] برای k در کلیدها فکر کنید — موتورهای استنتاج را به حد خود برسانید. هر نوع جستجوگر اکتشافی خاص خود را توسعه داده است، و تفاوت‌ها بیشتر از آن چیزی است که بیشتر توسعه‌دهندگان متوجه می‌شوند.

در سیستم‌های تولیدی که بارهای کاری واقعی را پردازش می‌کنند - چه یک CRM که سوابق مشتری را مدیریت می‌کند، یک ماژول صورت‌حساب که موارد خط تولید می‌کند یا یک خط لوله تجزیه و تحلیل معیارهای جمع‌آوری - ظروف خالی دائماً به عنوان الگوهای اولیه ظاهر می‌شوند. اشتباه گرفتن انواع آنها فقط اخطارهای مربوط به لینتر را ایجاد نمی کند. می‌تواند باگ‌های واقعی را که به زمان اجرا منتقل می‌شوند، بپوشاند.

Mypy: استنتاج به تعویق افتاده با هر یک از موارد ضمنی

Mypy، قدیمی‌ترین و پرکاربردترین جستجوگر نوع پایتون، رویکرد نسبتاً ملایمی برای ظروف خالی دارد. وقتی در محدوده تابع با x = [] مواجه می‌شود، سعی می‌کند تصمیم‌گیری نوع را به تعویق بیندازد و نوع عنصر را از استفاده بعدی استنتاج کند. اگر x = [] و سپس x.append(42) را بنویسید، mypy list[int] را استنباط می‌کند. این استراتژی "پیوستن" به طرز شگفت آوری برای موارد ساده ای که کانتینر در همان محدوده پر شده است، به خوبی کار می کند.

با این حال، رفتار mypy بسته به تنظیمات زمینه و سختگیری به طور چشمگیری تغییر می کند. در محدوده ماژول (کد سطح بالا)، یا زمانی که کانتینر قبل از پر شدن به یک تابع دیگر منتقل می‌شود، mypy اغلب به list[Any] برمی‌گردد. در زیر پرچم --strict، این یک خطا را ایجاد می‌کند، اما در حالت پیش‌فرض بی‌صدا می‌گذرد. این بدان معناست که تیم‌هایی که mypy را بدون حالت سخت‌گیرانه اجرا می‌کنند، می‌توانند ده‌ها کانتینر با تایپ ضمنی را جمع‌آوری کنند که به عنوان دریچه‌های فرار از سیستم نوع عمل می‌کنند و هدف آن را شکست می‌دهند.

یک رفتار بسیار ظریف: نسخه‌های mypy قبل از 0.990 گاهی اوقات لیست[ناشناس] را به صورت داخلی استنباط می‌کنند و سپس در هنگام انتساب به لیست[هرگونه] گسترده می‌شوند. پس از 0.990، استنتاج سخت‌تر شد، اما این تغییر، تعداد شگفت‌انگیزی از پایگاه‌های کد دنیای واقعی را شکست که بدون اینکه متوجه باشند، بر رفتار مجاز متکی بودند. این یک موضوع تکراری است - تغییرات در استنتاج ظرف خالی یکی از مخرب‌ترین به‌روزرسانی‌های بررسی‌کننده نوع هستند، زیرا الگوها بسیار فراگیر هستند.

Pyright: Strict Inference و نوع "Unknown"

Pyright که توسط مایکروسافت توسعه داده شده است و Pylance را در VS Code تقویت می کند، یک موضع فلسفی اساسا متفاوت دارد. به جای بازگشت بی سر و صدا به هر، حقوق حقوقی بین ناشناخته (نوعی که هنوز مشخص نشده است) و هر (یک انصراف صریح از بررسی نوع) تمایز قائل می شود. وقتی x = [] را در حالت سخت‌گیرانه حق نشر می‌نویسید، لیست[ناشناس] را استنباط می‌کند و یک تشخیص را گزارش می‌کند و شما را مجبور به ارائه حاشیه‌نویسی می‌کند.

Pyright همچنین در مورد تحریک کردن در محدوده تهاجمی‌تر است. اگر بنویسید:

  • x = [] به دنبال آن x.append("hello") — pyright استنباط می کند list[str]
  • x = [] به دنبال آن x.append(1) سپس x.append("hello") — pyright استنباط می کند list[int | str]
  • x = [] مستقیماً به تابعی منتقل می‌شود که انتظار دارد list[int] — pyright list[int] را از متن تماس سایت نتیجه می‌گیرد
  • x = [] از یک تابع بدون حاشیه نویسی نوع برگشتی برگردانده شد — حقوق نشر به جای حدس زدن، خطا را گزارش می کند

این استنتاج دوطرفه (با استفاده از هر دو نوع استفاده بعدی و انواع مورد انتظار از سایت‌های تماس) حق نشر را به طور قابل توجهی دقیق‌تر از mypy برای ظروف خالی می‌کند. مبادله پرحرفی است: بر اساس تجزیه و تحلیل چندین گزارش مهاجرت منبع باز، حالت سخت pyright تقریباً 30-40٪ مسائل بیشتر را در یک پایگاه کد معمولی بدون حاشیه نویسی در مقایسه با حالت سخت mypy نشان می دهد. برای تیم‌هایی که سیستم‌های باطنی پیچیده را می‌سازند - مثلاً پلتفرمی که 207 ماژول به هم پیوسته را مدیریت می‌کند که شامل CRM، حقوق و دستمزد و تجزیه و تحلیل می‌شود - سختگیری pyright، ناهماهنگی‌های ظریف رابط را پیدا می‌کند که استنباط ملایم آن را از دست می‌دهد.

Pytype و Pyre: The Less Traveled Roads

pytype گوگل شاید عملگراترین رویکرد را داشته باشد. به جای نیاز به حاشیه نویسی یا بازگشت به Any، pytype از تجزیه و تحلیل کل برنامه برای ردیابی نحوه استفاده از یک ظرف در سراسر مرزهای تابع استفاده می کند. اگر یک لیست خالی در یک تابع ایجاد کنید و آن را به دیگری ارسال کنید که اعداد صحیح را اضافه می کند، pytype اغلب می تواند بدون هیچ حاشیه نویسی list[int] را استنتاج کند. این استنتاج متقابل تابعی از نظر محاسباتی گران است - pytype به طور قابل توجهی کندتر از mypy یا pyright در پایگاه‌های کد بزرگ است - اما در کدهای بدون حاشیه‌نویسی مثبت کاذب کمتری ایجاد می‌کند.

Pytype همچنین مفهوم "انواع جزئی" را برای ظروف خالی معرفی می کند. یک [] تازه ایجاد شده یک نوع جزئی دریافت می‌کند که به تدریج با استفاده بیشتر جستجوگر اصلاح می‌شود. این از نظر مفهومی زیبا است، اما می‌تواند پیام‌های خطای گیج‌کننده‌ای ایجاد کند که نوع جزئی آن به طور کامل قابل حل نباشد، مانند زمانی که یک ظرف خالی از چندین عملکرد عبور می‌کند بدون اینکه هرگز پر شود.

💡 DID YOU KNOW?

Mewayz replaces 8+ business tools in one platform

CRM · Invoicing · HR · Projects · Booking · eCommerce · POS · Analytics. Free forever plan available.

Start Free →

پیر متا، در همین حال، به رفتار mypy نزدیک‌تر است، اما با پیش‌فرض‌های سخت‌تر. Pyre x = [] را به عنوان لیست[ناشناخته] در نظر می‌گیرد و در بیشتر زمینه‌ها به حاشیه‌نویسی نیاز دارد. جایی که pyre خود را متمایز می‌کند، مدیریت اصطلاحات لغت‌نامه خالی که به‌عنوان کوارگ استفاده می‌شود است - یک الگوی رایج در چارچوب‌های وب. Pyre دارای منطق مورد خاص برای استنتاج انواع دیکشنری از زمینه‌های آرگومان کلیدواژه است و بار حاشیه‌نویسی را در پایگاه‌های کد سنگین فریمورک کاهش می‌دهد. با توجه به اینکه اکثر برنامه‌های کاربردی وب مدرن شامل استفاده شدید از بازکردن دیکشنری برای پیکربندی و رسیدگی به درخواست‌ها می‌شوند، این عمل‌گرایی سودمند است.

تأثیر دنیای واقعی: هنگامی که واگرایی استنتاج گاز می گیرد

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

خطرناک‌ترین ظروف خالی، پرچم‌های چک‌کننده‌های نوع نیستند - آن‌هایی هستند که بی‌صدا با نوع استنباط‌شده Any عبور می‌کنند و به داده‌های ناسازگار اجازه می‌دهند بدون هشدار جمع شوند تا زمانی که یک تابع downstream در زمان اجرا با یک TypeError که ردیابی آن تقریباً غیرممکن است، در زمان اجرا خراب شود.

یک مثال عینی: تیمی در یک راه اندازی فین تک گزارش داد که سه روز را برای رفع اشکال یک مشکل تولید صرف کرده است که در آن یک لیست خالی که در یک تابع پردازش پرداخت مقداردهی اولیه شده است، توسط mypy به عنوان list[Any] استنباط شده است. فهرست قرار بود حاوی اشیاء اعشاری برای مقادیر ارز باشد، اما یک مسیر کد به جای آن مقادیر شناور را اضافه می‌کرد. استنباط ملایم مایپی بی سر و صدا اجازه داد. این اشکال تنها زمانی ظاهر شد که خطاهای گرد کردن در محاسبات شناور باعث اختلاف 0.01 دلار در دسته ای از 12000 فاکتور شد. اگر آنها از pyright در حالت سخت‌گیرانه استفاده می‌کردند، یا به سادگی فهرست خالی را به‌عنوان لیست[اعشاری] حاشیه‌نویسی می‌کردند، این اشکال در زمان توسعه شناسایی می‌شد.

در Mewayz، جایی که پلتفرم صورتحساب، محاسبات حقوق و دستمزد و تجزیه و تحلیل مالی را در بیش از 138000 حساب کاربری پردازش می‌کند، این نوع شکاف ایمنی نظری نیست - این تفاوت بین اجرای صحیح حقوق و دستمزد و محاسبه مجدد پرهزینه است. نظم و انضباط دقیق تایپ در مورد مقداردهی اولیه کانتینر یکی از آن شیوه‌های مهندسی «خسته‌کننده» است که از حوادث هیجان‌انگیز تولید جلوگیری می‌کند.

بهترین شیوه ها برای راه اندازی کانتینر دفاعی

صرف نظر از اینکه تیم شما از کدام نوع چک‌کننده استفاده می‌کند، استراتژی‌های مشخصی برای حذف کامل ابهام ظرف خالی وجود دارد. هدف این است که هرگز به استنتاج برای ظروف خالی اتکا نکنید — نوع را صریح کنید تا کد شما در همه چک‌ها قابل حمل باشد و از تغییرات رفتار استنتاج بین نسخه‌ها مصون باشد.

  1. همیشه متغیرهای ظرف خالی را حاشیه نویسی کنید. به جای نتایج = []، نتایج: list[int] = [] را بنویسید. هزینه پرحرفی جزئی در مقایسه با زمان صرفه جویی در رفع اشکال ناچیز است. این روش واحد تقریباً 80٪ از مسائل استنتاج ظرف خالی را حذف می کند.
  2. از توابع کارخانه برای ظروف پیچیده استفاده کنید. به جای cache = {}، تابعی مانند def make_cache() -> dict[str, list[UserRecord]] بنویسید: return {}. حاشیه نویسی نوع بازگشتی، نوع مورد نظر را بدون ابهام و مستندسازی می کند.
  3. سازنده‌های تایپ‌شده را برای انواع غیر پیش پا افتاده به حروف اللفظی ترجیح دهید. بجای تکیه بر استنتاج درک مجموعه، موارد را بنویسید: set[int] = set(). برای defaultdict و Counter، همیشه پارامتر نوع را ارائه دهید: counts: Counter[str] = Counter().
  4. حالت سختگیرانه جستجوگر نوع خود را برای کد جدید پیکربندی کنید. هم mypy و هم pyright از پیکربندی هر فایل یا هر دایرکتوری پشتیبانی می‌کنند. بررسی دقیق ماژول‌های جدید را فعال کنید و به تدریج کدهای قدیمی را منتقل کنید. این از تجمع ظروف جدید با تایپ ضمنی جلوگیری می کند.
  5. مقایسه جستجوگر نوع را به خط لوله CI خود اضافه کنید. اجرای هر دو mypy و pyright در پایگاه کد شما واگرایی استنتاج را زودتر پیدا می کند. اگر یک الگو از یک بررسی عبور کند اما دیگری را شکست دهد، این علامتی است که نوع آن به اندازه کافی واضح نیست.

تصویر بزرگتر: بررسی تایپ به عنوان تمرین تیمی

استنتاج ظرف خالی در نهایت یک چالش بزرگتر در سیستم نوع پایتون است: تنش بین راحتی و ایمنی. فلسفه پایتون مبنی بر "ما همه بزرگسالان راضی هستیم" برای نمونه سازی و اسکریپت ها به زیبایی کار می کند، اما سیستم های تولیدی که به هزاران کاربر خدمات رسانی می کنند به تضمین های قوی تری نیاز دارند. این واقعیت که چهار بررسی‌کننده نوع اصلی در مورد چیزی به همان ابتدایی نوع [] اختلاف نظر دارند، نشان می‌دهد که اکوسیستم تایپ پایتون هنوز در حال بلوغ است.

برای تیم‌های مهندسی که پلتفرم‌های پیچیده‌ای را می‌سازند - چه در حال مدیریت تعداد انگشت شماری از میکروسرویس‌ها یا یک سیستم یکپارچه با صدها ماژول به هم پیوسته مانند سیستم‌عامل تجاری Mewayz هستید - توصیه‌های عملی ساده است: برای ظروف خالی به استنتاج اتکا نکنید، یک بررسی‌کننده نوع انتخاب کنید و آن را به‌شدت پیکربندی کنید، و یادداشت‌های تایپ که اتفاق می‌افتد را به عنوان سند در نظر بگیرید. پنج دقیقه ای که صرف نوشتن فهرست[فاکتور] به جای می‌کنید، ساعت‌ها از اشکال‌زدایی شما صرفه‌جویی می‌کند.

از آنجایی که PEP 696 (پارامترهای نوع پیش‌فرض) و PEP 695 (نحوه نحو پارامتر نوع) همچنان در نسخه‌های جدیدتر پایتون قرار می‌گیرند، ارگونومی تایپ صریح همچنان بهبود می‌یابد. شکاف بین پایتون «حاشیه‌نویسی» و «بی حاشیه» کمتر خواهد شد. اما تا آن روز، انواع کانتینرهای صریح یکی از بالاترین روش‌های بازگشت سرمایه در جعبه ابزار توسعه‌دهنده پایتون باقی می‌ماند - یک رشته کوچک که در هر ماژول، هر سرعت و هر استقرار تولید سود مرکب می‌پردازد.

امروز سیستم عامل کسب و کار خود را بسازید

از فریلنسرها گرفته تا آژانس‌ها، Mewayz بیش از 138000 کسب‌وکار را با 207 ماژول یکپارچه قدرت می‌دهد. رایگان شروع کنید، وقتی رشد کردید ارتقا دهید.

رایگان ایجاد کنید