برنامه نویسی

متغیر و انواع داده

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

  • درس ۵ از ۱۴
  • مقدماتی
  • رایگان، بدون ثبت نام

پنج نوعی که دور هر مقدار می چرخند

نوع تعیین می کند چه کاری با مقدار می شود کرد، نه اینکه مقدار چقدر مهم است.

عدد، صحیح و اعشاریرشته، هر متنی
مقدارهر مقدار یک نوع داردبولی، درست یا غلط
دیکشنری، با کلیدلیست، به ترتیب

این پنج تا همه نوع ها نیستند. پایتون نوع های دیگری هم دارد و در برنامه های بزرگ تر خودتان نوع می سازید؛ ولی ماه های اول تقریبا تمامش با همین پنج تا می گذرد.

آخرین بررسی: فکت ها و نام ابزارهای این درس در همین تاریخ با منابعشان بازبینی شده اند.

متغیر دقیقا چیست و چطور می سازیمش؟

یک متغیر اسمی است که به یک مقدار می دهید. همین. در پایتون ساختنش یک خط است و هیچ کلمه اضافه ای نمی خواهد:

city = "Tehran"
visits = 1200

حالا هر جا city بنویسید، پایتون همان رشته را می گذارد. علامت مساوی هم اینجا معنی ریاضی ندارد؛ نمی گوید این دو برابرند، می گوید این اسم را به آن مقدار بچسبان. برای همین این خط کاملا منطقی است و در ریاضی بی معنی:

visits = visits + 1

تشبیه رایج «متغیر یک جعبه است» تا یک جایی کمک می کند و بعد گمراهتان می کند. دقیق تر این است: متغیر یک برچسب است که روی یک مقدار می چسبد. دو برچسب می توانند روی یک مقدار باشند، و همین است که در بخش پنجم همین درس یک تله واقعی می سازد.

سه قاعده برای اسم گذاری که همان روز اول کار را راحت می کنند. اسم را با حرف شروع کنید و فاصله در آن نگذارید؛ به جای فاصله از خط زیر استفاده کنید، مثل total_price. اسم را کوتاه نکنید تا کمتر تایپ کنید، چون سه هفته بعد tp برای خودتان هم معما است. و اسم انگلیسی بنویسید حتی اگر برنامه فارسی است، چون بقیه کدی که می خوانید همین طور است.

پنج نوعی که کار روزمره با آنها انجام می شود

شکل بالای صفحه پنج نوع را دور یک مرکز چیده و آن مرکز خود «مقدار» است. زبان های دیگر نوع های بیشتری دارند، ولی برای ماه های اول همین پنج تا تقریبا همه کار را می کنند.

price = 120000
rate = 1.09
name = "Sara"
is_paid = True
items = ["pen", "book"]
user = {"name": "Sara", "city": "Tehran"}

عدد صحیح و اعشاری دو نوع جدا هستند و همین جدایی بعدا به دردتان می خورد. رشته متن است و همیشه داخل گیومه. بولی فقط دو مقدار دارد، True و False، و قلب هر شرطی است. لیست چند مقدار به ترتیب است و با شماره به آنها می رسید. و دیکشنری مقدارها را با کلید نگه می دارد نه با شماره، یعنی به جای «سومی» می گویید «شهر».

print(items[0])
print(user["city"])

شمردن از صفر شروع می شود، پس items[0] اولین عضو است. این یکی از آن چیزهایی است که در ماه اول چند بار اذیتتان می کند و بعد عادی می شود.

اگر نمی دانید در دستتان چه چیزی است، از خود پایتون بپرسید. تابع type جواب می دهد و در تمام این درس ابزار اصلی ماست:

print(type(price))
print(type(name))

چرا سن به علاوه یک خطا می دهد؟

این اولین برخورد واقعی هر کسی با نوع داده است و تقریبا همیشه با input شروع می شود. این کد را اجرا کنید:

age = "17"
print(type(age))
print(age + 1)

خط دوم <class 'str'> چاپ می کند و خط سوم برنامه را می خواباند. پیام دقیقش این است، عین چیزی که خودمان اجرا کردیم:

TypeError: can only concatenate str (not "int") to str

ترجمه اش این است: علامت جمع بین دو رشته یعنی چسباندن و بین دو عدد یعنی جمع کردن، ولی بین یک رشته و یک عدد هیچ معنایی ندارد و پایتون حدس نمی زند کدامش را می خواستید. راه حل یک کلمه است:

age = "17"
print(int(age) + 1)

