پایتونمبتدی

لیست کامپرهنشن در پایتون: از حلقه تا یک‌خطی‌های زیبا

یاد بگیرید لیست کامپرهنشن چطور کار می‌کند، کِی کدتان را خواناتر می‌کند و کِی همان حلقهٔ for ساده انتخاب بهتری است.

اعداد ۱ تا ۴ از n * n عبور می‌کنند و به لیست ۱، ۴، ۹ و ۱۶ تبدیل می‌شوند
در این صفحه

اگر کد پایتونِ دیگران را خوانده باشید، تقریباً حتماً چیزی شبیه این دیده‌اید:

Python
squares = [n * n for n in range(10)]

این یک لیست کامپرهنشن (List Comprehension) است: روشی فشرده برای ساختن یک لیست جدید از روی یک مجموعهٔ قابل پیمایش. وقتی مفهومش جا بیفتد، مدام از آن استفاده خواهید کرد. در این راهنما از همان حلقهٔ for که می‌شناسید شروع می‌کنیم، فیلتر کردن و تبدیل داده را می‌بینیم، سراغ کامپرهنشنِ دیکشنری و مجموعه می‌رویم و در پایان می‌گوییم کِی نباید از آن استفاده کنید.

از حلقه‌ای که می‌شناسید شروع کنید#

روش کلاسیک ساختن لیستی از مربع‌ها این است:

Python
squares = []
for n in range(10):
    squares.append(n * n)

print(squares)  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

سه خط و سه کار: ساختن یک لیست خالی، پیمایش و افزودن. کامپرهنشن همین ایده را در یک خط بیان می‌کند:

Python
squares = [n * n for n in range(10)]

آن را این‌طور بخوانید: «به ازای هر n در range(10)، مقدار n * n را به من بده.» عبارتی که می‌خواهید جمع شود اول می‌آید و بعد حلقه‌ای که مقادیر را تولید می‌کند.

اجزای یک کامپرهنشن#

هر لیست کامپرهنشن از این الگو پیروی می‌کند:

Text
[expression for item in iterable if condition]
[] n * n⁧عبارت⁩⁧چه چیزی جمع شود⁩ for n in range(10)⁧بخش ⁦for⁩⁩⁧آیتم‌ها از کجا می‌آیند⁩ if n % 2 == 0⁧بخش ⁦if⁩ (اختیاری)⁩⁧کدام آیتم‌ها بمانند⁩ ⁧نتیجه: ⁦[0, 4, 16, 36, 64]⁩⁩
همین سه بخش در یک کامپرهنشن واقعی که اعداد زوجِ کمتر از ۱۰ را به توان دو می‌رساند
بخش معنی الزامی؟
expression چیزی که در لیست جدید قرار می‌گیرد بله
for item in iterable مقادیر از کجا می‌آیند بله
if condition فقط آیتم‌هایی که شرط را دارند نگه داشته می‌شوند خیر

فیلتر کردن با if#

برای نگه داشتن فقط بعضی آیتم‌ها، یک شرط به انتها اضافه کنید. این‌جا اعداد زوج را نگه می‌داریم:

Python
evens = [n for n in range(20) if n % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

می‌توانید هم‌زمان تبدیل و فیلتر کنید. این کد طول کلمه‌هایی را که بیش از سه حرف دارند جمع می‌کند:

Python
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 بگذارید. این عبارت شرطیِ پایتون است، نه فیلتر:

Python
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 تعیین می‌کند هر آیتم به چه چیزی تبدیل شود. اولی می‌تواند لیست را کوتاه‌تر کند؛ دومی هرگز.

مثال‌های واقعی#

کامپرهنشن‌ها وقتی داده را تمیز یا بازآرایی می‌کنید می‌درخشند.

یکدست کردن ورودی کاربر:

Python
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']

بیرون کشیدن یک فیلد از لیستی از دیکشنری‌ها:

Python
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 تودرتو می‌آمدند:

Python
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 استفاده کنید:

Python
words = ["git", "python", "sql"]
lengths = {w: len(w) for w in words}
# {'git': 3, 'python': 6, 'sql': 3}

دونقطه را حذف کنید تا یک مجموعه (set) بگیرید که تکراری‌ها را خودکار حذف می‌کند:

Python
unique_lengths = {len(w) for w in words}
# {3, 6}

Generator Expression: کامپرهنشن بدون لیست#

اگر کروشه‌ها را با پرانتز عوض کنید، یک generator expression دارید. این عبارت به‌جای ساختن کل لیست در حافظه، مقادیر را یکی‌یکی تولید می‌کند؛ مناسب وقتی که فقط یک بار به مقادیر نیاز دارید:

Python
total = sum(n * n for n in range(1_000_000))

وقتی generator expression تنها آرگومانِ یک تابع باشد، مثل بالا می‌توانید پرانتزهای اضافه را حذف کنید. توابعی مثل sum()، any()، all()، max() و min() با آن‌ها عالی کار می‌کنند:

Python
has_admin = any(u.get("role") == "admin" for u in users)

کِی کامپرهنشن ننویسیم#

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

  • در یکی دو خط به‌راحتی جا نمی‌شود. کامپرهنشن‌های تودرتو با چند شرط به‌سختی خوانده می‌شوند.
  • به اثر جانبی نیاز دارید. ننویسید [print(x) for x in items]. این کار لیستی دورریختنی از None می‌سازد. از حلقهٔ for استفاده کنید.
  • برای هر آیتم به try/except یا چند دستور نیاز دارید. کامپرهنشن فقط یک عبارت را در خود جا می‌دهد؛ یک حلقه یا یک تابع کمکی واضح‌تر است.
Python
# 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» بگردید. آن را به کامپرهنشن تبدیل کنید و صادقانه تصمیم بگیرید کدام نسخه خواناتر است.