آموزش پایتون - فصل سوم(توابع)

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

فراخوانی تابع :

قبلا نمونه ای از فراخوانی تابع رو باهم دیدیم :

>>> type(42)
<class 'int'>

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

در واقع میتونیم بگیم که یک تابع ، آرگومان رو میگیره و نتیجه رو به ما برمیگردونه ؛ به نتیجه ی تابع ، مقدار برگشتی هم گفته میشه .

پایتون توابعی رو برای تبدیل انواع داده ای در اختیار ما قرار داده . برای مثال تابع int مقداری رو به عنوان آرگومان میگیره و اگر بتونه اون رو به عدد صحیح تبدیل میکنه ؛ اگر هم نتونه به ما اخطار میده :

>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int(): Hello

تابع int میتونه مقادیر اعشاری رو به مقادیر صحیح تبدیل کنه ولی نکته ای که وجود داره اینه که بجای گرد کردن مقادیر اعشاری ، فقط قسمت اعشاری اونها رو حذف میکنه :

>>> int(3.99999)
3
>>> int(-2.3)
-2

تابع float هم مقادیر صحیح و رشته ای رو به عدد اعشاری تبدیل میکنه :

>>> float(32)
32.0
>>> float('3.14159')
3.14159

و در نهایت تابع str آرگومانی که بهش پاس دادیم رو به مقدار رشته ای تبدیل میکنه :

>>> str(32)
'32'

>>> str(3.14159)
'3.14159'



توابع ریاضی :

در پایتون ماژولی بنام math وجود داره که اکثر توابع معروف ریاضی رو در اختیار ما قرار میده . ماژول به فایلی گفته میشه که شامل مجموعه ای از توابع مرتبط هست.

قبل از اینکه بتونیم از توابع داخل یک ماژول استفاده کنیم ، باید ماژول مورد نظر رو با دستور import به پروژه خودمون اضافه کنیم :

import math

این دستور باعث ایجاد یک شیِ ماژول به نام math میشه . شیِ ماژول شامل توابع و متغیر های تعریف شده در داخل یک ماژول هست . برای دسترسی به هر یک از این توابع ، باید نام ماژول به همراه نام تابع که با یک نقطه از هم جدا شده اند رو وارد کنیم ؛ به این روش dot notation گفته میشه :

>>> ratio = signal_power / noise_power
>>> decibels = 10 * math.log10(ratio)
>>> radians = 0.7
>>> height = math.sin(radians)

در مثال اول از math.log10 برای محاسبه ی نسبت سیگنال به نویز در واحد دسیبل استفاده میکنیم (فرض کنید متغیرهای signal_power و noise_power از قبل تعریف شدن). علاوه بر این ، ماژول math تابع log رو برای محاسبه ی لگاریتم در مبنای e در اختیار ما قرار داده.

مثال دوم مقدار سینوس متغیر radians رو برای ما محاسبه میکنه . نکته ای که اینجا باید بهش اشاره کنم اینه که تابع سینوس و دیگر توابع مثلثاتی مانند کسینوس ، تانژانت و .... آرگومان رو بجای درجه بصورت رادیان دریافت میکنند.

برای تبدیل درجه به رادیان ، درجه رو بر 180 تقسیم کرده و در pi ضرب میکنیم :

>>> degrees = 45
>>> radians = degrees / 180.0 * math.pi
>>> math.sin(radians)
0.707106781187

عبارت math.pi از داخل ماژول math ، مقدار تقریبی عدد پی که تا 15 رقم اعشار محاسبه شده رو در اختیا ما قرار میده .


ترکیب بندی (composition):

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

x = math.sin(degrees / 360.0 * 2 * math.pi)

و یا شامل فراخوانی یک تابع باشه :

x = math.exp(math.log(x+1))

نکته : شما تقریبا در هر جایی میتونید مقدار و یا عبارت دلخواهی رو قرار بدید و در این مورد فقط یک استثنا وجود داره ؛ در سمت چپِ هر عبارتِ انتساب باید نام یک متغیر قرار بگیره و قرار دادن هر نوع عبارت دیگه ای باعث ایجاد خطای نحوی میشه :

>>> minutes = hours * 60        # right
>>> hours * 60 = minutes        # wrong
SuntaxError : can't assign to operator



افزودن توابع جدید :

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

def print_lyrics():
     print("I'm  Milad, and I'm okay.")
     print("I sleep all night and I work all day.")


