مقدمه

در فصل سوم دیدیم که چطور می‌توانیم به عناصر یک لیست به صورت مجزا از هم دسترسی داشته باشیم و در این فصل هم تا اینجا روی کار روی کل لیست متمرکز بوده‌ایم. اما این امکان هم وجود دارد که روی یک بخش از یک لیست، که در پایتون Slice نامیده می‌شود، کار کنیم. در این درس به این موضوع می‌پردازیم.

ایجاد یک Slice از لیست

در پایتون، slice به بخشی از یک دنباله (مانند لیست، رشته و تاپل) گفته می‌شود که با استفاده از عملگر دونقطه ( : ) استخراج شده است. با استفاده از این عملگر می‌توانیم بخش مورد نظرمان از یک دنباله را بدون اینکه به نوشتن کد پیچیده‌ای نیاز باشد، استخراج کنیم. ساختار کلی یک slice به صورت زیر است.

sequence[start:stop:step]

در اینجا sequence دنباله یا کالکشنی است که قصد داریم بخشی از آن را استخراج کنیم، start و stop به‌ترتیب، اندیس شروع و پایان هستند و step هم سایز گام را مشخص می‌کند. البته توجه داشته باشید که در اینجا هم مثل آنچه در مورد تابع range() دیدیم، مقدار stop در نتیجه‌ی نهایی لحاظ نمی‌شود؛ یعنی اندیس آخرین آیتم slice برابر با stop-1 خواهد بود. در ضمن، step دارای مقدار پیش‌فرض 1 است و بنابراین، اگر آن را تعیین نکنیم، برابر با 1 در نظر گرفته می‌شود. به مثال ساده‌ی زیر نگاه کنید.

Copy Icon slices.py
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sub_list = numbers[2:6]
print(sub_list)

در اینجا آیتم‌های دارای اندیس 2 تا 5 از لیست استخراج می شوند و بنابراین، نتیجه‌ی زیر را خواهیم داشت.

[2, 3, 4, 5]

حالا فرض کنید بخواهیم از لیست اعداد صفر تا 9 اعداد بخشپذیر بر 3 را استخراج کنیم.

Copy Icon slices.py
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sub_list = numbers[3:10:3]
print(sub_list)

حذف پارامترهای start و stop

اگر مقداری برای پارامتر start در نظر نگیریم، برابر با صفر در نظر گرفته می‌شود و بنابراین، اسلایس مورد نظر با شروع از اولین آیتم ساخته می‌شود. به همین ترتیب، اگر مقداری را برای پارامتر stop در نظر نگیریم، پایتون اندیس آخرین عنصر را برای آن در نظر می‌گیرد. مثال زیر این موضوع را نشان می‌دهد.

Copy Icon slices.py
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
left_sub_list = numbers[:5]  
right_sub_list = numbers[5:]  
print(left_sub_list)
print(right_sub_list)

نتیجه‌ی اجرای این کد به صورت زیر خواهد بود.

