انتقال لیست

انتقال لیست‌ها به یک تابع غالباً کاری سودمند است، چه لیستی از نام‌ها باشد، چه اعداد یا چیزهای پیچیده‌تر مثل دیکشنری‌ها. زمانی که لیستی را به تابعی می‌دهید، تابع به محتویّات آن دسترسی مستقیم دارد. اکنون از تابع‌ها برای کار کردن کارآمدتر با لیست‌ها استفاده می‌کنیم.

فرض کنید که لیستی از کاربران داریم و می‌خواهیم به هر کدامشان سلام کنیم. مثال زیر لیستی از نام‌ها را به تابعی به نام greet_users() می‌فرستد که به هر کدام از افراد داخل لیست به طور جداگانه سلام می‌کند.

1
2
3
4
5
6
7
8
def greet_users(names):
    """Print a simple greeting to each user in the list."""
    for name in names:
        msg = f"Hello, {name.title()}!"
        print(msg)

usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)

تابع greet_users() را طوری تعریف کرده‌ایم که لیستی از نام‌ها را بپذیرد و آن را به پارامتر names نسبت دهد. تابع داخل لیست دریافتی حلقه می‌زند و به هر کدام از کاربرها سلام می‌کند. در ۱ لیستی از کاربران تعریف کرده‌ایم و آن را (usernames) هنگام فراخوانی به تابع greet_users() داده‌ایم:

1
2
3
4
5
Hello, Hannah!

Hello, Ty!

Hello, Margot!

این همان خروجی‌ای است که می‌خواستیم. هر کاربر پیامی شخصی دریافت می‌کند و شما می‌توانید تابع را هر زمان برای سلام کردن به گروه مشخّصی از کاربران به کار بگیرید.

تغییر دادن یک لیست در داخل تابع

زمانی که لیستی را به یک تابع ارسال می‌کنید، آن تابع می‌تواند آن را تغییر دهد. تغییراتی که در داخل بدنه‌ی تابع روی لیست اعمال می‌کنید دائمی‌اند که این باعث می‌شود بتوانید با تعداد بسیار زیاد داده هم به خوبی کار کنید.

شرکتی را در نظر بگیرید که بر اساس طرّاحی‌هایی که کاربران ثبت می‌کنند، مدل‌های چاپ سه‌بعدی می‌سازد. طرح‌هایی که باید چاپ شوند در یک لیست ذخیره شده و پس از چاپ شدن به لیستی جداگانه منتقل می‌شوند. کد زیر این کار را بدون به‌کارگیری تابع‌ها انجام می‌دهد:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Start with some designs that need to be printed.
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []

# Simulate printing each design, until none are left.
# Move each design to completed_models after printing.
while unprinted_designs:
    current_design = unprinted_designs.pop()
    print(f"Printing model: {current_design}")
    completed_models.append(current_design)

# Display all completed models.
print("\nThe following models have been printed:")
for completed_model in completed_models:
    print(completed_model)

این برنامه با دو لیست شروع می‌شود: یکی لیستِ طرح‌هایی که باید چاپ شوند و دیگری لیستی خالی که طرح‌ها پس از چاپ شدن به آن منتقل می‌شوند. تا زمانی که طرحی در unprinted_designs باقی مانده باشد، حلقه‌ی while با خارج کردن طرح از انتهای لیست، ذخیره کردن آن در current_design و چاپ کردن پیامی مبنی بر در حالِ چاپ بودنِ طرح فعلی، عملیّات چاپ را شبیه‌سازی می‌کند. حلقه پس از آن طرح را به لیستِ مدل‌های کامل‌شده اضافه می‌کند. زمانی که حلقه به پایان رسید، فهرستی از طرح‌هایی که چاپ شده‌اند، نمایش داده می‌شود:

1
2
3
4
5
6
7
8
Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case

The following models have been printed:
dodecahedron
robot pendant
phone case