خروجی 18 است. int رشته را عدد صحیح می کند، float عدد اعشاری، و str برعکسش عمل می کند. سه تابع، و نود درصد کار تبدیل با همین سه تا انجام می شود.

یک نکته که بعدا در شرط ها به کارتان می آید: در پایتون رشته و عدد حتی وقتی شبیه هم باشند برابر نیستند. عبارت "7" == 7 جوابش False است. اگر روزی شرطی داشتید که مطمئن بودید باید درست باشد و نبود، اول همین را چک کنید.

و یک هشدار درباره تبدیل: int("abc") خطا می دهد و همین درست است. اگر جایی دیدید که کسی این خطا را با یک try خالی خفه کرده، آن برنامه دیگر خطا نمی دهد ولی از آن به بعد بی سر و صدا داده اشتباه تولید می کند، که بدتر است.

از متنی که کاربر تایپ کرده تا عددی که می شود با آن حساب کرد

قدم سوم را برنامه نویس می نویسد؛ اگر ننویسدش، برنامه در قدم چهارم می افتد.

  1. ۱

    ورودی می رسد

    همیشه رشته است، حتی وقتی کاربر عدد تایپ کرده.

  2. ۲

    تصمیم: این قرار است چه باشد؟

    کد پستی رشته می ماند، سن عدد می شود. فقط شما می دانید کدام.

  3. ۳

    تبدیل با int یا float

    یک کلمه، و همین یک کلمه است که بیشتر تازه کارها جا می اندازند.

  4. ۴

    حالا می شود حساب کرد

    از اینجا به بعد علامت جمع یعنی جمع کردن، نه چسباندن.

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

چرا نتیجه جمع دو اعشار آن چیزی نیست که انتظار دارید؟

این را اجرا کنید و به خروجی نگاه کنید:

print(0.1 + 0.2)

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

برای نمایش، راه حل ساده است:

print(round(0.1 + 0.2, 2))

که 0.3 چاپ می کند. ولی این فقط نمایش را درست می کند و برای پول کافی نیست.

قاعده پول را همین جا یاد بگیرید، چون گران ترین باگ هایی که یک برنامه نویس تازه کار می سازد از همین جنس اند: پول را با اعشار نگه ندارید. دو راه درست دارید. یکی اینکه همه چیز را با کوچک ترین واحد و به صورت عدد صحیح نگه دارید، یعنی به جای تومان اعشاری، ریال یا تومان صحیح. دوم اینکه از ماژول decimal استفاده کنید که برای همین ساخته شده.

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

لیست یا دیکشنری؛ کدام را کجا استفاده کنیم؟

قاعده در یک جمله: اگر چیزها هم جنس اند و ترتیبشان مهم است، لیست؛ اگر هر کدام یک اسم دارند و با اسم صدایشان می زنید، دیکشنری. شکل زیر همین را دو ستونه گذاشته و هیچ کدام هم بر آن یکی برتری ندارد.

ولی یک تله در لیست هست که بیشتر از هر چیز دیگری در این درس، آدم را غافلگیر می کند. یادتان هست گفتیم متغیر برچسب است نه جعبه؟ نتیجه اش این است:

a = [1, 2]
b = a
b.append(3)
print(a)

خروجی [1, 2, 3] است، نه [1, 2]. ما به b چیزی اضافه کردیم و a هم عوض شد، چون هر دو اسم روی یک لیست چسبیده اند و لیست دومی اصلا ساخته نشده. اگر واقعا نسخه جدا می خواهید، باید بگویید:

c = a.copy()
c.append(4)
print(a, c)

که چاپ می کند [1, 2, 3] [1, 2, 3, 4]. حالا دو چیز جدا هستند.

چرا این مهم است؟ چون در برنامه های بزرگ تر، لیست را به یک تابع می دهید، تابع تغییرش می دهد، و شما فکر می کنید نسخه خودتان دست نخورده مانده. این خطا نمی دهد و برنامه هم نمی خوابد؛ فقط داده تان بی سر و صدا عوض می شود. رشته و عدد این رفتار را ندارند، برای همین این تله فقط سراغ لیست و دیکشنری می آید.

لیست و دیکشنری، دو کار متفاوت

هیچ کدام بهتر از آن یکی نیست؛ سوال این است که چیزها اسم دارند یا شماره.

لیست

  • چیزهای هم جنس: نام ها، قیمت ها، فایل ها
  • ترتیب معنی دارد و حفظ می شود
  • با شماره صدایشان می زنید و شمردن از صفر است
  • برای «همه را یکی یکی بردار» ساخته شده