[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
          

مقادیر منفی برای پارامترهای Slice

هر سه پارامتر start، stop و step می‌توانند مقادیر منفی هم دریافت کنند. اجازه دهید ببینیم مقدار منفی برای هر یک از این پارامترها چه نتیجه‌ای دارد.

یادآوری می‌کنم که اندیس منفی در لیست‌ها باعث انجام شمارش از آخر به اول می‌شود؛ یعنی اندیس -n به n-امین عنصر از آخر اختصاص دارد. با در نظر گرفتن این موضوع، نتیجه‌ی استفاده از مقادیر منفی برای پارامترهای start و stop واضح است.

Copy Icon slices.py
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sub_list = numbers[-6:-3]
print(sub_list)
            
sub_list = numbers[:-3]
print(sub_list)
            
sub_list = numbers[-6:]
print(sub_list)

نتیجه‌ی اجرای کد بالا به صورت زیر خواهد بود.

[4, 5, 6]
[0, 1, 2, 3, 4, 5, 6]
[4, 5, 6, 7, 8, 9]

          

مقدار منفی برای پارامتر step هم همانطور که انتظار می‌رود، جهت پیمایش را عوض می‌کند.

Copy Icon slices.py
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sub_list = numbers[6:2:-1]
print(sub_list)

اجرای این کد باعث چاپ نتیجه‌ی زیر می‌شود.

[6, 5, 4, 3]

ایجاد یک Slice از رشته‌

مطاب انتظار، امکان ساخت slice از رشته‌ها هم وجود دارد. یادآوری می‌کنم که یک رشته دنباله‌ای است از کاراکترها. در مثال زیر بخشی از یک رشته را با استفاده از عملگر slice استخراج کرده‌ایم.

Copy Icon slices.py
text = "Hello, World!"
substring = text[7:12]
print(substring)

اگر این مثال را اجرا کنید، نتیجه‌ی زیر را مشاهده خواهید کرد.

World

همه‌ی مطالبی که در مورد ایجاد slice از لیست‌ها بیان کردیم (مانند حذف پارامترها و مقادیر منفی برای آنها) در مورد رشته‌ها هم صدق می‌کند و از بیان مجدد آنها خودداری می‌کنیم.

حلقه زدن روی یک Slice

با توجه به اینکه یک slice هم یک کالکشن یا دنباله محسوب می‌شود، می‌توانیم با استفاده از ساختار for عناصر یا آیتم‌های آن را پیمایش کنیم.

Copy Icon slices.py
cities = ['shiraz', 'tehran', 'isfahan', 'tabriz', 'ahwaz'] 
for city in cities[:3]
  print(city.title())

در اینجا یک حلقه‌ی for ایجاد شده که یک slice متشکل از سه عنصر ابتدایی یک لیست را پیمایش کرده و آنها را چاپ می‌کند. نتیجه به صورت زیر خواهد بود.

['Shiraz', 'Tehran', 'Isfahan']

کپی کردن لیست‌ها

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

Copy Icon slices.py
x = 5
y = x
print(f"x = {x}")
print(f"y = {y}")

آنچه در اینجا رخ داده یک کپی کامل یا اصطلاحاً یک Deep Copy است. مقدار متغیر x یعنی 5 به متغیر y اختصاص داده شده و این دو متغیر دارای مقدار یکسانی هستند اما در عین حال، از هم مستقل‌اند. به عبارت دیگر، ما دو متغیر داریم که مقدار یکسانی دارند اما به مکان‌های متفاوتی از حافظه اشاره می‌کنند. بنابراین، اگر مقدار یکی را تغییر دهیم، تأثیری روی مقدار دیگر نخواهد داشت. در پایتون، کپی رشته‌ها هم به همین صورت کار می‌کند و یک کپی کامل یا deep copy است.

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

Copy Icon slices.py
my_sports = ['football', 'chess', 'basketball', 'volleyball'] 
friend_sports = my_sports 
print(my_sports)
print(friend_sports)
            
friend_sports.append('tennis')
print(my_sports)

در اینجا ابتدا لیستی از ورزش‌های مورد علاقه‌ی من ایجاد شده و در متغیری به نام my_sports ذخیره شده است. در ادامه، لیستی هم برای ورزش‌های علاقه‌ی دوستم ایجاد کرده‌ام اما چون همه‌ی ورزش‌های لیست من مورد علاقه‌ی دوستم هم هست، به‌جای اینکه یک لیست جدید تعریف کنم، لیست ورزش‌های خودم را به یک متغیر با نام friend_sports تخصیص داده‌ام تا با این لیست شروع کند و احیاناً ورزش‌های دیگری را هم به آن اضافه کند. تا اینجا همه‌چیز خوب به نظر می‌رسد و دو گزاره‌ی print() بعدی نشان می‌دهند که این دو متغیر الان مقدار یکسانی دارند.

اما همانطور که گفته شد، این روش منجر به انجام یک کپی ضعیف می‌شود. بنابراین، وقتی یک آیتم جدید به لیست friend_sports اضافه شده، این تغییر در لیست my_sports هم منعکس شده و بنابراین، خرروجی زیر را خواهیم داشت.

['football', 'chess', 'basketball', 'volleyball']
['football', 'chess', 'basketball', 'volleyball']
['football', 'chess', 'basketball', 'volleyball', 'tennis']
          

در اغلب موارد، این چیزی نیست که ما می‌خواهیم و در عوض به دنبال انجام یک کپی کامل هستیم. و اینجاست که اسلایس‌ها به ما کمک می‌کنند تابه خواسته‌ی خود برسیم. می‌دانیم که اگر اندیسی برای ابتدا و انتهای اسلایس تعیین نکنیم، کل آیتم‌های لیست استخراج می‌شوند و ما به یک کپی از لیست می‌رسیم. اما نکته‌ی مهم اینجاست که کپیِ انجام‌شده به این روش یک کپی کامل است. برای روشن شدن موضوع، مثال زیر را بررسی و اجرا کنید.

Copy Icon slices.py
my_sports = ['football', 'chess', 'basketball', 'volleyball'] 
friend_sports = my_sports[:]
print(my_sports)
print(friend_sports)
            
friend_sports.append('tennis')
print(my_sports)

اگر این کد را اجرا کنید، خواهید دید که این بار تغییر یک متغیر تأثیری روی دیگری ندارد و نتیجه کاملاً مطابق انتظار است.

['football', 'chess', 'basketball', 'volleyball']
['football', 'chess', 'basketball', 'volleyball']
['football', 'chess', 'basketball', 'volleyball']