📸 میریم سراغ جواب این سوال
📝 احتمالا با list comprehension آشنا هستید:
[expression for variable in iterable if condition]
داخل این، روی iterable حلقه زده میشه و مقداری به variable اختصاص داده میشه، بعد اگه شرط condition (که گذاشتنش اختیاریه) برقرار بود، وارد بخش expression میشه و مقدار اون بخش رو تولید میکنه و میذاره توی لیست. مثلا:
>>> [x for x in range(5)]
[0, 1, 2, 3, 4]
>>> [x**2 for x in range(5)]
[0, 1, 4, 9, 16]
>>> [x**2 for x in range(5) if x%2==0]
[0, 4, 16]
رفع ابهام: میتونیم از if-else توی بخش expression استفاده کنیم؛ اما دقت کنید که این، با بخش condition فرق داره. در واقع، بخش condition اگه برقرار نبود، اصلا وارد بخش expression نمیشه که مقداری (توی اون دور از حلقه) اختصاص داده بشه؛ اما وقتی condition برقرار بود و [در نتیجه] وارد بخش expression شد، قطعا باید مقداری توی اون دور از حلقه قرار داده بده (برای همین، اگه قراره داخل expression، با شرط مقدار بذاریم، باید با if-else نوشته بشه که بهش میگیم conditional expression):
>>> [(x if x!=5 else "Hop!") for x in range(1, 6) if x%2==1]
[1, 3, 'Hop!']

➕ البته میتونید بصورت تو در تو هم استفاده کنید که بهش Nested Comprehensions میگن. مثلا:
>>> [
... [x * y for y in range(5)]
... for x in range(5)
... ]
[
[0, 0, 0, 0, 0],
[0, 1, 2, 3, 4],
[0, 2, 4, 6, 8],
[0, 3, 6, 9, 12],
[0, 4, 8, 12, 16]
]

🔎 اما آیا فقط list comprehension داریم؟ .... نه! ۳ مورد مشابه دیگه هم داریم:
1. set comprehension (set comp)
2. dictionary comprehension (dict comp)
3. generator expression (genexpr!)
1️⃣ همینجوری که برای لیست نوشتیم و تعریف کردیم، برای مجموعه‌ها هم میتونیم بکار ببریم. منتهی این یکی، مقادیر رو توی یک مجموعه جمع‌آوری میکنه، نه توی لیست! و مثلا طبق خواص مجموعه، اعضای تکراری حذف میشن یا مثلا unhashable typeها رو نمیتونیم داخلش قرار بدیم و ... و به این شکل هست:
{expression for variable in iterable if condition}
2️⃣ به همین ترتیب هم برای دیکشنری؛ منتهی دقت کنید که این سری باید هم key رو مشخص کنیم هم value رو:
{expr1: expr2 for variable in iterable if condition}
3️⃣ میرسیم به genexpr! فرم کلی generator expression هم مشابه موارد قبلی هست، منتهی با پرانتز!
(expression for variable in iterable if condition)
اما دقت کنید که این، تاپل نیست! بلکه یک iterator هست. چرا؟ به نقل از این لینک، فرق اساسی تاپل با چیزایی که گفتیم، توی این هست که شما نمیتونید به یک تاپل، بعد از تشکیلش، مثلا مقداری append کنید یا extend کنید یا ... و در کل immutable هست، پس اگه یک آبجکتی ازش تشکیل میشه (در ابتدای comp)، نمیشه دوباره مقداری بهش اضافه کرد. حتی یک جا به struct توی C تشبیهش کردن! و به قولی:
"Generally, lists are for looping; tuples for structs. Lists are homogeneous; tuples heterogeneous. Lists for variable length."
برگردیم به بحث اصلی! گفتیم که یک iterator هست، پس همه مقادیر رو درجا به ما نمیده! و برای گرفتن مقدار، باید next کنیم (و وقتی هم مقداری باقی نمونده باشه، StopIteration میده). میشه unpack هم کرد، و ... مثلا:
>>> (x**2 for x in range(5))
<generator object <genexpr> at 0x7fa76d750580>
>>> itr = (x**2 for x in range(5))
>>> next(itr)
0
... و بعد از چندبار next کردن ...:
>>> next(itr)
16
>>> next(itr)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

🔬 همونطور که گفتیم، میتونیم اون رو unpack کنیم و در نتیجه میشه توی تاپل (و لیست و مجموعه و ...) هم ذخیره کرد. یا حتی میتونید مستقیما اون رو به tuple یا list یا ... بدید. مثلا:
>>> tuple(x**2 for x in range(5))
(0, 1, 4, 9, 16)
یا که بیاید کمی خفن‌تر بنویسید:) و اول unpack کنید و بعد، با یک ویرگول توی آخرش، بگید که همه مقادیر داخل یک تاپل برن (دقت کنید که اینجا بعد از گرفتن مقادیر، یک آبجکت تاپل تشکیل میشه و مقادیر باهم داخلش قرار میگیرن):
>>> *(x**2 for x in range(5)),
(0, 1, 4, 9, 16)
ازش میشه توی دادن آرگومان به تابع‌ها هم استفاده کرد (مخصوصا توابعی مثل print که چندین آرگومان میگیرن و معمولا با پارامتر *args این‌هارو تشخیص میدیم)؛ مثلا کدی که عدد بگیره و به تعداد اون، جملات ابتدایی دنباله فیبوناچی رو نشون بده (فرض کنید تابع fibonacci برای گرفتن عدد دنباله نوشته شده) :
>>> print(*(
... fibonacci(x)
... for x in range(1, int(input("count: "))+1)
... ), sep = ' -> '
... )
count: 10
1 -> 1 -> 2 -> 3 -> 5 -> 8 -> 13 -> 21 -> 34 -> 55

🆔 @raspberry_python

Comments

Be the first to add a comment