سلام دوستان ...در قسمت قبل آموزش پایتون مفاهیم زیر را یاد دادیم
حال با ادامه آموزش پایتون همراه ما باشید.
همانند زبان C، عبارت break از داخلی ترین حلقه for یا while محصور شده شروع می شود. عبارات حلقه ممکن است بند else داشته باشند. بند else (برای حلقه for ) زمانی که حلقه پس از اتمام تکرار ها تمام شود، یا (برای while) زمانی که شرط غلط باشد اجرا می شود. اما اگر حلقه به واسطه عبارت break خاتمه یابد، اجرا نمی شود. مثال زیر که اعداد اول را جستجو می کند، مباحث ذکر شده را نشان می دهد.
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
(بله، این کد صحیح است. با دقت نگاه کنید، بند else متعلق به حلقه for است نه عبارت if). بند else در صورت استفاده در حلقه loop بیشتر شبیه بند else در عبارت try است، تا بند else در عبارات if. زمانی که هیچ استثنایی رخ ندهد، بند else در عبارت try اجرا می شود، ولی بند else در یک حلقه، زمانی که هیچ break ای رخ ندهد اجرا می شود. برای اطلاعات بیشتر درباره سعی و خطا (try and exception) لینک Handling Exceptions را ببینید. همچنین عبارت continue که بر گرفته از C است، از تکرار بعدی در حلقه شروع می کند.
>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9
عبارت pass هیچ کاری انجام نمی دهد. در شرایطی نیاز باشد یک عبارت از لحاظ نحوی نوشته شود اما برنامه هیچ عملی را انجام ندهد، می توان از pass استفاده کرد.
>>> while True:
... pass # Busy-wait for keyboard interrupt (Ctrl+C)
...
معمولا از این برای ساخت کلاس های کوچک استفاده می شود.
>>> class MyEmptyClass:
... pass
...
موردی دیگر که میتوان در آن از pass استفاده کرد، به عنوان فضا نگهدار برای یک تابع یا بدنه یک شرط است. در این شرایط این امکان را دارید تا در هنگامی که روی کد دیگری کار می کنید، در سطح انتزاع بیشتری فکر کنید. Pass در سکوت نادیده گرفته می شود
>>> def initlog(*args):
... pass # Remember to implement this!
...
میتوانیم تابعی ایجاد کنیم که سری فیبوناتچی را تا مرز دلخواه بنویسد:
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
کلمه کلیدی def تعریف یک تابع را معرفی می کند. پس از آن باید نام تابع و لیست پارامترهای رسمی آن درون پرانتز، قرار گیرد. عباراتی که بدنه تابع را می سازد، از خط بعد شروع می شود و باید فرو رفته باشند. عبارت اول در بدنه تابع می تواند به صورت اختیاری یک رشته حروف باشد. این رشته حروف، مدارک و مستندات یا docstring تابع است (اطلاعات بیشتر درباره docstring در بخش Documentation Strings موجود است).
ابزارهایی وجود دارد که از docstring برای تولید خودکار مستندات به صورت چاپی و آنلاین استفاده می کنند، یا به کاربر این امکان را می دهند تا به صورت تعاملی درون کد را مرور کنند. خوب است که از docstring درون کدی که مینویسید استفاده کنید، بنابراین به آن عادت کنید. اجرای یک تابع، یک جدول نماد جدید معرفی می کند که برای متغیرهای محلی تابع استفاده می شود. به طور دقیق تر، تمامی تخصیص ها به متغیر در یک تابع، مقدار را در جدول نماد محلی ذخیره می کند؛
زیرا مرجع های متغیر ابتدا درون جدول نماد محلی را نگاه می کنند، سپس جداول نماد محلی توابع داخلی، سپس درون جدول نماد سراسری، و در انتها جدول داخلی نام ها. بنابراین، نمی توان مستقیما در محدوده یک تابع یک مقدار را به متغیرهای سراسری و متغیرهای توابع محصور، تخصیص داد (مگر، برای متغیرهای سراسری، بیان شده در عبارت سراسری، یا برای متغیرهای توابع محصور، بیان شده در عبارت غیر محلی)، اگرچه ممکن است به آنها ارجاع داده شود.
آموزش پایتون , پارامترهای (آرگومان) حقیقی برای فراخوانی یک تابع درون جدول نماد محلی تابع فراخوانی شده، در هنگام فراخوانی، معرفی می شوند. بنابراین، آرگومان ها با استفاده از فراخوانی با مقدار (call by value) پاس داده می شوند (جایی که "مقدار" همیشه مرجع شی است، نه مقدار یک شی). 1 زمانی که یک تابع، تابع دیگری را فراخوانی می کند، یک جدول نماد محلی جدید برای آن فراخوانی ایجاد می شود.
تعریف یک تابع، نام تابع را درون جدول نماد فعلی معرفی می کند. مقدار نام تابع دارای یک نوع است که توسط مفسر به عنوان تابع تعریف شده توسط کاربر شناخته می شود. این مقدار میتواند به یک نام دیگر تخصیص داده شود که پس از آن نیز می تواند به عنوان یک تابع مورد استفاده واقع شود. این عمل به عنوان مکانیزم تغییر نام عمومی کار می کند.
>>> fib
<function fib="" at="" 10042ed0="">
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
</function>
اگر از زبان های دیگر آمده باشید، ممکن است مخالف این باشید که fib یک تابع است، بلکه میگویید یک روند (procedure) است زیرا مقداری باز نمی گرداند. در حقیقت، حتی توابع بدون عبارت return نیز یک مقدار باز می گردانند، هرچند که مقداری خسته کننده است. این مقدار None نام دارد(این یک نام داخلی است). معمولا نوشتن مقدار None , در صورتی که تنها مقدار نوشته شده باشد، توسط مفسر سرکوب می شود. با استفاده از تابع print() میتوانید آن را ببینید.
>>> fib(0)
>>> print(fib(0))
None
نوشتن یک تابع که لیستی از اعداد دنباله فیبوناتچی را به جای چاپ کردن، بازگرداند ساده است.
>>> def fib2(n): # return Fibonacci series up to n
... """Return a list containing the Fibonacci series up to n."""
... result = []
... a, b = 0, 1
... while a < n:
... result.append(a) # see below
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # call it
>>> f100 # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
این مثال، طبق معمول، تعدادی ویژگی جدید پایتون را نمایش می دهد.
1. عبارت return با یک مقدار از یک تابع باز می گردد. Return بدون آرگومان عبارت، none باز میگرداند. در انتهای یک تابع نیز none باز می گرداند.
2. عبارت result.append(a) یک متد از شی result لیست را صدا می کند. یک متد تابعی است که متعلق به یک شی است و به صورت obj.methodname نامگذاری می شود، که obj یک شی است (این می تواند یک اصطلاح باشد)، و methodname نام متدی است که توسط نوع شی تعریف شده است. انواع مختلف، متدهای مختلفی تعریف می کنند. متدها از انواع مختلف می توانند بدون بروز ابهام، نام مشابه داشته باشند. (همچنین می توان با استفاده از کلاس ها، انواع شی و متدهای خودتان را تعریف کنید. لینک Classes را ببینید.) متد append() نشان داده شده در مثال، برای اشیای لیست تعریف شده است. این متد یک عنصر جدید به انتهای لیست اضافه می کند که در این مثال با عبارت result = result + [a] برابر است اما کارآمد تر است.
همچنین میتوان توابعی با تعداد متغیری از آرگومان ها تعریف کرد. سه نوع وجود دارد که می توانند ترکیب شوند.
مفید ترین نوع، این است که یک مقدار پیش فرض برای یک یا چند آرگومان مشخص کنیم. این کار تابعی ایجاد می کند که با تعداد آرگومان کمتری نسبت به آنچه که در زمان تعریف اجازه داده شده است، قابل فراخوانی باشد. برای مثال:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
این تابع به چندین روش قابل فراخوانی است:
1-تنها ارائه آرگومان اجباری:
ask_ok('Do you really want to quit?')
2-ارائه یکی از آرگومان های اختیاری:
ask_ok('OK to overwrite the file?', 2)
3-حتی ارائه همه آرگومان ها :
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
این مثال همچنین کلمه کلیدی in را معرفی می کند. این کلمه کلیدی بررسی می کند که آیا یک دنباله شامل یک مقدار خاص می باشد یا خیر. مقادیر پیش فرض در نقطه تعریف تابع، درون حوزه تعریف، ارزیابی می شوند، بنابراین کد زیر عدد 5 را چاپ می کند.
i = 5
def f(arg=i):
print(arg)
i = 6
f()
مقدار پیش فرض تنها یک بار ارزیابی می شود. این امر زمانی که پیش فرض یک شی قابل تغییر (mutable) مانند یک لیست، دیکشنری یا یک نمونه از اکثر کلاس ها باشد، تفاوت ایجاد می کند. برای مثال، تابع زیر آرگومان های پاس داده شده به آن را در حین فراخوانی های پی در پی اضافه می کند:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
و خروجی چاپ شده:
[1]
[1, 2]
[1, 2, 3]
اگر نمی خواهید که پیش فرض بین فراخوانی های پی در پی مشترک باشد، می توانید کد را به صورت زیر بنویسید:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
همچنین میتوان توابع را با استفاده از آرگومان های کلمه کلیدی به صورت kwarg=value فراخوانی کرد. برای مثال، تابع زیر:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
یک آرگومان مورد نیاز (voltage) و سه آرگومان اختیاری (state، action و type) را می پذیرد. این تابع را می توان به هر یک از روش های زیر فراخوانی کرد:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
اما تمامی فراخوانی های زیر نامعتبر خواهند بود:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
در یک فراخوانی تابع، آرگومان های کلمه کلیدی باید از آرگومان های مکانی (positional arguments) پیروی کنند. تمامی آرگومان های کلمه کلیدی پاس داده شده، باید مطابق با یکی از آرگومان های پذیرفته شده توسط تابع باشند (برای مثال actor یک آرگومان معتبر برای تابع parrot نیست)، و ترتیب آنها مهم نیست. این مساله شامل آرگومان های غیر اختیاری نیز می شود (برای مثال parrot(voltage=1000) نیز معتبر است) . هیچ آرگومانی بیشتر از یک بار مقدار دریافت نمی کند. در اینجا مثالی وجود دارد که به دلیل همین محدودیت شکست می خورد.
>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for keyword argument 'a'
</module></stdin>
زمانی که یک پارامتر رسمی نهایی به صورت **name وجود دارد، یک دیکشنری شامل همه آرگومان های کلمه کلیدی به جز آنهایی که متناظر با یک پارامتر رسمی هستند، دریافت می کند.
ممکن است این با یک پارامتر رسمی به صورت *name (در بخش بعدی توضیح داده می شود) ترکیب شود که فراتر از لیست پارامتر رسمی، یک چندتایی (tuple) شامل آرگومان های مکانی دریافت می کند. (*name باید قبل از *name* رخ دهد.) برای مثال، اگر یک تابع را به صوت زیر تعریف کنیم:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
به صورت زیر میتواند فراخوانی شود:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
و البته خروجی زیر را چاپ می کند:
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
توجه داشته باشید، تطابق ترتیب چاپ آرگومان های کلمه کلیدی با ترتیب ارائه آنها در فراخوانی تابع، تضمین می شود.
به طور پیش فرض، آرگومان ها به واسطه مکان یا صراحتا با کلمه کلیدی، به یک تابع پایتون پاس داده می شوند. برای خوانایی و کارایی، بهتر است روش های پاس دادن آرگومان ها را محدود کنیم، در نتیجه یک توسعه دهنده تنها با نگاه به تعریف تابع، تعیین می کند که عناصر توسط مکان، توسط مکان-یا-کلمه کلیدی، یا توسط کلمه کلیدی پاس داده شده اند. تعریف یک تابع به صورت زیر است:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
که در آن / و * اختیاری هستند. در صورت استفاده، این نمادها بیان کننده نوع و نحوه پارامترهایی است که آرگومان ها توسط آنها به تابع پاس داده شده اند. فقط مکانی، مکانی-یا-کلمه کلیدی، فقط کلمه کلیدی. به پارامترهای کلمه کلیدی با عنوان پارامترهای نامگذاری شده (named parameters) نیز ارجاع داده می شود.
اگر / و * در تعریف تابع حضور نداشته باشد، ممکن است آرگومان ها توسط مکان یا کلمه کلیدی به یک تابع پاس داده شوند.
اگر به جزییات بیشتر توجه کنیم، می بینیم که می شود برخی از پارامترهای خاص را به صورت فقط مکانی علامت گذاری کرد.
اگر پارامترها فقط مکانی باشد، ترتیب آنها مهم است، و پارامترها نمی توانند با کلمه کلیدی پاس داده شوند. پارامترهای فقط مکانی قبل از / (forward-slash) قرار میگیرد. از / برای جدا کردن منطقی پارامترهای فقط مکانی از سایر پارامترها استفاده می شود. اگر هیچ / در تعریف تابع وجود نداشته باشد، پس هیچ پارامتر فقط مکانی نیز وجود ندارد. پارامترهایی که پس از / می آیند می توانند مکانی-یا-کلمه کلیدی یا فقط کلمه کلیدی باشند.
برای علامت گذاری پارامترها به عنوان فقط کلمه کلیدی، نمایش پارامترها باید توسط آرگومان کلمه کلیدی پاس داده شود. به این منظور یک * را قبل از اولین پارامتر فقط کلمه کلیدی در لیست آرگومان ها قرار دهید.
: تعریف توابع در مثال های زیر را ببینید و به علامت های / و * خوب توجه کنید:
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
در اولین تعریف تابع، standard_arg ، که آشنا ترین فرم نیز هست، هیچ محدودیتی روی قرارداد فراخوانی وجود ندارد و آرگومان ها ممکن است با مکان یا کلمه کلیدی پاس داده شوند.
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
در تابع دوم، pos_only_arg ، با توجه به وجود یک / در تعریف تابع، تابع فقط به استفاده از پارامترهای مکانی محدود شده است.
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
</module></stdin>
سومین تابع، kwd_only_args ، فقط آرگومان های کلمه کلیدی که با * در تعریف تابع بیان شده اند را مجاز می شمارد.
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
</module></stdin>
و آخرین مورد از هر سه قرارداد فراخوانی در همان تعریف تابع استفاده می کند.
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
</module></stdin></module></stdin>
در انتها، این تعریف تابع که احتمال تضاد بین آرگومان مکانی name و **kwds که name را با عنوان کلید دارد، در نظر بگیرید:
def foo(name, **kwds):
return 'name' in kwds
از آنجایی که کلمه کلیدی 'name' همیشه به اولین پارامتر متصل است، احتمال هرگونه فراخوانی که True را باز گرداند وجود ندارد. برای مثال:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
</module></stdin>
اما در صورت استفاده از / (آرگومان های فقط مکانی)، این احتمال ممکن است رخ دهد، زیرا name را یک آرگومان مکانی و ‘name’ را به عنوان کلید در آرگومان های کلمه کلیدی مجاز می داند.
def foo(name, /, **kwds):
return 'name' in kwds
>>> foo(1, **{'name': 2})
True
به بیان دیگر، می توان از نام های پارامترهای فقط مکانی، بدون ابهام در **kwds استفاده کرد.
موارد استفاده، تعیین کننده این است که در تعریف تابع از کدام پارامترها استفاده کنیم.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
به عنوان راهنما:
1-در صورتی که نمی خواهید نام پارامترها در دسترس کاربر باشد، از فقط مکانی استفاده کنید. در زمانی که نام پارامترها معنی واقعی ندارند، یا اگر در زمان فراخوانی تابع میخواهید ترتیب آرگومان ها را اجرا کنید، یا اگر نیاز به گرفتن تعدادی پارامتر مکانی و کلمات کلیدی اختیاری داشته باشید، فقط مکانی مفید است .
2- آزمانی که نام ها دارای معنی هستند و تعریف تابع در صورت استفاده صریح از نام قابل فهم تر است، یا می خواهید از اتکای کاربران به مکان رگومان های پاس داده شده جلوگیری کنید ، از فقط کلمه کلیدی استفاده کنید.
3- برای API از فقط مکانی استفاده کنید، تا در صورت تغییر نام پارامتر در آینده، از اعمال تغییرات در API جلوگیری شود.
در انتها، کم استفاده ترین گزینه این است که مشخص کنید یک تابع می تواند با تعداد اختیاری از آرگومان ها فراخوانی شود. این آرگومان ها در یک چندتایی (tuple) قرار می گیرند
ممکن است پیش از تعداد متغیر آرگومان، هیچ و یا بیشتر آرگومان عادی باشد.
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
عموما، این آرگومان های variadic در آخر لیست پارامترهای رسمی قرار می گیرند، زیرا آنها تمامی آرگومان های ورودی باقی مانده که به تابع پاس داده شده اند را، جا به جا می کنند. هر پارامتر رسمی که پس از پارامتر *args آید، آرگومان های فقط کلمه کلیدی هستند، به این معنی که تنها به عنوان کلمات کلیدی قابل استفاده هستند نه آرگومان های مکانی.
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'