می‌توانیم این کد را با نوشتن دو تابع که هر یک کار خاصّی انجام می‌دهند، بازآرایی کنیم. بیشترِ کد تغییر نخواهد کرد؛ تنها آن را با دقّت بیشتری ساختاردهی می‌کنیم. تابع اوّل چاپ کردن طرح‌ها را مدیریّت می‌کند و دومی کارهای چاپی را که انجام شده خلاصه می‌کند:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def print_models(unprinted_designs, completed_models):
    """Simulate printing each design, until none are left.
    Move each design to completed_models after printing."""
    while unprinted_designs:
        current_design = unprinted_designs.pop()
        print(f"Printing model: {current_design}")
        completed_models.append(current_design)


def show_completed_models(completed_models):
    """Show all the models that were printed."""
    print("\nThe following models have been printed:")
    for completed_model in completed_models:
        print(completed_model)


unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []

print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

در ۱ تابع print_models() را با دو پارامتر تعریف می‌کنیم: لیستی از طرح‌هایی که باید چاپ شوند و لیستی از مدل‌های تکمیل‌شده. تابع با گرفتنِ این دو لیست، فرآیند چاپ را با خالی کردنِ لیست طرح‌های چاپ‌نشده و پر کردنِ لیستِ مدل‌های تکمیل‌شده شبیه‌سازی می‌کند. در ۲ تابع show_completed_models() را با یک پارامتر تعریف می‌کنیم: لیستِ مدل‌های تکمیل‌شده. تابع show_completed_models() با داشتنِ این لیست، نام هر کدام از مدل‌های چاپ‌شده را نمایش می‌دهد.

خروجیِ این برنامه مشابه نسخه‌ی بدون تابعِ آن است امّا کد آن بسیار سازمان‌یافته‌تر است. کدی که بیشترِ کارها را انجام می‌دهد به دو تابع مجزّا منتقل شده است که باعث می‌شود درک بخش اصلی برنامه ساده‌تر باشد. به بدنه‌ی برنامه نگاه کنید تا ببینید که درک آن‌چه که انجام می‌دهد چه‌قدر ساده‌تر است:

1
2
3
4
5
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []

print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

لیستی از طرح‌های چاپ‌نشده و یک لیست خالی که مدل‌های تکمیل‌شده را نگه می‌دارد تعریف می‌کنیم. پس از آن، چون قبلاً دو تابع خود را تعریف کرده‌ایم، تنها آن‌ها را با پارامترهای درست فراخوانی کنیم. تابع print_models() را صدا کرده و دو لیست مورد نیاز را به آن می‌دهیم؛ چنان‌که انتظار داشتیم این تابع چاپ کردن طرح‌ها را شبیه‌سازی می‌کند. سپس show_completed_models() را فراخوانی کرده و لیست مدل‌های تکمیل‌شده را به آن می‌دهیم تا بتواند چاپ شدنِ آن‌ها را گزارش کند. این نام‌های تابع گویا به دیگران این امکان را می‌دهد که کد را حتّی بدون توضیحات، خوانده و درک کنند.

نگه‌داری و توسعه‌ی این برنامه از نسخه‌ی بدونِ تابعِ آن ساده‌تر است. اگر بخواهیم بعداً طرح‌های دیگری را چاپ کنیم به سادگی تابع print_models() را دوباره فرامی‌خوانیم. اگر متوجّه شویم که کد مربوط به چاپ کردن باید تغییر کند، کد را یک بار تغییر می‌دهیم و هر جا که تابع استفاده شده باشد، تمام تغییراتِ اعمالی اعمال می‌شوند. این روش از تغییر دادن کد در جای جایِ برنامه کارآمدتر است.

این مثال همچنین این ایده را نشان می‌دهد که هر تابع بهتر است تنها یک وظیفه‌ی مشخّص داشته باشد. تابع اوّل طرح‌ها را چاپ می‌کند و دومی مدل‌های تکمیل‌شده را نشان می‌دهد. این کار از این که از یک تابع برای انجام هر دو کار استفاده کنیم، مفیدتر است. اگر در حال نوشتن تابعی هستید و متوجّه می‌شوید که تابع کارهای بسیار متفاوتی را انجام می‌دهد، سعی کنید آن را به دو تابع تقسیم کنید. فراموش نکنید که همواره می‌توانید تابعی را در تابع دیگری فراخوانی کنید که این کار هنگام تقسیم یک کار پیچیده به دنباله‌ای از مراحل مفید است.

