همه چیز در پایتون یک شیء محسوب می شود. یعنی چه؟ به کد زیر توجه کنید:
print(type(None)) print(type(True)) print(type(5)) print(type([])) print(type({})) print(type(()))
ما قبلا یاد گرفتیم که دستور type نوع داده را برای ما مشخص می کرد. من این کدها را درون visual studio code می نویسم. به محض اینکه یک فایل پایتون (فایلی با پسوند py) را در visual studio code باز کنید از شما خواسته می شود افزونه هایی را نصب کنید (مثل pylint و امثال آن) حتما این کار را انجام بدهید. برای اجرای این فایل نیز می توانیم در vscode روی صفحه کلیک راست کرده و گزینه Run Python File in Terminal را کلیک کنیم. با این کار ترمینال درون vscode باز شده، فایل ما (کدهای بالا) در آن اجرا شده و نتیجه زیر را می گیریم:
<class 'NoneType'> <class 'bool'> <class 'int'> <class 'list'> <class 'dict'> <class 'tuple'>
همانطور که می بینید ما فقط یک رشته ساده دریافت نمی کنیم بلکه تمام نوع داده ها درون یک کلاس (class) قرار گرفته اند! بنابراین همه چیز در پایتون در قالب کلاس ها نوشته شده است و به عبارتی همه چیز یک شیء است. احتمالا متوجه حرف من نمی شوید و طبیعی است؛ شما هنوز نمی دانید شیء و کلاس چه هستند. برای درک این موضوع باید شروع به ساخت یک کلاس کنیم، در غیر این صورت درک شیء بودن پایتون غیرممکن است.
تا این قسمت از آموزش ما کدهای خودمان را به صورت عادی و خط به خط می نوشتیم. به این روش برنامه نویسی procedural یا «رویه ای» می گویند. یعنی هر خط کد اجرا شده و سپس به خط بعدی می رویم. مشکل اینجاست که در دنیای واقعی استفاده از چنین روشی تقریبا ممکن نیست. چرا؟ به دلیل اینکه در دنیای واقعی هزاران یا صدها هزار خط کد داریم و این کدها در صدها یا هزاران فایل مختلف قرار دارند بنابراین اجرا شدن خط به خط آن ها تقریبا غیرممکن است. ما باید کدهای خود را به روشی بنویسیم که مدیریت آن ها را آسان تر کند و به ما اجازه بدهد کدها را به سادگی گسترش بدهیم.
به طور مثال تصور کنید که ما می خواهیم یک پهپاد را برنامه نویسی کنیم تا شیء ای را از جایی به جای دیگر ببرد. این پهپاد از نظر منطقی قسمت های مختلفی دارد: پره ها، دوربین، چنگال های نگهداری از اشیاء، دریافت و ارسال سیگنال برای هدایت و الی آخر. اگر بخواهیم چنین کدهایی را در یک فایل و به صورت رویه ای بنویسیم، نه تنها قابلیت کار تیمی را از دست داده ایم بلکه با هر تغییر کوچک در قسمتی از کد بقیه اجزای کد نیز بهم خواهد ریخت. برای حل این مسئله برنامه نویسی شیء گرا (Object-oriented Programming) ایجاد شده است که یک پارادایم یا سبک کدنویسی است. برنامه نویسی شیء گرا بدین صورت کار می کند که اجزاء مختلف یک سیستم را از هم جدا کرده و سپس آن ها را به صورت جداگانه کدنویسی می کند. در مثال پهپاد می توانیم برنامه نویسی پره ها را به یک تیم بدهیم، برنامه نویسی دوربین را به یک تیم بدهیم و الی آخر. همانطور که متوجه شده اید کارکرد یک پهپاد را به قسمت های مختلف تقسیم کرده ایم تا بتوانیم دنیای واقعی را شبیه سازی کنیم. حالا اگر بخواهیم به جای پهپاد از یک ماشین خودران استفاده کنیم چطور؟ ما می توانیم از همان کدهایی که برای پهپاد نوشتیم برای هدایت ماشین خودران نیز استفاده کنیم. به طور مثال همان کدهای آنالیز دوربین پهپاد برای دوربین ماشین استفاده شوند بنابراین به همین سادگی در استفاده از کدها صرفه جویی کرده ایم.
بنابراین برنامه نویسی شیء گرا روشی برای نظم بخشیدن به کدهای ما و یک پارادایم مشهور برنامه نویسی است. در مثال ماشین خودران دو جنبه اصلی وجود دارد:
ما این دو جنبه را نیز در برنامه نویسی شیء گرا داریم: به اولین جنبه (ویژگی ها) attribute یا خصوصیت و به دومین جنبه (اعمال) method یا متد گفته می شود اما قبل از توضیح این مسائل باید با کلاس ها آشنا شویم.
کلاس ها نقشه ای برای ساخت اشیاء هستند. یعنی چه؟ یک خانه را در نظر بگیرید. نقشه این خانه که روی کاغذ کشیده شده است معادل یک کلاس است و خود خانه یک شیء می باشد. در مثال ماشین خودران، نقشه و دستور العمل ساخت ماشین همان «کلاسِ» ماشین است و خود ماشین برابر با «شیء» ای است که از روی آن کلاس ساخته شده است. به فرآیند ساخت و ساز شیء از روی کلاس، «نمونه سازی» یا instantiation گفته می شود. بنابراین هر شیء یک نمونه یا instance از یک کلاس است.
برای ساخت کلاس ها باید از کلیدواژه class استفاده کنیم:
class MyFirstClass: pass
همانطور که در کد بالا مشخص است ابتدا class را آورده و سپس نام دلخواه خود را به این کلاس می دهیم. من نام MyFirstClass را انتخاب کرده ام که از قانون pascal case پیروی می کند. ما تا این قسمت برای نوشتن نام متغیرها از snake case پیروی می کردیم؛ یعنی کلمات را با علامت آندراسکور (_) از هم جدا کرده و همه کلمات با حروف کوچک نوشته می شدند (به طور مثالmy_first_class). اما در pascal case حرف اول تمام کلمات با حرف بزرگ آمده و کلمات را نیز به هم میچسبانیم (به طور مثال MyFirstClass). این مسئله یک قرارداد بوده و قانون نیست بنابراین الزامی برای پیروی از آن ندارید اما به شدت پیشنهاد می شود. احتمالا از کد بالا متوجه شده اید که کلاس ها نیز مانند توابع یک بلوک خاص کد دارند. من از آنجایی که فعلا نمی خواهم کدی را درون این کلاس بنویسم از pass استفاده کرده ام تا به خطا برخورد نکنیم.
اگر بخواهیم از کلاس بالا یک نمونه یا یک شیء بسازیم (عملیات instantiation) باید به شکل زیر عمل کنیم:
class MyFirstClass: pass obj1 = MyFirstClass()
به عبارت دیگر ما نام کلاس را مانند یک تابع صدا می زنیم (پرانتزها قسمت مهمی هستند و باید حضور داشته باشند) و نتیجه یک شیء می شود که درون obj1 قرار می گیرد. حالا obj1 یک نمونه یا instance از کلاس MyFirstClass است. شما می توانید هر تعداد نمونه ای که خواستید را از یک کلاس بسازید. حالا اگر از دستور type استفاده کنیم چه نتیجه ای می گیریم؟
class MyFirstClass: pass obj1 = MyFirstClass() print(type(obj1))
با اجرای این کد نتیجه زیر را دریافت می کنید:
<class '__main__.MyFirstClass'>
اگر قسمت __main__ را نادیده بگیریم، می بینید که این کد دقیقا مانند دیگر نوع داده هایی بود که در ابتدای مقاله به شما نشان دادم. به همین سادگی متوجه می شویم که این نوع داده ها نیز حتما یک کلاس هستند و زمانی که ما یک رشته یا لیست یا دیکشنری و غیره را می سازیم در واقع در حال instantiate کردن یک شیء جدید از کلاس هایشان می باشیم. برای درک بهتر باید یک کلاس واقعی ساخته و در آن کدنویسی کنیم.
تصور کنید که ما در حال کار برای یک شرکت بازی سازی هستیم و این شرکت می خواهد یک بازی جادوگری شبیه به هری پاتر بسازد. هر فرد در این بازی باید یک شخصیت را انتخاب کند تا با آن در بازی حضور داشته باشد و ما باید این شخصیت را کدنویسی کنیم. من کلاسی را برایتان می نویسم که هنوز دستوراتش را نمی دانید اما مشکلی نیست. فعلا روی فرآیند کلی تمرکز کنید تا در ادامه همه چیز را برایتان توضیح بدهم:
class PlayerCharacter: def __init__(self, name): self.name = name def run(self): print("RUN!")
اگر یادتان باشد به شما گفته بودم که متدها و توابع در اصل یکی هستند و تنها تفاوتشان در نحوه صدا زدنشان و تعلق آن ها به شیء ای خاص است. در آن زمان شاید متوجه حرف من نشده باشید اما منظور من دقیقا همین کد است. ما درون این کلاس دو متد را تعریف کرده ایم که دقیقا مانند توابع با کلیدواژه def تعریف می شوند؛ متد اول __init__ نام داشته و یک متد خاص است، متد دوم نیز run نام دارد که فقط عبارت RUN را چاپ می کند. احتمالا می پرسید self چیست؟ بگذارید ساختار این کلاس را بیشتر توضیح بدهم.
init مخفف initiate و به معنی آغاز کردن یک کار است. متد __init__ متعلق به دسته ای خاص از متدها به نام Magic Methods یا Dunder methods است که در آینده یک جلسه کامل را به آن ها اختصاص خواهیم داد. اگر با زبان های برنامه نویسی دیگر مانند جاوا اسکریپت آشنا باشید می دانید که متد __init__ معادل متد constructor در آن زبان ها است. معمولا در تعریف کلاس ها اگر بخواهند از متد __init__ استفاده کنند آن را مانند کد ما، در ابتدای کلاس تعریف می کنند. حالا کار __init__ چیست؟ هر زمان که بخواهید نمونه ای از این کلاس را بسازید، متد __init__ به صورت خودکار اجرا می شود.
پارامتر self چیست؟ self در فارسی به معنی «خود» است و به خودِ کلاس یا شیء ساخته شده از آن اشاره می کند. یعنی چه؟ ما در کلاس بالا متدی به نام run داریم که عبارت RUN را چاپ می کند. چطور مشخص کنیم که این متد متعلق به این کلاس و طبیعتا هر شیء ای است که از این کلاس ساخته می شود؟ با پارامتر self. در واقع در زبان پایتون هر زمانی که متدی را تعریف می کنیم باید self را به عنوان پارامتر اول به آن پاس بدهیم تا مشخص شود که این متد متعلق به این کلاس و اشیاء ساخته شده از آن است. شاید در نگاه اول این موضوع برایتان عجیب باشد (مخصوصا اگر از زبان های برنامه نویسی دیگر آمده باشید) اما به مرور زمان به این مسئله عادت می کنید. در واقع اگر self را از متد بالا حذف کنید، ویرایشگر کد به شما خطایی خواهد داد.
سوال بعدی اینجاست که name چیست؟ name یک پارامتر دلخواه است که من برای این کلاس تعریف کرده ام (دقیقا مثل پارامتر هایی که برای توابع تعریف می کردیم. شما می توانید اصلا name را نداشته باشید یا هر پارامتر دیگری را برایش بنویسید اما ما می خواهیم یک شخصیت در یک بازی کامپیوتری را بسازیم بنابراین اول از همه باید نامش را مشخص کنیم. برای انجام این کار من پارامتری به نام name را در __init__ قرار داده ام تا هر زمان که خواستیم یک نمونه از این کلاس را بسازیم، حتما مجبور به پاس دادن این نام شویم. همانطور که نمی توانستیم توابع را بدون پاس دادن آرگومان هایشان اجرا کنیم، نمی توانیم متدها را نیز بدون آرگومان ها اجرا کنیم. از طرفی می دانیم که به محض ساخت یک شیء (یک نمونه) از یک کلاس، متد init اجرا می شود. آیا می دانید این یعنی چه؟ یعنی برای ساخت شیء از کلاس PlayerCharacter باید نامی را به آن پاس بدهیم:
player1 = PlayerCharacter('Amir')
اگر PlayerCharacter را به صورت خالی صدا بزنیم خطا دریافت خواهیم کرد.
سوال بعدی من این است که self.name چیست؟ در ابتدای همین مقاله برایتان توضیح دادم که یک خودرو از نظر برنامه نویسی شیء گرا دو جنبه اصلی دارد:
ما این دو جنبه را نیز در برنامه نویسی شیء گرا داریم: به اولین جنبه (ویژگی ها) attribute یا خصوصیت و به دومین جنبه (اعمال) method یا متد گفته می شود. حالا دوباره به کد خودمان نگاهی بندازید:
class PlayerCharacter: def __init__(self, name): self.name = name def run(self): print("RUN!")
تا اینجا می دانیم که run و __init__ متدهای ما هستند بنابراین می دانیم که چطور متدها را تعریف کنیم اما تعریف خصوصیات چطور؟ برای انجام این کار باز هم از self استفاده کرده و نام آن خصوصیت را معادلش قرار می دهیم. در مثال بالا شخصیت ما نیاز به یک نام دارد بنابراین self.name را برابر با نام پاس داده شده قرار داده ایم. شما می توانید نام این خصوصیت را هر چیزی بگذارید. به طور مثال:
class PlayerCharacter: def __init__(self, name): self.player_name = name def run(self): print("RUN!")
من این بار نام این خصوصیت (attribute) در این کلاس را player_name گذاشته ام اما باز هم می گویم که مانند متغیرها می توانید هر نام دلخواهی را برایش انتخاب کنید و این نام هیچ ربطی به پارامتر پاس داده شده ندارد. در ضمن به یاد داشته باشید که هر کلاس می تواند خصوصیات (attribute) و متدهای (method) بی نهایتی داشته باشد. مثال:
class PlayerCharacter: def __init__(self, name): self.player_name = name self.skill = 10 def run(self): print("RUN!")
من در این قسمت خصوصیتی به نام skill را تعریف کرده ام و به جای دریافت آن به صورت پارامتر، مستقیما عدد ۱۰ را به آن داده ام.
حالا فرض کنید به حالت اول برگشته و کلاس زیر را داریم:
class PlayerCharacter: def __init__(self, name): self.palyer_name = name def run(self): print("RUN!")
اگر بخواهیم از این کلاس یک نمونه بسازیم، چه کار باید انجام بدهیم؟ اگر بخواهیم به این خصوصیات و متدها دسترسی داشته باشیم چطور؟ بیایید ابتدا با خصوصیات شروع کنیم:
class PlayerCharacter: def __init__(self, name): self.palyer_name = name def run(self): print("RUN!") player1 = PlayerCharacter('Amir') player2 = PlayerCharacter('Ahmad') print(player1.palyer_name) print(player2.palyer_name)
همانطور که گفتم متد init ما را مجبور می کند که یک آرگومان را برای name پاس بدهیم در غیر این صورت خطا دریافت می کنیم. من دو نمونه یا دو شیء از کلاس PlayerCharacter ساخته ام: شیء اول Player1 و شیء دوم Player2. در مرحله بعدی با علامت نقطه به خصوصیت player_name آن ها دسترسی پیدا کرده و آن را چاپ کرده ام. به نظر شما با اجرای این کد چه نتیجه ای می گیریم؟ نتیجه به شکل زیر خواهد بود:
Amir Ahmad
همانطور که می بینید به راحتی با یک کد توانسته ایم چندین شیء بسازیم و هر کدام متفاوت هستند. چطور؟ پارامتر خاص self به ما کمک می کند که این دو نمونه را به صورت کاملا جداگانه بسازیم. یکی از مزایای اصلی برنامه نویسی شیء گرا همین مسئله استفاده چند باره از کدها و صرفه جویی در کدنویسی است. در مرحله بعدی باید به سراغ متدها برویم. دسترسی به متدها نیز دقیقه به همین روش است:
class PlayerCharacter: def __init__(self, name): self.palyer_name = name def run(self): print("RUN!") player1 = PlayerCharacter('Amir') player2 = PlayerCharacter('Ahmad') player1.run() player2.run()
یادتان باشد که برای دسترسی به متدها حتما باید علامت پرانتز آن ها را بیاورید در غیر این صورت فقط آدرس متد در مموری را دریافت می کنید. با اجرای کد بالا نتیجه زیر را می گیریم:
RUN! RUN!
از آنجایی که متدهای این دو شیء عینا یکی هستند RUN را دو بار می بینیم. اگر یادتان باشد در بخش های اول این دوره در مورد متدهای رشته ها و متدهای لیست ها و غیره نیز صحبت کردیم. مثال:
some_variable = "Amir" print(some_variable.lower())
بنابراین lower یک متد از کلاس رشته ها است. اگر بخواهیم تمام متدهای موجود برای رشته ها را ببینیم می توانیم به راحتی از کلاس string استفاده کنیم:
Help on class str in module builtins: class str(object) | str(object='') -> str | str(bytes_or_buffer[, encoding[, errors]]) -> str | | Create a new string object from the given object. If encoding or | errors is specified, then the object must expose a data buffer | that will be decoded using the given encoding and error handler. | Otherwise, returns the result of object.__str__() (if defined) | or repr(object). | encoding defaults to sys.getdefaultencoding(). | errors defaults to 'strict'. | | Methods defined here: | | __add__(self, value, /) | Return self+value. | | __contains__(self, key, /) | Return key in self. | | __eq__(self, value, /) | Return self==value. | | __format__(self, format_spec, /) | Return a formatted version of the string as described by format_spec. | | __ge__(self, value, /) | Return self>=value. | | __getattribute__(self, name, /) | Return getattr(self, name). | | __getitem__(self, key, /) // بقیه توضیحات
همانطور که می بینید متد help که قبلا نیز با آن آشنا شده بودیم، مستقیما کلاس str را گرفته و تمام متدها و خصوصیات موجود در آن را برایمان چاپ می کند که بسیار طولانی است. زمانی که می گوییم همه چیز در پایتون شیء است منظور ما دقیقا همین چیزی است که می بینید. همه چیز به صورت OOP (برنامه نویسی شیء گرا - Object-Oriented Programming) نوشته شده است.
در قسمت قبلی دیدیم که خصوصیات یا attribute ها به لطف دستور self برای هر شیء ای که از کلاس ساخته می شود، متفاوت هستند (در مثال قبلی خودمان دیدیم که name برای دو شیء ای که ایجاد کرده بودیم متفاوت بود). جدا از مقادیر که به attribute معروف هستند، مقادیر دیگری به نام class object attribute را نیز داریم که مقادیری ثابت و غیر پویا هستند. این مقادیر برای تمام اشیاء ساخته شده بر اساس یک کلاس یکسان بوده و مختص آن شیء نیستند. به مثال زیر توجه کنید:
class PlayerCharacter: membership = True def __init__(self, name): self.palyer_name = name def run(self): print("RUN") player1 = PlayerCharacter('Amir') player2 = PlayerCharacter('Ahmad') print(player1.membership) print(player2.membership)
همانطور که در کد بالا مشخص است مقداری به نام membership وجود دارد که مقدار True را دارد. در این مثال membership یک class object attribute است و هر شیء ای که از این کلاس ساخته شود حتما این مقدار را خواهد داشت. من در کد بالا با علامت نقطه به آن دسترسی پیدا کرده ام (دقیقا مانند attribute های عادی) تا آن را چاپ کنیم. با اجرای این کد نتیجه زیر را می گیریم:
True True
همانطور که می بینید هر دو شیء این مقدار را دارند. ما زمانی از class object attribute ها استفاده می کنیم که بخواهیم یک مقدار ثابت را در تمام اشیاء ساخته شده از یک کلاس مشخص داشته باشیم. به طور مثال اگر در برنامه ما چندین کلاس وجود داشته باشد و بخواهیم شناسه ای به هر کلاس بدهیم تا بعدا اشیاء آن را شناسایی کنیم، class object attribute ها گزینه بسیار خوبی هستند.
در حال حاضر ما چنین کدی را به عنوان کلاس خودمان داریم:
class PlayerCharacter: membership = True def __init__(self, name): self.palyer_name = name def run(self): print("RUN")
در حال حاضر متد run فقط عبارت RUN را برای تمام اشیاء چاپ می کند اما بهتر است کاری کنیم که نام کاربر را نیز نمایش بدهد:
class PlayerCharacter: membership = True def __init__(self, name): self.palyer_name = name def run(self): print(f"RUN {self.palyer_name}") player1 = PlayerCharacter('Amir') player2 = PlayerCharacter('Ahmad') player1.run() player2.run()
برای استفاده از یک attribute درون خود کلاس باید حتما مانند کد بالا از دستور self استفاده کنید. همچنین می دانیم که برای قرار دادن یک متغیر در یک رشته باید از formatted strings استفاده کنیم که با قرار دادن حرف f قبل از رشته مشخص می شود. حالا با اجرای کد بالا نتیجه زیر را دریافت می کنیم:
RUN Amir RUN Ahmad
اما اگر بخواهیم به class object attribute ها دسترسی داشته باشیم دو راه داریم؛ یا مجددا از دستور self استفاده کنیم و یا اینکه از خود نام کلاس استفاده نماییم. به مثال زیر توجه کنید:
class PlayerCharacter: membership = True def __init__(self, name): self.palyer_name = name def run(self): print(f"RUN {PlayerCharacter.membership}") player1 = PlayerCharacter('Amir') player1.run()
با اجرای کد بالا نتیجه زیر را دریافت می کنیم:
RUN True
بنابراین نهایتا به خود شما بستگی دارد که از self یا از نام کلاس استفاده نمایید اما در نظر داشته باشید که استفاده از نام کلاس تنها برای دسترسی به class object attribute ها مجاز می باشد.
حالا که با کلیت کلاس ها و شی گرایی در پایتون آشنا شده ایم بهتر است یک تمرین از آن را حل کنیم. اشتباه نکنید، ما هنوز مطالب بسیار زیادی در مورد شی گرایی در پایتون و کلاس ها داریم که در جلسات بعدی بررسی خواهیم کرد اما فعلا برای یادگیری مطالبی که تا این قسمت توضیح داده شده است باید یک تمرین ساده را داشته باشیم. فرض کنید کلاس زیر را به ما داده اند:
class Cat: species = 'mammal' def __init__(self, name, age): self.name = name self.age = age
این کلاس یک cat (گربه) می سازد. درون این کلاس یک class object attribute به نام species (گونه) داریم که مقدار mammal (پستاندار) را دارد. این کلاس فقط یک متد init دارد که نام و سن گربه را از شما می خواهد. شما باید به سه سوال زیر درباره این کلاس پاسخ بدهید:
تمرین های این دوره مهم ترین بخش یادگیری آن هستند بنابراین سعی کنید حتما خودتان به این سوالات جواب بدهید و بعد از آن به پاسخ من توجه کنید:
class Cat: species = 'mammal' def __init__(self, name, age): self.name = name self.age = age # سه شیء از کلاس گربه peanut = Cat("Peanut", 3) garfield = Cat("Garfield", 5) snickers = Cat("Snickers", 1) # تابعی برای پیدا کردن پیرترین گربه def get_oldest_cat(*args): return max(args) # نمایش سن پیرترین گرفته print( f"پیرترین گربه {get_oldest_cat(peanut.age, garfield.age, snickers.age)} سال دارد.")
همانطور که می بینید سوال اول بسیار ساده است؛ من سه گربه را از کلاس گربه ایجاد کرده و نام ها و سن های مختلفی به آن ها داده ام. سوال دوم کمی پیچیده تر است چرا که باید از مباحث جلسات قبل استفاده می کردید. از آنجایی که نمی دانیم ممکن است چند گربه به تابع ما پاس داده شود بنابراین باید از args* استفاده کنیم. اگر یادتان باشد با استفاده از این دستور می توانیم تعداد نامحدودی آرگومان را پاس بدهیم. برای مشخص کردن بزرگترین مقدار نیز می توانیم از تابع max استفاده کنیم. در مورد سوال سوم نیز روش کار ساده ای دارد. من تابع نوشته شده توسط خودم (get_oldest_cat) و formatted string ها استفاده کرده ام تا سن پیرترین گربه را نمایش بدهم. با اجرای کد بالا باید نتیجه زیر را دریافت کنید:
پیرترین گربه 5 سال دارد.
از آنجایی که نتیجه کد از چپ به راست نوشته می شود شاید حروف فارسی را به خوبی نشان ندهد اما با چک کردن حروف به سادگی متوجه متن خروجی می شوید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.