کلمه ی def نشون میده که این عبارت مربوط به تعریف یک تابع هست و همچنین عبارت print_lyrics به عنوان نام تابع در نظر گرفته میشه .

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

پرانتز باز و بسته نشون دهنده ی این هست که این تابع هیچ آرگومانی رو دریافت نمیکنه . به اولین خط تعریف تابع ، هِدِر گفته میشه و مابقی به عنوان بدنه ی تابع شناخته میشن . در آخرِ هدر تابع باید دونقطه (:) قرار داده بشه و بدنه ی تابع نیز بصورت پیشفرض به اندازه ی ۴ عدد space تورفتگی خواهد داشت تا ساختار تابع به درستی شکل بگیره .

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

>>> def print_lyrics():
...         print("I'm Milad, and I'm okay.")
...         print("I sleep all night and I work all day.")
...

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

هنگام تعریف یک تابع ، یک شئِ تابع ساخته میشه که از نوع تابع هست :

>>> print(print_lyrics)
<function print_lyrics at 0xb7e99e9c>
>>> type(print_lyrics)
<class 'function'>

برای فراخوانی توابعی که ساختیم ، مانند فراخوانی توابع داخل پایتون عمل میکنیم :

>>> print_lyrics()
I'm  Milad, and I'm okay.
I sleep all night and I work all day.

بعد از تعریف یک تابع ، میتونیم اون رو داخل توابع دیگه فراخوانی کنیم . برای مثال تابعی به نام repeat_lyrics میسازیم و تابع قبلی رو داخل اون دوبار فراخوانی میکنیم :

def repeat_lyrics():
      print_lyrics()
      print_lyrics()

و بعد تابع repeat_lyrics رو فراخوانی میکنیم :

>>> repeat_lyrics()
I'm  Milad, and I'm okay.
I sleep all night and I work all day.
I'm  Milad, and I'm okay.
I sleep all night and I work all day.



تعاریف و کاربردها :

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

def print_lyrics():
      print("I'm Milad, and I'm okay.")
      print("I sleep all night and I work all day.")
def repeat_lyrics():
      print_lyrics()
      print_lyrics()
repeat_lyrics()

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

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

به عنوان مثال و برای اینکه بیشتر متوجه بشید ، در برنامه ی بالا ، جای آخرین خط برنامه (فراخوانی تابع) رو عوض کنید و اون رو به اول برنامه انتقال بدید . ببینید که بعد از اجرای برنامه با چه خطایی مواجه میشید.

حالا برنامه رو به حالت اول برگردونید و این بار تابع print_lyrics رو بعد از تابع repeat_lyrics تعریف کنید . بررسی کنید که بعد از اجرای این کار چه اتفاقی خواهد افتاد.



روند اجرا :

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

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

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

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



پارامترها و آرگومان ها :

بعضی از توابعی که قبلا باهم دیدم آرگومان هایی رو نیاز داشتند . برای مثال وقتی تابع math.sin رو فراخوانی میکنیم ، عددی رو به عنوان آرگومان به اون پاس میدیم. بعضی از توابع بیش از یک آرگومان میگیرن مانند تابع math.pow که دو آرگومان رو میگیره ؛ اولی به عنوان پایه و دومی به عنوان توان .

در داخل توابع ،مقدار آرگومان ها به متغیر هایی بنام پارامتر ها انتقال پیدا میکنن . برای مثال در اینجا تابعی رو تعریف کردیم که یک آرگومان رو دریافت میکنه :

def print_twice(name):
     print(name)
     print(name)

این تابع مقدار آرگومان دریافتی رو به پارامتری بنام name انتقال میده و هنگام فراخوانی تابع ، مقدار پارامتر (هرچیزی که باشه) دوبار چاپ خواهد شد.

این تابع با هر مقداری که قابل چاپ شدن باشه ، میتونه کار کنه :

>>> print_twice('Spam')
Spam
Spam
>>> print_twice(42)
42
42
>>> print_twice(math.pi)
3.14159265359
3.14159265359

مشابه قوانین ترکیب بندی برای توابع درون-ساخت(Built-in) زبان پایتون ، این قوانین ، توابع ساخته شده توسط کاربر رو هم شامل میشه و میتونید از هر نوع عبارتی به عنوان آرگومان برای تابع print_twice استفاده کنید :

>>> print_twice('Spam '*4)
Spam Spam Spam Spam
Spam Spam Spam Spam
>>> print_twice(math.cos(math.pi))
-1.0
-1.0