دیکشنری

  • چیزهای مختلف که هر کدام یک اسم دارند
  • ترتیب مهم نیست؛ کلید مهم است
  • با کلید صدایشان می زنید، مثل شهر یا قیمت
  • برای «مشخصات یک چیز» ساخته شده

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

وقتی نوع غلط است ولی هیچ خطایی نمی آید

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

نمونه اول. هر متن این بخش آموزش باید یک آرایه چهار زبانه باشد، یعنی یک ساختار داده با چهار کلید. یک بار در بخش هوش مصنوعی همین سایت به جای آرایه، یک رشته ساده نوشته شده بود. هیچ چیز خطا نداد؛ رشته هم بالاخره یک مقدار است و چاپ می شود. نتیجه اش این بود که صفحه انگلیسی، متن فارسی چاپ می کرد. اشتباه در مقدار نبود، در نوع بود. حالا اعتبارسنج همین بخش قبل از انتشار همین را چک می کند و اگر رشته ببیند می گوید «باید آرایه چهار زبانه باشد، رشته گرفتم».

نمونه دوم و آموزنده تر. قالب این سایت ستون های عددی جدول ها را به میله تبدیل می کند و برای این کار باید عددی را که در متن نوشته شده بخواند. قاعده اولیه این بود که کاما جداکننده هزارگان است. روی صفحه ترکی، «۲,۶ مگابایت» شد بیست و شش و «۸۰,۶ درصد» شد هشتصد و شش، و میله ای کشیده شد که هشت برابر چیزی بود که خود صفحه نوشته بود. علتش هم فرض ما بود: ترکی نقطه را هزارگان و کاما را اعشار می گذارد، درست برعکس فارسی و انگلیسی.

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

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

مسیر سریع با هوش مصنوعی

جایی که نوع داده واقعا وقت می برد، فایل واقعی است: یک اکسل صادراتی، یک خروجی CSV، جوابی که از یک سرویس می آید. مسیر سریع این نیست که مدل داده را برایتان تمیز کند، چون داده ای که نمی بیند را نمی تواند تمیز کند. مسیر سریع این است که از او یک نقشه نوع بگیرید و بعد فهرست چیزهایی که آن نقشه را می شکنند، و آن فهرست را خودتان روی کل فایل امتحان کنید. مدل سریع و ارزان برای این کار کافی است؛ انتخاب فعلی ما در <a class="text-link" href="/ai/">بخش هوش مصنوعی</a> هست.

  1. پنج ردیف واقعی از داده خودتان را بچسبانید، نه توصیفش را. توصیف، همان چیزی است که فکر می کنید در فایل هست؛ ردیف، همان چیزی است که واقعا هست.
  2. برای هر ستون نوع و تبدیل دقیق را بخواهید، با دلیل. جایی که دلیل ضعیف است، معمولا همان جایی است که فرض اشتباهی در کار است.
  3. فهرست مقدارهایی را بخواهید که هر تبدیل را می شکنند: خالی، رقم غیرلاتین، کامای اعشاری، صفر ابتدایی، عدد منفی، فاصله اضافه.
  4. تبدیل را روی کل فایل اجرا کنید و شکست ها را بشمارید. نگاه کردن به پنج ردیف اول همان کاری است که ما با جدول ترکی کردیم و هشت برابر اشتباه از آب درآمد.

نسخه آماده کپی

اینها پنج ردیف واقعی از فایل من هستند، دست نخورده:

{پنج ردیف واقعی، با سرستون ها}

این داده قرار است برای {کاری که می خواهید بکنید} استفاده شود.

سه چیز بده و فقط همین سه تا:

۱. جدول نوع: برای هر ستون، نوع پیشنهادی، تابع تبدیل دقیق، و یک جمله دلیل.
   ستونی که باید رشته بماند را هم صریح بگو و بگو چرا.
۲. برای هر ستون، مقدارهایی که تبدیلش را می شکنند. حتما این موارد را در نظر
   بگیر: سلول خالی، رقم فارسی یا عربی، کاما به عنوان اعشار، صفر ابتدایی،
   عدد منفی، فاصله اضافه در ابتدا و انتها.
۳. کد تبدیل که در صورت شکست، خطا بدهد و ردیف را چاپ کند. خطا را با
   try خالی نگیر و مقدار پیش فرض جای داده خراب نگذار.

فرض نکن کاما جداکننده هزارگان است. اگر مطمئن نیستی، بپرس.

