-
-
-
-
-4. ابزارهای بیشتر برای کنترل جریان
-بر عبارت while که به تازگی معرفی شد، پایتون از چند عبارت دیگر نیز استفاده میکند که در این فصل با آنها روبهرو خواهیم شد.
-4.1. عبارات if
-شاید شناختهشدهترین نوع عبارت، عبارت if باشد. برای مثال:
->>> x = int(input("Please enter an integer: "))
-Please enter an integer: 42
->>> if x < 0:
-... x = 0
-... print('Negative changed to zero')
->>> elif x == 0:
-... print('Zero')
->>> elif x == 1:
-... print('Single')
->>> else:
-... print('More')
-...
-More
-
-ممکن است هیچ یا چندین بخش elif وجود داشته باشد و بخش else نیز اختیاری است. کلمه کلیدی «elif» کوتاهشدهی «else if» است و برای جلوگیری از تورفتگی بیش از حد مفید است. دنبالهای از if … elif … elif … جایگزینی برای عبارات switch
یا case
در زبانهای دیگر است.
-اگر در حال مقایسهی یک مقدار با چند ثابت هستید یا در حال بررسی نوعها یا ویژگیهای خاصی هستید، ممکن است عبارت match نیز برای شما مفید باشد. برای جزئیات بیشتر به عبارات match مراجعه کنید.
-4.2. عبارات for
-عبارت for در پایتون کمی با آنچه ممکن است در زبانهایی مانند C یا Pascal به آن عادت داشته باشید، متفاوت است. به جای اینکه همیشه بر روی یک پیشرفت حسابی از اعداد تکرار کند (مانند Pascal)، یا به کاربر امکان تعریف گام تکرار و شرط توقف را بدهد (مانند C)، عبارت for در پایتون بر روی آیتمهای هر دنبالهای (مانند لیست یا رشته) به ترتیبی که در دنباله ظاهر میشوند، تکرار میکند. برای مثال: (بدون قصد بازی با کلمات).
->>> # Measure some strings:
->>> words = ['cat', 'window', 'defenestrate']
->>> for w in words:
-... print(w, len(w))
-...
-cat 3
-window 6
-defenestrate 12
-
-کدی که یک مجموعه را هنگام تکرار روی همان مجموعه تغییر میدهد، ممکن است دشوار باشد که به درستی نوشته شود. به جای آن، معمولاً سادهتر است که روی یک کپی از مجموعه تکرار کنید یا یک مجموعه جدید ایجاد کنید:
-# Create a sample collection
-users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
-
-# Strategy: Iterate over a copy
-for user, status in users.copy().items():
- if status == 'inactive':
- del users[user]
-
-# Strategy: Create a new collection
-active_users = {}
-for user, status in users.items():
- if status == 'active':
- active_users[user] = status
-
-
-اگر نیاز دارید که بر روی دنبالهای از اعداد تکرار کنید، تابع داخلی ()range به کارتان میآید. این تابع پیشرفتهای حسابی را تولید میکند:
->>> for i in range(5):
-... print(i)
-...
-0
-1
-2
-3
-4
-
-نقطه انتهایی داده شده هرگز بخشی از دنباله تولید شده نیست؛ range(10)
ده مقدار تولید میکند که شاخصهای قانونی برای آیتمهای یک دنباله به طول 10 هستند. این امکان وجود دارد که بازه از یک عدد دیگر شروع شود یا افزایش (حتی منفی؛ که گاهی "گام" نامیده میشود) دیگری را مشخص کنید:
->>> list(range(5, 10))
-[5, 6, 7, 8, 9]
->>> list(range(0, 10, 3))
-[0, 3, 6, 9]
->>> list(range(-10, -100, -30))
-[-10, -40, -70]
-
-برای تکرار بر روی شاخصهای یک دنباله، میتوانید ()range را با ()len به این صورت ترکیب کنید:
->>> a = ['Mary', 'had', 'a', 'little', 'lamb']
->>> for i in range(len(a)):
-... print(i, a[i])
-...
-0 Mary
-1 had
-2 a
-3 little
-4 lamb
-
-در بیشتر چنین مواردی، استفاده از تابع ()enumerate که در بخش تکنیکهای حلقهزنی توضیح داده میشود، راحتتر است.
-یک اتفاق عجیب رخ میدهد اگر فقط یک بازه را چاپ کنید:
-range(10)
-range(0, 10)
-
-در بسیاری از موارد، شیء بازگشتی از ()range مانند یک لیست رفتار میکند، اما در واقع اینطور نیست. این یک شیء است که آیتمهای متوالی دنباله مورد نظر را زمانی که روی آن تکرار میکنید برمیگرداند، اما لیست واقعی را نمیسازد، بنابراین در فضا صرفهجویی میکند.
-ما به چنین شیئی میگوییم iterable (قابل پیمایش)، یعنی مناسب به عنوان هدف برای توابع و ساختارهایی که انتظار دارند چیزی که میتوانند از آن آیتمهای متوالی را دریافت کنند تا زمانی که تمام شوند. ما قبلاً دیدهایم که دستور for چنین ساختاری است، در حالی که مثالی از تابعی که یک iterable میگیرد، تابع ()sum است:
-sum(range(4)) # 0 + 1 + 2 + 3
-6
-
-بعداً توابع بیشتری خواهیم دید که iterableها را برمیگردانند و iterableها را به عنوان آرگومانها میپذیرند. در فصل ساختارهای داده، در مورد ()list به تفصیل بیشتری بحث خواهیم کرد.
-4.4. عبارات break و continue
-دستور break از درونیترین حلقهی for یا while خارج میشود:
->>> for n in range(2, 10):
-... for x in range(2, n):
-... if n % x == 0:
-... print(f"{n} equals {x} * {n//x}")
-... break
-...
-4 equals 2 * 2
-6 equals 2 * 3
-8 equals 2 * 4
-9 equals 3 * 3
-
-دستور continue با تکرار بعدی حلقه ادامه میدهد:
->>> for num in range(2, 10):
-... if num % 2 == 0:
-... print(f"Found an even number {num}")
-... continue
-... print(f"Found an odd number {num}")
-...
-Found an even number 2
-Found an odd number 3
-Found an even number 4
-Found an odd number 5
-Found an even number 6
-Found an odd number 7
-Found an even number 8
-Found an odd number 9
-
-4.5. عبارات else در حلقهها
-در یک حلقه for
یا while
، دستور break
ممکن است با یک بخش else
همراه شود. اگر حلقه بدون اجرای break
به پایان برسد، بخش else
اجرا میشود.
-در حلقه for، بخش else
بعد از اتمام آخرین تکرار حلقه اجرا میشود، یعنی در صورتی که هیچ break
رخ نداده باشد.
-در حلقه while، این بخش بعد از اینکه شرط حلقه نادرست شد، اجرا میشود.
-در هر نوع حلقهای، بخش else
در صورتی اجرا نمیشود که حلقه با break متوقف شده باشد. به طور طبیعی، سایر روشهای خاتمه دادن زودهنگام حلقه مانند استفاده از return یا رخ دادن یک استثنا (exception)، باعث میشود که بخش else نیز اجرا نشود.
-این موضوع در مثال زیر با یک حلقه for
که به دنبال اعداد اول میگردد، نشان داده شده است:
->>> 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
این است که آن را در کنار دستور if
درون حلقه تصور کنید. زمانی که حلقه اجرا میشود، توالیای مانند if/if/if/else
را اجرا میکند. دستور if
درون حلقه قرار دارد و چندین بار با آن برخورد میشود. اگر شرط if
در هر زمانی درست باشد، دستور break
اجرا میشود. اگر شرط هیچگاه درست نباشد، بخش else
خارج از حلقه اجرا میشود.
-هنگامی که از بخش else
در کنار یک حلقه استفاده میشود، این بخش بیشتر به بخش else
در دستور try شباهت دارد تا به بخش else
در دستور if
: بخش else
در یک دستور try
زمانی اجرا میشود که هیچ استثنایی رخ ندهد و بخش else
در یک حلقه زمانی اجرا میشود که هیچ break
اتفاق نیفتد. برای اطلاعات بیشتر در مورد دستور try
و استثناها، به بخش "مدیریت استثناها" مراجعه کنید.
-4.6. عبارات 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!
-...
-
-4.7. عبارات match
-ترجمه:
-یک دستور match یک عبارت را میگیرد و مقدار آن را با الگوهای متوالی که بهصورت یک یا چند بلوک case
داده شدهاند، مقایسه میکند. این از نظر ظاهری شبیه به دستور switch
در زبانهایی مانند C، جاوا یا جاوااسکریپت (و بسیاری دیگر از زبانها) است، اما بیشتر به تطابق الگو (pattern matching) در زبانهایی مانند Rust یا Haskell شباهت دارد. تنها اولین الگویی که مطابقت داشته باشد، اجرا میشود و همچنین میتواند اجزا (عناصر یک دنباله یا ویژگیهای یک شیء) را از مقدار به متغیرها استخراج کند.
-سادهترین شکل آن، یک مقدار موضوعی را با یک یا چند مقدار ثابت مقایسه میکند:
-def http_error(status):
- match status:
- case 400:
- return "Bad request"
- case 404:
- return "Not found"
- case 418:
- return "I'm a teapot"
- case _:
- return "Something's wrong with the internet"
-
-به بلوک آخر توجه کنید: نام متغیر _
بهعنوان یک کاراکتر wildcard عمل میکند و هرگز در مطابقت شکست نمیخورد. اگر هیچ کدام از حالات مطابقت نداشته باشد، هیچکدام از شاخهها اجرا نمیشود.
-شما میتوانید چندین مقدار ثابت را در یک الگوی واحد با استفاده از |
(عملگر "یا") ترکیب کنید:
-case 401 | 403 | 404:
- return "Not allowed"
-
-الگوها میتوانند شبیه انتسابهای unpacking به نظر برسند و میتوانند برای متصل کردن متغیرها استفاده شوند:
-# point is an (x, y) tuple
-match point:
- case (0, 0):
- print("Origin")
- case (0, y):
- print(f"Y={y}")
- case (x, 0):
- print(f"X={x}")
- case (x, y):
- print(f"X={x}, Y={y}")
- case _:
- raise ValueError("Not a point")
-
-آن را با دقت مطالعه کنید! الگوی اول دارای دو مقدار ثابت است و میتوان آن را بهعنوان گسترشی از الگوی ثابت نشاندادهشده در بالا در نظر گرفت. اما دو الگوی بعدی یک مقدار ثابت و یک متغیر را ترکیب میکنند، و متغیر مقداری را از موضوع (point
) متصل میکند. الگوی چهارم دو مقدار را ضبط میکند که از نظر مفهومی مشابه انتساب unpacking (x, y) = point
است.
-اگر از کلاسها برای ساختاردهی دادههای خود استفاده میکنید، میتوانید از نام کلاس بههمراه یک لیست آرگومان شبیه به یک سازنده استفاده کنید، اما با قابلیت ضبط ویژگیها در متغیرها:
-class Point:
- def __init__(self, x, y):
- self.x = x
- self.y = y
-
-def where_is(point):
- match point:
- case Point(x=0, y=0):
- print("Origin")
- case Point(x=0, y=y):
- print(f"Y={y}")
- case Point(x=x, y=0):
- print(f"X={x}")
- case Point():
- print("Somewhere else")
- case _:
- print("Not a point")
-
-میتوانید از پارامترهای موقعیتی با برخی از کلاسهای داخلی که برای ویژگیهای خود ترتیبی ارائه میدهند (مثلاً dataclass
ها) استفاده کنید. همچنین میتوانید با تنظیم ویژگی ویژهی __match_args__
در کلاسهای خود، موقعیت خاصی برای ویژگیها در الگوها تعریف کنید. اگر این ویژگی به ("x"، "y") تنظیم شود، الگوهای زیر همگی معادل خواهند بود (و همگی ویژگی y
را به متغیر var
متصل میکنند):
-Point(1, var)
-Point(1, y=var)
-Point(x=1, y=var)
-Point(y=var, x=1)
-
-یک روش پیشنهادی برای خواندن الگوها این است که به آنها بهعنوان شکلی گسترده از آنچه در سمت چپ یک انتساب قرار میدهید نگاه کنید، تا بفهمید کدام متغیرها به چه چیزی اختصاص داده میشوند. تنها نامهای مستقل (مانند var
در مثال بالا) توسط دستور match
مقداردهی میشوند. نامهای نقطهگذاریشده (مانند foo.bar
)، نام ویژگیها (مانند x=
و y=
در مثال بالا) یا نام کلاسها (که با “(…)” کنار آنها مانند Point
در مثال بالا شناخته میشوند) هرگز مقداردهی نمیشوند.
-الگوها میتوانند بهصورت دلخواه تودرتو باشند. برای مثال، اگر یک لیست کوتاه از نقاط (Points) داشته باشیم که ویژگی __match_args__
به آن اضافه شده باشد، میتوانیم به این صورت آن را مطابقت دهیم:
-class Point:
- __match_args__ = ('x', 'y')
- def __init__(self, x, y):
- self.x = x
- self.y = y
-
-match points:
- case []:
- print("No points")
- case [Point(0, 0)]:
- print("The origin")
- case [Point(x, y)]:
- print(f"Single point {x}, {y}")
- case [Point(0, y1), Point(0, y2)]:
- print(f"Two on the Y axis at {y1}, {y2}")
- case _:
- print("Something else")
-
-ما میتوانیم یک بخش if
به یک الگو اضافه کنیم که بهعنوان "محافظ" (guard) شناخته میشود. اگر محافظ نادرست باشد، match
به بررسی بلوک case
بعدی ادامه میدهد. توجه داشته باشید که مقداردهی (capture) متغیرها قبل از ارزیابی محافظ انجام میشود.
-match point:
- case Point(x, y) if x == y:
- print(f"Y=X at {x}")
- case Point(x, y):
- print(f"Not on the diagonal")
-
-چند ویژگی کلیدی دیگر این دستور:
-
--
-
مانند انتسابهای unpacking، الگوهای tuple
و list
دقیقاً معنای یکسانی دارند و عملاً با دنبالههای دلخواه مطابقت مییابند. یک استثنای مهم این است که آنها با iterator
ها یا رشتهها مطابقت ندارند.
-
--
-
الگوهای دنبالهای از unpacking گسترده پشتیبانی میکنند: [x, y, *rest]
و (x, y, *rest)
شبیه به انتسابهای unpacking عمل میکنند. نامی که بعد از *
میآید میتواند _
باشد، بنابراین (x, y, *_)
با دنبالهای از حداقل دو آیتم مطابقت مییابد بدون اینکه آیتمهای باقیمانده را به متغیری متصل کند.
-
--
-
الگوهای نگاشت: {"bandwidth": b, "latency": l}
مقادیر "bandwidth" و "latency" را از یک دیکشنری استخراج میکند. برخلاف الگوهای دنبالهای، کلیدهای اضافی نادیده گرفته میشوند. یک unpacking مانند **rest
نیز پشتیبانی میشود (اما **_
اضافی است، بنابراین مجاز نیست).
-
--
-
الگوهای فرعی را میتوان با استفاده از کلیدواژه as
استخراج کرد:
-
-
-python
- case (Point(x1, y1), Point(x2, y2) as p2): ...
-این کد عنصر دوم ورودی را بهعنوان p2
ضبط میکند (مشروط بر اینکه ورودی یک دنباله از دو نقطه باشد).
-
--
-
بیشتر مقادیر ثابت با برابری مقایسه میشوند، اما True
، False
و None
با هویت مقایسه میشوند.
-
--
-
الگوها میتوانند از ثابتهای نامگذاریشده استفاده کنند. این ثابتها باید بهصورت نامهای نقطهگذاریشده (dotted names) باشند تا بهعنوان متغیرهای capture تفسیر نشوند:
-
-
-```python
- from enum import Enum
- class Color(Enum):
- RED = 'red'
- GREEN = 'green'
- BLUE = 'blue'
-color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
-match color:
- case Color.RED:
- print("I see red!")
- case Color.GREEN:
- print("Grass is green")
- case Color.BLUE:
- print("I'm feeling the blues :(")
- ```
-برای توضیحات دقیقتر و مثالهای بیشتر، میتوانید به PEP 636 مراجعه کنید که بهصورت یک آموزش نوشته شده است.
-4.8. تعریف توابع
-ما میتوانیم یک تابع ایجاد کنیم که سری فیبوناچی را تا یک مرز دلخواه بنویسد:
->>> def fib(n): # write Fibonacci series less than n
-... """Print a Fibonacci series less than 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 یک تعریف تابع را معرفی میکند. باید با نام تابع و فهرست پارامترهای رسمی داخل پرانتز دنبال شود. دستورات تشکیلدهنده بدنه تابع در خط بعدی شروع میشوند و باید تورفتگی داشته باشند.
-اولین دستور در بدنه تابع میتواند بهصورت اختیاری یک رشته (string literal) باشد؛ این رشته، مستندات تابع (یا docstring) است. (اطلاعات بیشتر درباره docstringها را میتوانید در بخش "مستندات رشتهها" پیدا کنید.) ابزارهایی وجود دارند که از docstringها برای تولید خودکار مستندات آنلاین یا چاپی استفاده میکنند، یا به کاربر اجازه میدهند تا بهصورت تعاملی در کد مرور کند؛ بنابراین، بهتر است در کدی که مینویسید docstringها را درج کنید و این را به یک عادت تبدیل کنید.
-اجرای یک تابع یک جدول نماد جدید برای متغیرهای محلی تابع معرفی میکند. به طور دقیقتر، تمام انتسابهای متغیر در یک تابع مقدار را در جدول نماد محلی ذخیره میکنند؛ در حالی که ارجاعات متغیر ابتدا در جدول نماد محلی جستجو میشوند، سپس در جداول نماد محلی توابع بیرونی، سپس در جدول نماد سراسری و در نهایت در جدول نامهای داخلی. بنابراین، نمیتوان مستقیماً به متغیرهای سراسری و متغیرهای توابع بیرونی درون یک تابع مقدار داد (مگر اینکه متغیرهای سراسری در یک دستور global و متغیرهای توابع بیرونی در یک دستور nonlocal نامگذاری شوند)، هرچند میتوان به آنها ارجاع داد.
-پارامترهای واقعی (آرگومانها) بههنگام فراخوانی تابع در جدول نماد محلی تابع فراخوانیشده وارد میشوند؛ بنابراین، آرگومانها بهصورت "فراخوانی با مقدار" ارسال میشوند (که در آن مقدار همیشه یک ارجاع به شیء است، نه مقدار خود شیء). [^1] هنگامی که یک تابع تابع دیگری را فراخوانی میکند یا خود را بهصورت بازگشتی فراخوانی میکند، یک جدول نماد محلی جدید برای آن فراخوانی ایجاد میشود.
-تعریف تابع، نام تابع را با شیء تابع در جدول نماد فعلی پیوند میدهد. مفسر شیءای را که به آن نام اشاره دارد بهعنوان یک تابع کاربر تعریفشده تشخیص میدهد. نامهای دیگر نیز میتوانند به همان شیء تابع اشاره کنند و از طریق آنها میتوان به تابع دسترسی پیدا کرد.
->>> fib
-<function fib at 10042ed0>
->>> f = fib
->>> f(100)
-0 1 1 2 3 5 8 13 21 34 55 89
-
-اگر از زبانهای برنامهنویسی دیگر آمده باشید، ممکن است اعتراض کنید که fib
یک تابع نیست بلکه یک رویه است، زیرا مقداری را برنمیگرداند. در واقع، حتی توابعی که دستور 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]
-
-این مثال، مانند همیشه، برخی از ویژگیهای جدید پایتون را نشان میدهد:
-
--
-
دستور return مقداری را از یک تابع برمیگرداند. استفاده از return
بدون آرگومان، مقدار None
را برمیگرداند. پایان یافتن تابع نیز باعث بازگشت مقدار None
میشود.
-
--
-
دستور result.append(a)
یک متد از شیء لیست result
را فراخوانی میکند. یک متد تابعی است که به یک شیء «تعلق» دارد و بهصورت obj.methodname
نامگذاری میشود، که در آن obj
یک شیء است (که میتواند یک عبارت باشد) و methodname
نام متدی است که توسط نوع شیء تعریف شده است. انواع مختلف متدهای متفاوتی را تعریف میکنند. متدهای انواع مختلف میتوانند نام یکسانی داشته باشند بدون اینکه ابهامی ایجاد شود. (با استفاده از کلاسها میتوان انواع شیء و متدهای خود را تعریف کرد؛ برای اطلاعات بیشتر، به بخش کلاسها مراجعه کنید). متد append()
که در مثال نشان داده شده، برای اشیاء لیست تعریف شده است؛ این متد یک عنصر جدید را به انتهای لیست اضافه میکند. در این مثال، معادل با result = result + [a]
است، اما کارآمدتر است.
-
-
-4.9. اطلاعات بیشتر در مورد تعریف توابع
-همچنین میتوان توابعی با تعداد متغیری از آرگومانها تعریف کرد. سه شکل مختلف وجود دارد که میتوان آنها را با هم ترکیب کرد.
-4.9.1. مقادیر پیشفرض آرگومانها
-مفیدترین شکل، مشخص کردن یک مقدار پیشفرض برای یک یا چند آرگومان است. این کار تابعی ایجاد میکند که میتواند با تعداد کمتری آرگومان از آنچه که برای آن تعریف شده است فراخوانی شود. برای مثال:
-def ask_ok(prompt, retries=4, reminder='Please try again!'):
- while True:
- reply = input(prompt)
- if reply in {'y', 'ye', 'yes'}:
- return True
- if reply in {'n', 'no', 'nop', 'nope'}:
- return False
- retries = retries - 1
- if retries < 0:
- raise ValueError('invalid user response')
- print(reminder)
-
-این تابع را میتوان به چندین روش فراخوانی کرد:
-
--
-
با دادن فقط آرگومان اجباری: ask_ok('Do you really want to quit?')
-
--
-
با دادن یکی از آرگومانهای اختیاری: ask_ok('OK to overwrite the file?', 2)
-
--
-
یا حتی با دادن تمام آرگومانها: ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
-
-
-این مثال همچنین کلیدواژه in را معرفی میکند. این کلیدواژه بررسی میکند که آیا یک دنباله شامل یک مقدار خاص است یا خیر.
-مقدارهای پیشفرض در نقطه تعریف تابع در دامنه تعریفشده ارزیابی میشوند، بنابراین
-i = 5
-
-def f(arg=i):
- print(arg)
-
-i = 6
-f()
-
-5 را چاپ میکند.
-هشدار مهم: مقدار پیشفرض تنها یکبار ارزیابی میشود. این موضوع زمانی که مقدار پیشفرض یک شیء قابل تغییر (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
-
-4.9.2. آرگومانهای کلیدی
-توابع همچنین میتوانند با استفاده از آرگومانهای کلیدی به شکل 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, "!")
-
-یک آرگومان اجباری (ولتاژ
) و سه آرگومان اختیاری (وضعیت
، عمل
و نوع
) را میپذیرد. این تابع میتواند به هر یک از روشهای زیر فراخوانی شود:
-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
-
-در یک فراخوانی تابع، آرگومانهای کلیدی باید پس از آرگومانهای موقعیتی بیایند. تمام آرگومانهای کلیدی که ارسال میشوند باید با یکی از آرگومانهایی که تابع قبول میکند مطابقت داشته باشند (برای مثال، 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 argument 'a'
-
-ترجمه:
-هنگامی که یک پارامتر رسمی نهایی بهصورت **name
وجود داشته باشد، یک دیکشنری (به بخش انواع نگاشتها — dict مراجعه کنید) را دریافت میکند که شامل تمام آرگومانهای کلیدی بهجز آنهایی است که به یک پارامتر رسمی مربوط میشوند. این میتواند با یک پارامتر رسمی بهصورت *name
(که در زیر بخش بعدی توصیف شده است) ترکیب شود که یک تاپل شامل آرگومانهای موقعیتی فراتر از فهرست پارامترهای رسمی را دریافت میکند. (*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
-
-توجه داشته باشید که ترتیب آرگومانهای کلیدی که چاپ میشوند، تضمین شده است که با ترتیبی که در فراخوانی تابع ارائه شدهاند، مطابقت دارد.
-4.9.3. پارامترهای خاص
-بهطور پیشفرض، آرگومانها میتوانند به یک تابع پایتون بهصورت موقعیتی یا بهطور صریح با کلیدواژه ارسال شوند. برای بهبود خوانایی و کارایی، منطقی است که روشهایی را که آرگومانها میتوانند ارسال شوند محدود کنیم تا یک توسعهدهنده تنها با نگاه کردن به تعریف تابع بتواند تعیین کند که آیا آیتمها بهصورت موقعیتی، بهصورت موقعیتی یا کلیدواژهای، یا بهصورت کلیدواژهای ارسال میشوند.
-تعریف یک تابع ممکن است به شکل زیر باشد:
-def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
- ----------- ---------- ----------
- | | |
- | Positional or keyword |
- | - Keyword only
- -- Positional only
-
-جایی که /
و *
اختیاری هستند. اگر استفاده شوند، این نمادها نوع پارامتر را با توجه به نحوهی ارسال آرگومانها به تابع نشان میدهند: تنها موقعیتی، موقعیتی یا کلیدواژهای، و فقط کلیدواژهای. پارامترهای کلیدواژهای همچنین بهعنوان پارامترهای نامگذاری شده نیز شناخته میشوند.
-4.9.3.1. آرگومانهای موقعیتی یا کلیدی
-اگر /
و *
در تعریف تابع وجود نداشته باشند، آرگومانها میتوانند به یک تابع بهصورت موقعیتی یا بهصورت کلیدواژهای ارسال شوند.
-4.9.3.2. پارامترهای فقط موقعیتی
-نگاهی دقیقتر به این موضوع نشان میدهد که امکان مشخص کردن برخی پارامترها بهعنوان تنها موقعیتی وجود دارد. اگر پارامترها تنها موقعیتی باشند، ترتیب آنها اهمیت دارد و نمیتوان آنها را بهصورت کلیدواژهای ارسال کرد. پارامترهای تنها موقعیتی قبل از یک /
(خطمایل) قرار میگیرند. /
برای جدا کردن منطقی پارامترهای تنها موقعیتی از سایر پارامترها استفاده میشود. اگر در تعریف تابع هیچ /
وجود نداشته باشد، پارامترهای تنها موقعیتی وجود ندارند.
-پارامترهایی که پس از /
قرار دارند، میتوانند موقعیتی یا کلیدواژهای یا تنها کلیدواژهای باشند.
-4.9.3.3. آرگومانهای فقط کلیدی
-برای مشخص کردن پارامترها بهعنوان تنها کلیدواژهای، که نشان میدهد این پارامترها باید با آرگومان کلیدواژهای ارسال شوند، یک *
در فهرست آرگومانها درست قبل از اولین پارامتر تنها کلیدواژهای قرار دهید.
-4.9.3.4. مثالهای تابع
-به تعریفهای تابع مثال زیر توجه کنید و به نشانهگذاران /
و *
دقت کنید:
->>> 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 some positional-only arguments passed as keyword arguments: 'arg'
-
-تعریف تابع سوم، kwd_only_arg
، تنها آرگومانهای کلیدواژهای را مجاز میشمارد که با وجود یک *
در تعریف تابع نشان داده شده است:
->>> 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
-
-و آخرین تابع از هر سه روش فراخوانی در یک تعریف تابع استفاده میکند:
->>> 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 some positional-only arguments passed as keyword arguments: 'pos_only'
-
-ترجمه:
-در نهایت، به این تعریف تابع توجه کنید که احتمال تداخل بین name آرگومان موقعیتی و **kwds
که name
را بهعنوان یک کلید دارد، وجود دارد:
-def foo(name, **kwds):
- return 'name' in kwds
-
-هیچ تماسی وجود ندارد که بتواند باعث شود این تابع True
برگرداند، زیرا کلمه کلیدی 'name'
همیشه به اولین پارامتر متصل میشود. برای مثال:
->>> foo(1, **{'name': 2})
-Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
-TypeError: foo() got multiple values for argument 'name'
->>>
-
-اما با استفاده از /
(آرگومانهای فقط موقعیتی)، این امکان وجود دارد، زیرا این روش اجازه میدهد که name
به عنوان یک آرگومان موقعیتی و 'name' به عنوان یک کلید در آرگومانهای کلیدی استفاده شود.
->>> def foo(name, /, **kwds):
-... return 'name' in kwds
-...
->>> foo(1, **{'name': 2})
-True
-
-به عبارت دیگر، نامهای پارامترهای فقط موقعیتی میتوانند بدون ابهام در **kwds
استفاده شوند.
-4.9.3.5. خلاصه
-موارد استفاده تعیینکنندهی این خواهند بود که کدام پارامترها در تعریف تابع استفاده شوند:
-def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
-
-به عنوان راهنما:
-
--
-
از پارامترهای فقط موقعیتی استفاده کنید اگر میخواهید نام پارامترها برای کاربر قابل دسترسی نباشد. این امر زمانی مفید است که نامهای پارامتر معنای واقعی ندارند، اگر میخواهید ترتیب آرگومانها هنگام فراخوانی تابع را تحمیل کنید یا اگر نیاز دارید که برخی پارامترهای موقعیتی و کلیدواژههای دلخواه را بپذیرید.
-
--
-
از پارامترهای فقط کلیدی استفاده کنید زمانی که نامها معنا دارند و تعریف تابع با وضوح نامها بیشتر قابل درک است یا میخواهید از وابستگی کاربران به موقعیت آرگومانهای منتقل شده جلوگیری کنید.
-
--
-
برای یک API، از پارامترهای فقط موقعیتی استفاده کنید تا از تغییرات شکستن API جلوگیری کنید اگر نام پارامتر در آینده تغییر کند.
-
-
-4.9.4 لیستهای آرگومان دلخواه
-سرانجام، کمترین گزینهی استفاده شده مشخص کردن این است که یک تابع میتواند با تعداد دلخواهی از آرگومانها فراخوانی شود. این آرگومانها در یک تاپل قرار میگیرند (به تاپلها و دنبالهها مراجعه کنید). قبل از آرگومانهای متغیر، ممکن است صفر یا بیشتر آرگومانهای عادی وجود داشته باشد.
-def write_multiple_items(file, separator, *args):
- file.write(separator.join(args))
-
-به طور معمول، این آرگومانهای متغیر در انتهای فهرست پارامترهای رسمی قرار میگیرند، زیرا آنها تمام آرگومانهای ورودی باقیماندهای که به تابع منتقل میشوند را جمعآوری میکنند. هر پارامتر رسمی که بعد از پارامتر *args بیاید، آرگومانهای «فقط کلیدی» هستند، به این معنی که میتوانند فقط به عنوان کلیدواژهها استفاده شوند و نه به عنوان آرگومانهای موقعیتی.
->>> def concat(*args, sep="/"):
-... return sep.join(args)
-...
->>> concat("earth", "mars", "venus")
-'earth/mars/venus'
->>> concat("earth", "mars", "venus", sep=".")
-'earth.mars.venus'
-
-4.9.5. پک کردن لیستهای آرگومان
-وضعیت معکوس زمانی اتفاق میافتد که آرگومانها قبلاً در یک لیست یا تاپل قرار دارند، اما نیاز است که برای فراخوانی تابعی که به آرگومانهای موقعیتی جداگانه نیاز دارد، از هم جدا شوند. به عنوان مثال، تابع داخلی range() به آرگومانهای جداگانه شروع و پایان نیاز دارد. اگر این آرگومانها به طور جداگانه در دسترس نباشند، میتوانید فراخوانی تابع را با استفاده از عملگر *
برای باز کردن آرگومانها از یک لیست یا تاپل بنویسید:
->>> list(range(3, 6)) # normal call with separate arguments
-[3, 4, 5]
->>> args = [3, 6]
->>> list(range(*args)) # call with arguments unpacked from a list
-[3, 4, 5]
-
-به همین ترتیب، دیکشنریها میتوانند آرگومانهای کلیدی را با استفاده از عملگر **
تحویل دهند:
->>> def parrot(voltage, state='a stiff', action='voom'):
-... print("-- This parrot wouldn't", action, end=' ')
-... print("if you put", voltage, "volts through it.", end=' ')
-... print("E's", state, "!")
-...
->>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
->>> parrot(**d)
--- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
-
-4.9.6. عبارات لامبدا
-توابع کوچک ناشناس میتوانند با استفاده از کلمه کلیدی lambda ایجاد شوند. این تابع مجموع دو آرگومان خود را برمیگرداند: lambda a, b: a+b
. توابع لامبدا میتوانند در هر جایی که اشیاء تابع نیاز است استفاده شوند. از نظر نحوی، آنها به یک بیان واحد محدود هستند. از نظر معنایی، آنها فقط قند syntactic برای یک تعریف تابع عادی هستند. مانند تعریف توابع تو در تو، توابع لامبدا میتوانند به متغیرهای موجود در دامنهی حاوی خود اشاره کنند:
->>> def make_incrementor(n):
-... return lambda x: x + n
-...
->>>f = make_incrementor(42)
->>> f(0)
-42
->>> f(1)
-43
-
-مثال بالا از یک عبارت لامبدا برای بازگرداندن یک تابع استفاده میکند. استفاده دیگری این است که یک تابع کوچک را به عنوان آرگومان منتقل کنیم:
->>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
->>> pairs.sort(key=lambda pair: pair[1])
->>> pairs
-[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
-
-4.9.7. رشتههای مستندات
-در اینجا برخی از قراردادها درباره محتوا و فرمت رشتههای مستندات آورده شده است.
-خط اول باید همیشه یک خلاصهی کوتاه و مختصر از هدف شیء باشد. برای اختصار، نباید نام یا نوع شیء را بهطور صریح بیان کند، زیرا این اطلاعات از طرق دیگر در دسترس هستند (مگر اینکه نام بهطور تصادفی یک فعل باشد که عملیات تابع را توصیف کند). این خط باید با حرف بزرگ شروع شده و با نقطه به پایان برسد.
-اگر در رشته مستندات خطوط بیشتری وجود داشته باشد، خط دوم باید خالی باشد تا بهطور بصری خلاصه را از بقیه توضیحات جدا کند. خطوط بعدی باید شامل یک یا چند پاراگراف باشند که کنوانسیونهای فراخوانی شیء، اثرات جانبی آن و غیره را توصیف کنند.
-تحلیلگر پایتون از تورفتگی در ادبیات رشتههای چند خطی در پایتون حذف نمیکند، بنابراین ابزارهایی که مستندات را پردازش میکنند باید در صورت نیاز تورفتگی را حذف کنند. این کار با استفاده از قرارداد زیر انجام میشود. اولین خط غیرخالی پس از اولین خط رشته، مقدار تورفتگی را برای کل رشته مستندات تعیین میکند. (نمیتوانیم از خط اول استفاده کنیم زیرا معمولاً در مجاورت علائم نقل قول آغازین رشته قرار دارد و بنابراین تورفتگی آن در ادبیات رشته مشخص نیست.) سپس فضای خالی «معادل» با این تورفتگی از ابتدای تمام خطوط رشته حذف میشود. خطوطی که کمتر از این مقدار تورفتگی دارند نباید وجود داشته باشند، اما اگر وجود داشتند، تمام فضای خالی اولیه آنها باید حذف شود. معادل بودن فضای خالی باید پس از گسترش تبها (به ۸ فضای خالی، معمولاً) آزمایش شود.
-در اینجا یک مثال از یک docstring چندخطی آورده شده است:
->>> def my_function():
-... """Do nothing, but document it.
-...
-... No, really, it doesn't do anything.
-... """
-... pass
-...
->>> print(my_function.__doc__)
-Do nothing, but document it.
-
- No, really, it doesn't do anything.
-
-4.9.8. حاشیهنویسی توابع
-حاشیهنویسی توابع کاملاً اطلاعات متادیتا اختیاری درباره نوعهای استفاده شده در توابع تعریفشده توسط کاربر است (برای اطلاعات بیشتر به PEP 3107 و PEP 484 مراجعه کنید).
-حاشیهنویسیها بهصورت یک دیکشنری در ویژگی __annotations__
تابع ذخیره میشوند و تأثیری بر هیچ قسمت دیگری از تابع ندارند. حاشیهنویسیهای پارامترها با یک دو نقطه بعد از نام پارامتر تعریف میشوند و سپس یک عبارت که به مقدار حاشیهنویسی ارزیابی میشود، قرار میگیرد. حاشیهنویسیهای بازگشتی با یک نماد ->
تعریف میشوند و پس از آن یک عبارت قرار میگیرد که بین لیست پارامترها و دو نقطهای است که پایان دستور def را مشخص میکند. مثال زیر شامل یک آرگومان الزامی، یک آرگومان اختیاری و یک مقدار بازگشتی حاشیهنویسی شده است:
->>> def f(ham: str, eggs: str = 'eggs') -> str:
-... print("Annotations:", f.__annotations__)
-... print("Arguments:", ham, eggs)
-... return ham + ' and ' + eggs
-...
->>> f('spam')
-Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
-Arguments: spam eggs
-'spam and eggs'
-
-4.10. بینابین (Intermezzo): سبک کدنویسی
-اکنون که شما به نوشتن قطعات طولانیتر و پیچیدهتر پایتون نزدیک میشوید، زمان مناسبی است تا درباره سبک کدنویسی صحبت کنیم. بیشتر زبانها میتوانند به سبکهای مختلف نوشته شوند (یا به عبارت دیگر، فرمت شوند) و برخی از آنها خواناتر از بقیه هستند. آسانتر کردن خواندن کد شما برای دیگران همیشه ایده خوبی است و اتخاذ یک سبک کدنویسی خوب به طرز چشمگیری در این زمینه کمک میکند.
-برای پایتون، PEP 8 به عنوان راهنمای سبک که اکثر پروژهها به آن پایبند هستند، شناخته شده است؛ این راهنما یک سبک کدنویسی بسیار خوانا و دلپذیر را ترویج میکند. هر توسعهدهنده پایتون باید در یک نقطه آن را بخواند؛ در اینجا مهمترین نکات آن برای شما استخراج شده است:
-
-- از تورفتگی ۴ فضایی استفاده کنید و از تبها خودداری کنید.
-
-۴ فضای خالی یک تعادل خوب بین تورفتگی کوچک (که عمق تو رفتگی بیشتری را مجاز میسازد) و تورفتگی بزرگ (که خواندن را آسانتر میکند) است. تبها باعث ایجاد سردرگمی میشوند و بهتر است از آنها صرفنظر شود.
-
-- خطوط را طوری پیچیده کنید که بیشتر از ۷۹ کاراکتر نشوند.
-
-این کار به کاربران با نمایشگرهای کوچک کمک میکند و امکان قرار دادن چندین فایل کد در کنار یکدیگر در نمایشگرهای بزرگتر را فراهم میکند.
-
--
-
از خطوط خالی برای جداسازی توابع و کلاسها و همچنین بلوکهای بزرگتر کد درون توابع استفاده کنید.
-
--
-
در صورت امکان، نظرات را در یک خط جداگانه قرار دهید.
-
--
-
از docstringها استفاده کنید.
-
--
-
از فضاها در اطراف عملگرها و بعد از ویرگولها استفاده کنید، اما نه بهطور مستقیم در داخل ساختارهای پرانتزی: a = f(1, 2) + g(3, 4)
.
-
--
-
نام کلاسها و توابع خود را بهطور منسجم انتخاب کنید؛ قاعده این است که از UpperCamelCase برای کلاسها و lowercase_with_underscores برای توابع و متدها استفاده کنید. همیشه از self
به عنوان نام اولین آرگومان متد استفاده کنید (برای اطلاعات بیشتر به «نگاهی اولیه به کلاسها» مراجعه کنید).
-
--
-
اگر کد شما قرار است در محیطهای بینالمللی استفاده شود، از رمزگذاریهای پیچیده استفاده نکنید. پیشفرض پایتون، UTF-8، یا حتی ASCII معمولی در هر حال بهترین عملکرد را دارد.
-
--
-
به همین ترتیب، اگر تنها کمترین احتمال وجود دارد که افرادی که به زبان دیگری صحبت میکنند کد را بخوانند یا آن را نگهداری کنند، از کاراکترهای غیر ASCII در شناسهها استفاده نکنید.
-
-
-پانویس
-[^1]: در واقع، توصیف "فراخوانی بر اساس مرجع شیء" توصیف بهتری خواهد بود، زیرا اگر یک شیء قابل تغییر (mutable) منتقل شود، فراخوانیکننده هر گونه تغییری که کالید (callee) بر روی آن اعمال کند (مانند اقلامی که به یک لیست اضافه میشود) را مشاهده خواهد کرد.
-
-
-
-
-
-
-
-
-
-
-
-
-
-