مبانی ثانویه‌ی پایتون – تفاوت تابع و متد

Professional Python: Method vs Function

13 اسفند 1399
درسنامه درس 10 از سری پایتون حرفه‌ای
Python حرفه ای: مبانی ثانویه – تفاوت تابع و متد (قسمت 10)

کلیدواژه return

زمانی که بحث از توابع در پایتون می شود دستور return اولین چیزی است که به ذهن ما می آید. ما هم در طول این دوره مکررا از این دستور استفاده خواهیم کرد بنابراین بهتر است به خوبی با آن آشنا شویم. بهتر است با یک مثال ساده شروع کنیم؛ تابعی برای جمع زدن دو عدد:

def sum(number1, number2):

  number1 + number2




sum(2, 6)

همانطور که می بینید تابع بالا فقط دو عدد را گرفت و با هم جمع می زند به همین دلیل من آن را با اعداد ۲ و ۶ فراخوانی کرده ام. به نظر شما با اجرای تابع بالا چه اتفاقی می افتد؟ هیچ اتفاقی نمی افتد. شاید تصور کنید که دلیل این مسئله، استفاده نکردن از تابع print است اما اینطور نیست:

def sum(number1, number2):

  number1 + number2



print(sum(2, 6))

با اجرای این کد None را دریافت می کنیم. چرا؟ none به معنی عدم وجود یک مقدار است بنابراین اصلا چیزی برای چاپ شدن وجود نداشته است. باید توجه کنید که توابع در زبان های برنامه نویسی همیشه مقداری را return می کنند یا در فارسی می گوییم «برمی گردانند». قبلا هم توضیح داده بودم که توابع مجموعه ای از کدها هستند که روی یک مقدار خاص اجرا شده یا عملیاتی را انجام می دهند و نهایتا نتیجه ای را برمی گردانند.  زبان پایتون نیز از این قاعده مستثنی نیست و اگر تابعی در آن، مقداری را برنگرداند (return نکند) زبان پایتون به صورت خودکار none را برایش برمی گرداند. با این حساب در تابع خودمان و پس از جمع زدن اعداد باید نتیجه را برگردانیم یا به زبان فنی تر return کنیم:

def sum(number1, number2):

  return number1 + number2


print(sum(2, 6))

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

def sum(number1, number2):

  the_sum = number1 + number2

  return the_sum


print(sum(2, 6))

من در این مثال نتیجه جمع را در متغیری به نام the_sum قرار داده و سپس آن را return کرده ام. البته توجه داشته باشید که برخی از توابع واقعا None را برمی گردانند و اشکالی در برگرداندن none نیست. به طور مثال تابع clear که قبلا با آن آشنا شدیم یک لیست را خالی می کند اما مقداری را برنمی گرداند. مشکل در جایی است که ما انتظار یک مقدار را داریم اما چیزی return نمی شود. به تابع زیر توجه کنید:

def sah_hello():

  print('hello Roxo')




print(say_hello())

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

  • معمولا هر تابع یک هدف مشخص را دنبال می کند.
  • معمولا هر تابع مقدار خاصی را برمی گرداند.

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

def sum(number1, number2):

  print(number1 + number2)



print(sum(2, 6))

این تابع مقدار خاصی را برنمی گرداند بلکه جمع دو عدد را مستقیما چاپ می کند بنابراین با اجرای آن عدد ۸ و None را می گیریم (none به دلیل دستور print ایجاد می شود).  این کد مقداری را برنمی گرداند بنابراین به ما اجازه نمی دهد تا به این عدد دسترسی داشته باشیم. اگر بخواهیم همین کد را به صورت بهتر بنویسیم می گوییم:

def sum(number1, number2):

  return number1 + number2



my_sum = sum(2, 6)


print(my_sum)

همانطور که می بینید اگر تابع ما مقداری را برگرداند ما می توانیم آن مقدار را در یک متغیر ذخیره کنیم بنابراین من هم جمع دو عدد را در متغیر my_sum ذخیره کرده و آن را پرینت کرده ام. در برنامه های واقعی می توانیم نتایج را به همین شکل در قسمت های مختلف برنامه مورد استفاده قرار بدهیم. یک مثال کاربردی تر به شکل زیر خواهد بود:

def is_two(number):

  if(number == 2):

    return True

  else:

    return False


if(is_two(2)):

  print('Number is 2!')

else:

  print('Number is not 2!')

من در شرط بالا از یک تابع استفاده کرده ام! چطور؟ تابع ما عددی را گرفته و بررسی می کند که آیا این عدد ۲ است یا عدد دیگری است. اگر ۲ باشد مقدار true و در غیر این صورت false را برمی گرداند. از آنجایی که ما می دانیم این تابع مقدار true یا false را برمی گرداند می توانیم به راحتی از آن در یک شرط ساده استفاده کنیم! اگر شرط صحیح بود طبیعتا عدد ۲ است و عبارت Number is 2 را دریافت می کنیم و در غیر این صورت نیز مقدار Number is not 2 چاپ می شود.

