یکی از معمولترین مشکلاتی که در پایتون وجود دارد این است که نوع دادههای آن دینامیک است. این بدین معنی است که شما متغیرها را بدون اینکه نوع داده آنها را مشخص کنید تعریف میکنید. نوع متغیرها بر حسب اینکه چه نوع دادهای به آنها پاس داده شود به صورت اتوماتیک تعیین میشود. به عنوان مثال:
president_name = "Franklin Delano Roosevelt" print(type(president_name)) # Result is: # <class 'str'>
در مثال بالا متغیر president_name به عنوان یک str ساخته شده است زیرا ما به آن یک رشته نسبت دادهایم. اما پایتون تا زمانی که آن خط از کد را اجرا نکند نمیداند این متغیر از نوع رشته است.
اما در مقام مقایسه زبانی مثل جاوا استاتیک-تایپ است. برای ساخت همین متغیر در جاوا شما باید نوع متغیر را مشخصا با کلمه String مشخص کنید:
String president_name = "Franklin Delano Roosevelt";
از آنجا که جاوا از قبل میداند که متغیر president_name از نوع String است اگر شما به آن یک مقدار اشتباه مثلا یک عدد را نسبت دهید کامپایلر به شما گزارش خطا نشان خواهد داد.
چرا نوع داده مهم است؟
معمولا نوشتن کد به حالت دینامیک-تایپ مانند حالتی که در پایتون نوشته میشود سریعتر است زیرا شما مجبور نیستید که نوع متغیرها را اعلام کنید، اما هنگامی که کد شما شروع به بزرگ شدن میکند با تعداد زیادی خطای زمان اجرا روبرو خواهید شد، خطاهایی که در زبانهای استاتیک-تایپ به واسطه استاتیک-تایپ بودن از آنها جلوگیری میشود.
در مثال زیر یکی از رایجترین خطاها در زبان پایتون را می بینید:
def get_first_name(full_name): return full_name.split(" ")[0] fallback_name = { "first_name": "UserFirstName", "last_name": "UserLastName" } raw_name = input("Please enter your name: ") first_name = get_first_name(raw_name) # If the user didn't type anything in, use the fallback name if not first_name: first_name = get_first_name(fallback_name) print(f"Hi, {first_name}!")
همهی آنچه که ما در کد بالا انجام دادهایم این است که از کاربر اسمش را پرسیدهایم و عبارت "Hi, <first name>!" را چاپ کردهایم، و اگر کاربر چیزی را وارد نکند عبارت “Hi, UserFirstName!” را چاپ میکنیم.
این برنامه تا زمانی که کاربر چیزی را وارد کند به خوبی کار میکند. اما اگر کاربر مقدار ورودی را خالی بگذارد برنامه کرش میکند:
Traceback (most recent call last): File "test.py", line 14, in <module> first_name = get_first_name(fallback_name) File "test.py", line 2, in get_first_name return full_name.split(" ")[0] AttributeError: 'dict' object has no attribute 'split'
مشکل آنجاست که متغیر fallback_name یک رشته نیست بلکه یک دیکشنری است و از آنجا که نوع داده دیکشنری تابعی به نام ()split. ندارد صدا زدن تابع get_first_name بر روی fallback_name با شکست مواجه میشود.
این یک خطای ساده و آشکار است اما آنچه که این خطا را مشکلساز میکند این است که شما هیچوقت از وجود آن آگاه نمیشوید تا اینکه کاربر برنامه را اجرا کند و بر حسب تصادف جای نام را خالی بگذارد. شما ممکن است برنامه را صدها بار تست کنید و هیچوقت از وجود همچین باگ سادهای مطلع نشوید زیرا همیشه اسم را وارد میکنید.
استفاده از مدل استاتیک-تایپ از همچین باگی جلوگیری میکند. قبل از آنکه حتی برنامهتان را اجرا کنید مدل استاتیک-تایپ به شما میگوید که شما نمیتوانید متغیر fallback_name را به تابع get_first_name() پاس دهید زیرا این تابع یک رشته میگیرد ولی شما به آن یک دیکشنری فرستادهاید. حتی ممکن است ویرایشگر کد شما هنگامی که در حال نوشتن هستید این خطا را هایلایت کند.
هنگامی که این نوع از خطا در پایتون رخ میدهد معمولا مانند این مثال در یک تابع ساده نیست، بلکه معمولا چند لایه از ماژولها را رد می کند و نوع داده ارسالی تفاوت کمی با نوع داده مورد قبول دارد. برای دیباگ همچین خطایی شما مجبورید ورودی کاربر را بازسازی کنید و کشف کنید که کجا ممکن است خطا اتفاق افتاده باشد. مقدار بسیار زیادی زمان برای دیباگ کردن همچین خطای قابل پیشگیریی تلف میشود.
خبر خوب اینکه شما حالا میتوانید اگر بخواهید در پایتون از مدل استاتیک-تایپ استفاده کنید، و بالاخره به نسخه ۳.۶ پایتون یک سینتکس معقول برای اعلان نوع دادهها اضافه شد.
ترمیم برنامه مشکلدار بالا
اجازه دهید برنامه مثال بالا را با اعلان نوع متغیرها و ورودی/خروجی توابع دوباره نویسی کنیم:
from typing import Dict def get_first_name(full_name: str) -> str: return full_name.split(" ")[0] fallback_name: Dict[str, str] = { "first_name": "UserFirstName", "last_name": "UserLastName" } raw_name: str = input("Please enter your name: ") first_name: str = get_first_name(raw_name) # If the user didn't type anything in, use the fallback name if not first_name: first_name = get_first_name(fallback_name) print(f"Hi, {first_name}!")
در پایتون ۳.۶ شما نوع یک متغیر را اینجوری اعلام میکنید:
variable_name: type
و اگر متغیر شما مقدار اولیه بگیرد اعلان آن اینجوری خواهد بود:
my_string: str = "My String Value"
و همچنین نوع ورودی و خروجی توابع را اینجوری اعلام میکنید:
def function_name(parameter1: type) -> return_type:
این کار بسیار ساده است، فقط یک دستکاری کوچک در سینتکس نرمال پایتون.
حالا که نوع دادهها را در مثال بالا مشخص کردیم اجازه دهید ببینیم با اجرای برنامه چک کننده نوع چه اتفاقی میافتد:
$ mypy typing_test.py test.py:16: error: Argument 1 to "get_first_name" has incompatible type Dict[str, str]; expected "str"
برنامه چک کننده نوع بدون اینکه حتی برنامه را اجرا کند میفهمد که خط ۱۶ از کد مشکل دارد. شما میتوانید این مشکل را همین الان رفع کنید بدون اینکه مجبور باشید صبر کنید تا یک کاربر سه ماه بعد آن را کشف کند.
و اگر شما از یک IDE مثل PyCharm استفاده میکنید این IDE قبل از اینکه حتی برنامهتان را اجرا کنید به صورت خودکار نوع متغیرها را چک میکند و به شما نشان میدهد کجای کدتان مشکل دارد:
مثالهای بیشتری از سینتکس پایتون ۳.۶ برای اعلان نوع داده
اعلان نوع str یا int راحت است، دردسر واقعی وقتی پیش میآید که بخواهیم نوع داده یک متغیر پیچیدهتر مثل یک لیست تو در تو یا یک دیکشنری تو در تو را اعلام کنیم. خوشبختانه سینتکس پایتون ۳.۶ برای این کار چندان هم بد نیست.
الگوی پایه برای این کار این است که نام نوع داده پیچیده را از ماژول typing وارد کنیم و نوع دادههای داخلیتر را داخل براکت بنویسیم. معمولترین نوع دادههای پیچیدهای که شما استفاده میکنید List,Dict و Tuple هستند. در زیر یک مثال از استفاده از این نوع دادهها را میبینید:
from typing import Dict, List # A dictionary where the keys are strings and the values are ints name_counts: Dict[str, int] = { "Adam": 10, "Guido": 12 } # A list of integers numbers: List[int] = [1, 2, 3, 4, 5, 6] # A list that holds dicts that each hold a string key / int value list_of_dicts: List[Dict[str, int]] = [ {"key1": 1}, {"key2": 2} ]
تاپلها قدری ویژهتر هستند زیرا اجازه میدهند نوع هر کدام از عناصرشان را به صورت جداگانه اعلان کنید:
from typing import Tuple my_data: Tuple[str, int, float] = ("Adam", 10, 5.7)
شما همچنی می توانید با اختصاص دادن یک نام برای اعلان نوع دادههای پیچیدهتر برای آنها نام مستعار (alias) تعیین کنید:
from typing import List, Tuple LatLngVector = List[Tuple[float, float]] points: LatLngVector = [ (25.91375, -60.15503), (-11.01983, -166.48477), (-11.01983, -166.48477) ]
بعضی وقتا تابع شما باید آنقدر انعطافپذیر باشد که بتواند چندین نوع داده مختلف را اداره کند یا با هر نوع دادهای کار کند، در این مواقع شما میتوانید از نوع داده Union به جای چندین نوع داده استفاده کنید یا از نوع داده Any برای پذیرش همهی انواع دادهها استفاده کنید.
پایتون ۳.۶ همچین از بعضی انواع دادههای سطح بالاتر که ممکن است در دیگر زبانها دیده باشید پشتیبانی میکند، مثل جنریکها و نوع دادههای تعریف شده توسط کاربر.
اجرای برنامه چک کننده نوع
هر چند که پایتون ۳.۶ این سینتکس جدید برای اعلان نوع دادهها را در اختیار شما میگذار اما خودش فعلا هیچ کاری با این اعلان نوع انجام نمیدهد. برای چک کردن نوع دادهها دو راه دارید:
۱. برنامه متنباز mypy را دانلود کنید و با استفاده از آن هنگام توسعه یا تست کدتان نوع دادهها را چک کنید.
۲. از چک کننده توکار PyCharm استفاده کنید. یا اگر از ویرایشگر دیگری مثل Atom استفاده میکنید پلاگین مخصوص این کار را برایش نصب کنید.
من توصیه میکنم از هر دوی اینها با هم استفاده کنید زیرا PyCharm و mypy از دو روش مختلف برای چک کردن نوع دادهها استفاده می کنند، در نتیجه هر کدام ممکن است مشکلی را کشف کند که دیگری نتواند ببیند. شما میتوانید از PyCharm برای کشف خطاها در حین نوشتن کد استفاده کنید و از mypy در هنگام تست کد به عنوان بازبینی نهایی.
آیا لازم است از این به بعد کدهای پایتون را با اعلان نوع بنویسیم؟
اعلان نوع در پایتون پدیده خیلی تازهای است که فقط در پایتون ۳.۶ به طور کامل کار میکند. اگر شما کد دارای اعلان نوع را به یک توسعهدهنده پایتون دیگر نشان دهید احتمالا فکر میکند دیوانه شدهاید، همچنین برنامه mypy هنوز در حال توسعه است و به نظر نمیرسد به این زودیها به نسخه پایدار برسد. در نتیجه کمی زود است که همهی پروژههایتان را به این سمت سوق دهید. اما اگر بر روی پروژه جدیدی کار می کنید که میتوانید پایتون ۳.۶ را به عنوان نیازمندی حداقل آن تعیین کنید ممکن است ارزشش را داشته باشد که اعلان نوع داده را در کدتان تجربه کنید، این عمل پتانسیلش را دارد که از تعداد زیادی باگ در برنامهتان جلوگیری کند.
گزینه خوبی که وجود دارد این است که شما میتوانید کدی که در آن اعلان نوع وجود دارد را با کدی که اعلان نوع ندارد ترکیب کنید. یعنی اعلان نوع یک چیز «همه یا هیچ» نیست. در نتیجه شما میتوانید با نوشتن نوع دادهها در قسمتهایی از کد که مهمتر هستند شروع کنید، بدون اینکه همهی کدتان را تغییر دهید. از آنجا که پایتون هنگام اجرا هیچ کاری با این اعلان نوع ندارد در نتیجه اعلان نوع برای کد شما هیچ مشکلی پیش نمیآورد.
پایان!
منبع: +