منعِ یک تابع از تغییر دادن یک لیست

گاهی شما می‌خواهید مانع تغییر یافتنِ یک لیست در تابع شوید. برای نمونه فرض کنید می‌خواهید از لیست طرح‌های چاپ‌نشده شروع کنید و تابعی بنویسید که مانند مثال قبل آن‌ها را به لیست مدل‌های تکمیل‌شده منتقل کند. شاید تصمیم بگیرید علاوه بر چاپ کردنِ تمام طرح‌ها، لیست اصلی طرح‌های چاپ‌نشده را برای بایگانی نگه دارید. امّا از آن‌جایی که نام تمامِ طرح‌ها را از unprinted_designs خارج کرده‌اید، این لیست اکنون خالی است و لیستِ خالی تنها نسخه‌ای است که در اختیار دارید؛ لیست اصلی از بین رفته است. در چنین حالتی می‌توانید مشکل را با ارسال یک کپی از لیست (نه اصلِ آن) به تابع حل کنید. هر تغییری که تابع در لیست ایجاد کند تنها کپی آن را تحت تاثیر قرار می‌دهد و لیست اصلی دست‌نخورده باقی می‌ماند.

می‌توانید کپی‌ای از لیست را به این صورت به تابع ارسال کنید:

1
function_name(list_name[:])

نماد برش [:] یک کپی از لیست را به تابع می‌فرستد. اگر قصد نداشتیم که لیست طرح‌های چاپ‌نشده را در printing_models.py دست بزنیم، باید print_models() را مانند زیر فراخوانی می‌کردیم:

1
print_models(unprinted_designs[:], completed_models)

تابع print_models() می‌تواند به کار خود برسد زیرا همچنان نام تمام طرح‌های چاپ‌نشده را دریافت می‌کند. امّا این بار این از کپیِ لیست اصلی طرح‌های چاپ‌نشده استفاده می‌کند و نه لیستِ unprinted_designs واقعی. لیست completed_models مثل قبل با نام مدل‌های چاپ‌شده پر می‌شود امّا لیستِ اصلیِ طرح‌هایِ چاپ‌نشده در این تابع تغییری نمی‌کند.

اگرچه با ارسال یک کپی از لیست به تابع می‌توانید محتویّات آن را حفظ کنید، امّا تا زمانی که دلیل مشخّصی برای ارسال کپی ندارید، باید اصلِ لیست را بفرستید. کار کردن با لیستی که قبلاً ایجاد شده برای یک تابع کارآمدتر است زیرا از صرف زمان و حافظه برای ساختن یک لیست جداگانه پرهیز می‌کند، به ویژه هنگامی که با لیست‌های بزرگ سروکار دارید.

خودآزمایی

۸-۹. پیامک‌ها: لیستی شامل تعدادی پیامک بسازید. لیست را به تابع show_message() بفرستید تا هر پیامک را چاپ کند.

۸-۱۰. ارسال پیامک‌ها: از یک کپی از برنامه‌ی تمرین ۸-۹ شروع کنید. تابعی به نام send_messages() بنویسید که هر پیامک را چاپ کرده و آن را پس از چاپ به لیستی جدید به نام sent_messages منتقل می‌کند. پس از فراخوانی تابع هر دو لیست را چاپ کنید تا مطمئن شوید که پیامک‌ها به درستی منتقل شده‌اند.

۸-۱۱. پیامک‌های بایگانی‌شده: کار خود را از تمرین ۸-۱۰ شروع کنید. تابع send_messages() را با یک کپی از لیست پیامک‌ها فراخوانی کنید. بعد از صدا کردنِ تابع هر دو لیست را چاپ کنید و نشان دهید که لیست اصلی پیامک‌های خود را حفظ کرده است.