ما حتی می توانیم درون یک تابع، یک تابع دیگر را تعریف کرده و برگردانیم:

def sum(num1, num2):

  def another_func(num1, num2):

    return num1 + num2

  return another_func



total = sum(10, 20)

print(total)

در اینجا تابعی به نام sum داریم که درون خود تابعی دیگر به نام another_func را تعریف کرده است و سپس مجموع اعداد را برمی گرداند. به نظر شما با اجرای کد بالا چه نتیجه ای می گیریم؟ اگر به فرورفتگی کدها دقت کنید متوجه می شوید که آخرین return در کد بالا متعلق به تابع sum است و ربطی به another_func ندارد. توجه داشته باشید که اگر تابع sum را صدا بزنیم، دستور return num1 + num2 اجرا نمی شود چرا که این دستور متعلق به another_func است و ما اصلا another_func را صدا نزده ایم. در واقع پارامترهای num1 و num2 برای متد sum اضافی هستند و اصلا کاری انجام نمی دهند. با این حساب و با اجرای کد بالا نتیجه زیر را می گیرید:

<function sum.<locals>.another_func at 0x2b283a6bbea0>

همانطور که از نتیجه معلوم است ما تعریف تابع another_func را در آدرس 0x2b283a6bbea0 در مموری دریافت کرده ایم. اگر بخواهیم نتیجه جمع را ببینیم دو گزینه داریم. گزینه اول صدا زدن another_func به شکل زیر است:

def sum():

  def another_func(num1, num2):

    return num1 + num2

  return another_func



total = sum()

print(total(10, 10))

از آنجایی که نیازی به پارامترهای num1 و num2 در تابع sum نبود من آن ها را حذف کردم. زمانی که sum را بدین شکل صدا می زنیم و نتیجه را در total ذخیره می کنیم یعنی تعریف تابع another_func در total قرار گرفته است بنابراین می توانیم خود متغیر total را مانند یک تابع صدا بزنیم و اعداد ۱۰ و ۱۰ را به آن پاس بدهیم. با اجرای کد بالا نتیجه ۲۰ را دریافت می کنیم.

روش دوم انجام این کار این است که در تعریف تابع another_func حالت فراخوانی شده آن را return کنیم:

def sum(num1, num2):

  def another_func(num1, num2):

    return num1 + num2

  return another_func(num1, num2)




total = sum(10, 10)

print(total)

در واقع من درون تابع sum،‌ تابع another_func را تعریف کرده و سپس آن را فراخوانی کرده ام و مقدار فراخوانی شده را return کرده ام. برای واضح تر شدن کد باید آن را به شکل زیر بنویسیم:

def sum(num1, num2):

  def another_func(n1, n2):

    return n1 + n2

  return another_func(num1, num2)




total = sum(10, 10)

print(total)

حالا متوجه می شوید که کدام پارامترها به کدام قسمت تعلق دارند. من این مثال پیچیده و عجیب و غریب را برایتان آورده ام تا شما را به چالش بکشم و قوه تحلیل شما را فعال کنم. در برنامه های واقعی به ندرت از چنین کدهای عجیب و غریبی استفاده می کنیم.

نکته آخری که باید به آن توجه ویژه ای داشته باشید این است که دستور return باعث خروج کامل از تابع می شود (چیزی شبیه به break در حلقه ها). ما می توانیم این مسئله را با کد زیر نمایش بدهیم:

def sum(num1, num2):

  print("Before return")

  return num1 + num2

  print("After return")




total = sum(10, 10)

print(total)

من قبل و بعد از دستور return دو تابع print را دارم که اولی می گوید «قبل از return» و دومی می گوید «بعد از return» بنابراین با اجرای این کد مشخص خواهد شد که return چه تاثیری روی تابع دارد. نتیجه:

Before return

20

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

def sum(num1, num2):

  def another_func(n1, n2):

    return n1 + n2

  return another_func(num1, num2)




total = sum(10, 10)

print(total)

دقت کنید که return اول در کد بالا مربوط به تابع another_func است (به فرورفتگی توجه کنید) بنابراین اجرا نخواهد شد چرا که ما درون این تابع نیستیم بلکه درون تابع sum قرار داریم.

تفاوت تابع و متد در پایتون | تابع (function) در مقابل متد (method)

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

my_function()

اما برای صدا زدن متدها از علامت نقطه استفاده کرده و آن ها را روی داده خاصی صدا می زنیم:

