لیست کامپرهنشن در پایتون: از حلقه تا یکخطیهای زیبا
یاد بگیرید لیست کامپرهنشن چطور کار میکند، کِی کدتان را خواناتر میکند و کِی همان حلقهٔ for ساده انتخاب بهتری است.
در این صفحه
اگر کد پایتونِ دیگران را خوانده باشید، تقریباً حتماً چیزی شبیه این دیدهاید:
squares = [n * n for n in range(10)]این یک لیست کامپرهنشن (List Comprehension) است: روشی فشرده برای ساختن یک لیست جدید از روی یک مجموعهٔ قابل پیمایش. وقتی مفهومش جا بیفتد، مدام از آن استفاده خواهید کرد. در این راهنما از همان حلقهٔ for که میشناسید شروع میکنیم، فیلتر کردن و تبدیل داده را میبینیم، سراغ کامپرهنشنِ دیکشنری و مجموعه میرویم و در پایان میگوییم کِی نباید از آن استفاده کنید.
از حلقهای که میشناسید شروع کنید#
روش کلاسیک ساختن لیستی از مربعها این است:
squares = []
for n in range(10):
squares.append(n * n)
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]سه خط و سه کار: ساختن یک لیست خالی، پیمایش و افزودن. کامپرهنشن همین ایده را در یک خط بیان میکند:
squares = [n * n for n in range(10)]آن را اینطور بخوانید: «به ازای هر n در range(10)، مقدار n * n را به من بده.» عبارتی که میخواهید جمع شود اول میآید و بعد حلقهای که مقادیر را تولید میکند.
اجزای یک کامپرهنشن#
هر لیست کامپرهنشن از این الگو پیروی میکند:
[expression for item in iterable if condition]| بخش | معنی | الزامی؟ |
|---|---|---|
expression |
چیزی که در لیست جدید قرار میگیرد | بله |
for item in iterable |
مقادیر از کجا میآیند | بله |
if condition |
فقط آیتمهایی که شرط را دارند نگه داشته میشوند | خیر |
فیلتر کردن با if#
برای نگه داشتن فقط بعضی آیتمها، یک شرط به انتها اضافه کنید. اینجا اعداد زوج را نگه میداریم:
evens = [n for n in range(20) if n % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]میتوانید همزمان تبدیل و فیلتر کنید. این کد طول کلمههایی را که بیش از سه حرف دارند جمع میکند:
words = ["git", "python", "sql", "linux", "css", "docker"]
long_word_lengths = [len(w) for w in words if len(w) > 3]
# [6, 5, 6]انتخاب مقدار با if ... else#
اگر بخواهید همهٔ آیتمها را نگه دارید اما بعضیشان را تغییر دهید چه؟ یک if ... else را قبل از for بگذارید. این عبارت شرطیِ پایتون است، نه فیلتر:
scores = [92, 47, 78, 30, 85]
results = ["pass" if s >= 50 else "fail" for s in scores]
# ['pass', 'fail', 'pass', 'fail', 'pass']قانون جایگاه را به خاطر بسپارید
if بعد از for آیتمها را حذف میکند. if ... else قبل از for تعیین میکند هر آیتم به چه چیزی تبدیل شود. اولی میتواند لیست را کوتاهتر کند؛ دومی هرگز.
مثالهای واقعی#
کامپرهنشنها وقتی داده را تمیز یا بازآرایی میکنید میدرخشند.
یکدست کردن ورودی کاربر:
raw_emails = [" Ada@Example.com", "grace@example.com ", "", "LINUS@example.com"]
emails = [e.strip().lower() for e in raw_emails if e.strip()]
# ['ada@example.com', 'grace@example.com', 'linus@example.com']بیرون کشیدن یک فیلد از لیستی از دیکشنریها:
users = [
{"name": "Ada", "active": True},
{"name": "Grace", "active": False},
{"name": "Linus", "active": True},
]
active_names = [u["name"] for u in users if u["active"]]
# ['Ada', 'Linus']تخت کردن یک لیست تودرتو. حلقهها به همان ترتیبی میآیند که در دستورهای for تودرتو میآمدند:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [value for row in matrix for value in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]کامپرهنشنِ دیکشنری و مجموعه#
همین سینتکس با آکولاد هم کار میکند. برای ساختن دیکشنری از key: value استفاده کنید:
words = ["git", "python", "sql"]
lengths = {w: len(w) for w in words}
# {'git': 3, 'python': 6, 'sql': 3}دونقطه را حذف کنید تا یک مجموعه (set) بگیرید که تکراریها را خودکار حذف میکند:
unique_lengths = {len(w) for w in words}
# {3, 6}Generator Expression: کامپرهنشن بدون لیست#
اگر کروشهها را با پرانتز عوض کنید، یک generator expression دارید. این عبارت بهجای ساختن کل لیست در حافظه، مقادیر را یکییکی تولید میکند؛ مناسب وقتی که فقط یک بار به مقادیر نیاز دارید:
total = sum(n * n for n in range(1_000_000))وقتی generator expression تنها آرگومانِ یک تابع باشد، مثل بالا میتوانید پرانتزهای اضافه را حذف کنید. توابعی مثل sum()، any()، all()، max() و min() با آنها عالی کار میکنند:
has_admin = any(u.get("role") == "admin" for u in users)کِی کامپرهنشن ننویسیم#
کامپرهنشنها برای خواناتر کردن کد هستند. اگر کدی خواناییاش را از دست داد، از حلقه استفاده کنید. مراقب این نشانهها باشید:
- در یکی دو خط بهراحتی جا نمیشود. کامپرهنشنهای تودرتو با چند شرط بهسختی خوانده میشوند.
- به اثر جانبی نیاز دارید. ننویسید
[print(x) for x in items]. این کار لیستی دورریختنی ازNoneمیسازد. از حلقهٔforاستفاده کنید. - برای هر آیتم به
try/exceptیا چند دستور نیاز دارید. کامپرهنشن فقط یک عبارت را در خود جا میدهد؛ یک حلقه یا یک تابع کمکی واضحتر است.
# Hard to read: avoid
result = [x * y for x in range(10) if x % 2 for y in range(x) if y > 2 and x + y < 12]
# Clearer: give the logic a name
def odd_pair_products(limit):
for x in range(1, limit, 2):
for y in range(3, x):
if x + y < 12:
yield x * y
result = list(odd_pair_products(10))جمعبندی#
[expr for x in items]همهٔ آیتمها را تبدیل میکند.[expr for x in items if cond]فیلتر هم میکند.[a if cond else b for x in items]برای هر آیتم یک مقدار انتخاب میکند.- برای دیکشنری از
{k: v ...}، برای مجموعه از{x ...}و برای generatorهای تنبل از(x ...)استفاده کنید. - اگر کد دارد بیش از حد زیرکانه میشود، حلقه معمولاً با خوانندهٔ بعدی مهربانتر است؛ خوانندهای که شاید خودتان باشید.
تمرین: یکی از اسکریپتهایی را که اخیراً نوشتهاید باز کنید و دنبال الگوی «لیست خالی، حلقه، append» بگردید. آن را به کامپرهنشن تبدیل کنید و صادقانه تصمیم بگیرید کدام نسخه خواناتر است.