قبل از اعتماد به خروجی: مدل پنج ردیف را دیده، نه پنجاه هزار ردیف را. خرابی ها دقیقا در ردیف هایی هستند که نچسبانده اید، پس نتیجه این کار یک نقشه است نه یک تضمین. دو کار را هم هیچ وقت نکنید: کد تمیز کردن را روی فایل اصلی اجرا نکنید، روی یک کپی اجرا کنید و تعداد ردیف های قبل و بعد را با هم مقایسه کنید؛ و اگر کدی که گرفتید خطا را می بلعد و مقدار پیش فرض می گذارد، همان را عوض کنید. باگی که خطا می دهد یک ساعت وقت می برد و باگی که بی صدا عدد غلط می سازد، ماه ها.

هوش مصنوعی در این کار

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

ابزارهایی که واقعا کمک می کنند

  • Claude برای «این خطا یعنی چه» و برای ساختن فهرست مقدارهایی که یک تبدیل را می شکنند خوب جواب می دهد. ایران در فهرست کشورهای پشتیبانی شده انتروپیک نیست و این را از صفحه خود انتروپیک خوانده ایم.
  • Gemini فارسی را خوب می فهمد، پس وقتی داده تان فارسی است و ارقام فارسی دارد، توضیح دادن مسئله به آن آسان تر است. صفحه خود گوگل می گوید اپ وب جمنای در بیش از دویست و سی کشور و منطقه کار می کند و ایران در آن فهرست نیست.
  • ChatGPT رایج ترین انتخاب و برای همین کار قابل قبول. ما هنوز صفحه ای برایش در بخش هوش مصنوعی نداریم، پس درباره دسترسی و قیمتش اینجا ادعایی نمی کنیم.

کجا نتیجه معکوس می دهد

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

منبع ها: Python docs: built-in types Anthropic: supported countries Google: where Gemini Apps are available

حد این توصیه

پایتون نوع را در زمان اجرا چک می کند، یعنی هیچ چیز قبل از اجرا به شما نمی گوید نوع را اشتباه گرفته اید. زبان های نوع ایستا این را قبل از اجرا می گیرند و همین یک تفاوت واقعی است که این درس جایش نیست. سه چیز هم عمدا اینجا نیامده اند: کلاس و نوع های ساخته خودتان، جزئیات ماژول decimal، و نوع های پایگاه داده که قاعده های خودشان را دارند. و یک نکته که فراموش می شود: دانستن نوع ها جلوی داده اشتباه را نمی گیرد. تنها چیزی که جلویش را می گیرد اجرا کردن برنامه روی داده واقعی است، نه روی سه ردیف نمونه.

از تجربه خود ما

اولین باری که ستون های عددی جدول های این سایت به میله تبدیل شدند، آزمایش روی جدول شاخص های حیاتی وب افتاد و نتیجه اش درس امروز است. در یک ستون «۲.۵ ثانیه» و «۲۰۰ میلی ثانیه» و «۰.۱» کنار هم بودند. کد عددها را درست خوانده بود، ولی میله ها می گفتند INP هشتاد برابر LCP است، جمله ای که آن صفحه هرگز نگفته بود. مشکل نوع نبود، واحد بود: ۲.۵ و ۲۰۰ هر دو عددند و تا وقتی واحدشان یکی نباشد قابل مقایسه نیستند. قاعده ای که بعد از آن گذاشتیم و هنوز سر جایش است: یک ستون فقط وقتی میله می گیرد که باقیمانده متن همه سلول هایش، یعنی واحدشان، دقیقا یکی باشد. عدد بدون واحدش هنوز یک مقدار کامل نیست.

سوال هایی که واقعا پرسیده می شوند

چطور بفهمم داخل یک متغیر چه چیزی هست؟

با print(type(x)) نوعش را می بینید و با print(x) خودش را. این دو خط را در هر جایی که شک دارید اضافه کنید و بعد پاکشان کنید؛ همین ساده ترین و پرکاربردترین ابزار دیباگ در پایتون است. اگر جواب <class 'str'> بود و انتظار عدد داشتید، مسئله را همان لحظه پیدا کرده اید.

پایتون نوع را خودش تشخیص می دهد، پس چرا باید نگرانش باشم؟

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

type hints را از همین اول یاد بگیریم؟

نه در روزهای اول. type hints برای کد بزرگ و کار تیمی ارزش دارد و در برنامه بیست خطی چیزی به شما اضافه نمی کند. یک چیز را هم بدانید تا انتظار اشتباه نداشته باشید: مستندات خود پایتون می گویند این حاشیه نویسی ها در زمان اجرا اعمال نمی شوند و ابزارهای بیرونی مثل type checkerها از آنها استفاده می کنند. یعنی نوشتن age: int جلوی رسیدن یک رشته را نمی گیرد.