'this is a string'.capitalize()

در صورتی که متدها را به صورت خالی صدا بزنید (مثلا ()capitalize) با خطا روبرو خواهید شد.

Docstring چیست؟

گاهی اوقات در زبان پایتون مانند دیگر زبان های برنامه نویسی نیاز به اضافه کردن توضیحاتی در مورد توابع خود داریم. در چنین حالتی باید به سراغ docstring برویم که به ما اجازه اضافه کردن این توضیحات را می دهد. روش انجام کار بدین شکل است که باید ابتدا از علامت single quote سه بار استفاده کرده و سپس توضیحات خود را به آن اضافه کنید. مثال:

def sum(num1, num2):

  '''

  This function adds two numbers together

  '''

  return num1 + num2



sum()

قسمت درون سه علامت ''' نوع خاصی از کامنت است بنابراین اجرا نمی شود اما زمانی که در حال نوشتن فراخوانی تابع sum هستید این توضیحات را مشاهده خواهید کرد:

docstring و توضیحات آن در ویرایشگر Visual Studio Code
docstring و توضیحات آن در ویرایشگر Visual Studio Code

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

help(print)

توجه کنید که من تابع print را فراخوانی نکرده ام (پرانتزهایش را قرار نداده ام). با اجرای این تابع نتیجه زیر را می گیرید:

Help on built-in function print in module builtins:




print(...)

    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

   

    Prints the values to a stream, or to sys.stdout by default.

    Optional keyword arguments:

    file:  a file-like object (stream); defaults to the current sys.stdout.

    sep:   string inserted between values, default a space.

    end:   string appended after the last value, default a newline.

    flush: whether to forcibly flush the stream.

این توضیحات کاملی از نحوه کار متد print است. ما می توانیم همین کار را برای تابع sum خودمان نیز انجام بدهیم:

def sum(num1, num2):

  '''

  This function adds two numbers together

  '''

  return num1 + num2




help(sum)

با اجرای این کد نتیجه زیر را دریافت می کنید:

Help on function sum in module __main__:




sum(num1, num2)

    This function adds two numbers together

همانطور که می بینید docstring ما در اینجا نمایش داده شده است.

تمرین: نمونه ای از کد تمیز

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

def is_even(number):

  if number % 2 == 0:

    return True

  elif number % 2 != 0:

    return False




print(is_even(4))

اگر یادتان باشد اپراتور modulo (علامت %) باقی مانده تقسیم را به ما نشان می داد. هر عددی تقسیم بر ۲ که باقی مانده آن صفر باشد یک عدد زوج محسوب می شود بنابراین گفته ایم اگر باقی مانده تقسیم بر ۲ برابر با صفر True را برمی گردانیم و اگر برابر با صفر نباشد False را برمی گردانیم. این کد هیچ مشکلی نداشته و به درستی کار می کند اما اضافه نویسی دارد. به طور مثال ما می دانیم که سوال ما فقط دو حالت دارد:

  • باقی مانده صفر است بنابراین عدد زوج است
  • باقی مانده صفر نیست بنابراین عدد فرد است

بنابراین نیازی به دو شرط جداگانه if نیست بلکه می توانیم از یک else استفاده کنیم:

def is_even(number):

  if number % 2 == 0:

    return True

  else:

    return False




print(is_even(4))

اما از طرف دیگر می دانیم که return ما را از تابع خارج می کند بنابراین می توان هنوز هم این کد را خلاصه تر کرد:

def is_even(number):

  if number % 2 == 0:

    return True

  return False



print(is_even(4))

اگر شرط صحیح باشد true برگردانده شده و از تابع خارج می شویم اما اگر شرط صحیح نباشد false با برمی گردانیم. البته هنوز هم می توانیم خلاصه تر بنویسیم:

def is_even(number):

  return number % 2 == 0


print(is_even(4))

تقسیم عدد بر ۲ و بررسی باقی مانده آن یک expression است (مقدار جدیدی تولید می کند) و هر expression ای می تواند به boolean تبدیل شود. اگر یادتان باشد لیستی از مقادیر false را برایتان توضیح دادم و گفتم که غیر از آن ها همه چیز True است. با این حساب می توانیم نتیجه این expression را برگردانیم که نهایتا true یا false خواهد بود. امیدوارم متوجه معنی کد تمیز و خلاصه شده باشید. هر کس که بتواند بدین شکل کد بنویسد یعنی در سطح خوبی از برنامه نویسی قرار دارد و متوجه مسائل پایه ای و جزئی تر برنامه نویسی می شود. البته یادگیری چنین روش هایی زمان می برد و طبیعتا نمی توانید در رو اول به صورت یک برنامه نویس با تجربه کد بنویسید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.