همچنین ما میتونیم از یک متغیر به عنوان آرگومان تابع استفاده کنیم :

>>> var1 = "Python Is Amazing"
>>> print_twice(var1)
Python Is Amazing
Python Is Amazing



متغیرها و پارامترهای محلی :

زمانی که شما متغیری رو داخل یک تابع ایجاد میکنید ، اون متغیر یک متغیر محلی هست ؛ به این معنی که فقط داخل اون تابع وجود داره . برای مثال :

def cat_twice(part1, part2):
     cat = part1 + part2
     print_twice(cat)

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

>>> line1 = ' this is the first string '
>>> line2 = ' and that is the second string'
>>> cat_twice(line1, line2)
'this is the first string and that is the second string'
'this is the first string and that is the second string'

بعد از اینکه تابع cat_twice خاتمه پیدا کرد ، متغیر cat از بین میره و اگر بخوایم که مقدار اون رو چاپ کنیم با خطای زیر مواجه میشیم :

>>> print(cat)
NameError: name 'cat' is not defined

پارامترها هم مانند متغیرهای داخل توابع ، محلی هستند . برای مثال خارج از تابع print_twice ، هیچ چیزی بنام name وجود نداره .



توابع دارای مقدار برگشتی و توابع خالی(fruitful functions and void functions) :


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

وقتی ما یک تابعِ fruitful رو فراخوانی میکنیم ، قصد داریم که از مقدار برگشتی اون برای انجان یک کاری استفاده کنیم. برای مثال میخوایم که مقدار برگشتی رو داخل یک متغیری بریزیم و یا اینکه مقدار برگشتی به عنوان بخشی از یک عبارت مورد استفاده قرار بگیره :

x = math.cos(radians)

golden = (math.sqrt(5) + 1) / 2

وقتی که ما تابعی رو داخل مفسر پایتون فراخوانی میکنیم ، مقدار برگشتی برای ما نمایش داده میشه :

>>> math.sqrt(5)
2.2360679774997898

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

math.sqrt(5)

اسکریپت بالا ریشه ی دوم عدد 5 رو محاسبه میکنه ولی چون نه مقدار برگشتی رو داخل متغیری میریزه و نه مقدار رو برای ما نمایش میده ، زیاد به درد بخور نیست :)

توابع Void میتونن مقداری رو برای ما نمایش بدن و یا تاثیرات دیگه ای داخل برنامه داشته باشن ولی هیچ مقداری رو برای برنمیگردونن ؛ اگر با توابع void مانند توابع fruitful رفتار کنیم (یعنی مقدار برگشتی رو داخل متغیری بریزیم) با نوع خاصی از مقدار بنام None مواجه خواهیم شد :

>>> result = print_twice('Bing')
Bing
Bing
>>> print(result)
None

مقدار None مشابه مقدار رشته ای "None" نیست و نوع مخصوص خودش رو در زبان پایتون داره :

>>> print(type(None))
<class 'NoneType'>



چرا از توابع استفاده میکنیم ؟

  1. ایجاد توابع به ما این امکان رو میده که دستورات برنامه رو بصورت گروهی نام گذاری کنیم که این کار خوانایی و خطایابی برنامه رو راحت تر میکنه.
  2. استفاده از توابع باعث حذف کدهای تکراری و کاهش حجم کدهای برنامه میشه ؛ که این کار سبب میشه تغییرات رو راحت تر بتونیم اعمال بکنیم.
  3. تقسیم یک برنامه به توابع این امکان رو به ما میده که بخش های مختلف برنامه رو بصورت جداگانه خطایابی کنیم.
  4. توابعی که به خوبی طراحی و پیاده سازی شدن ، میتونن در بسیاری از برنامه ها و پروژه های دیگه دوباره استفاده بشن.



تمرین ها :

  1. تابع average رو طوری تعریف کنید که دو عدد رو به عنوان پارامتر دریافت کنه و میانگین اونها رو برای ما نمایش بده.
  2. تابع right_justify رو طوری تعریف کنید که پارامتری بنام s از نوع رشته رو بگیره و با رعایت فاصله ی کافی از سمت چپ ، رشته ی مورد نظر رو طوری چاپ بکنه که حرف آخر رشته در ستون 70 ام صفحه نمایش قرار گرفته باشه. (راهنمایی : با استفاده از تلفیق و تکرار رشته ها و تابع len میتونید این تمرین رو حل کنید)


خب اینم از فصل سوم ، امیدوارم مفید واقع بشه.

موفق باشید :)