مبانی اولیه‌ی پایتون - tuple و set

Professional Python: tuple and set

10 اسفند 1399
درسنامه درس 7 از سری پایتون حرفه‌ای
Python حرفه ای: مبانی اولیه - tuple و set (قسمت 07)

آشنایی با tuple در پایتون

نوع داده بعدی ما Tuple (به هر دو لفظ «تاپِل» و «تیوپِل» تلفظ می شود اما دومی رایج تر است) است که شباهت بسیاری به لیست ها دارند اما برخلاف لیست ها ما اجازه ویرایش آن ها را نداریم، بنابراین می توان گفت که یک Immutable list هستند. برای تعریف یک tuple در پایتون از علامت های پرانتز استفاده می کنیم:

my_tuple = (1,2,3,4,5)

همانطور که می بینید این ساختار کاملا شبیه به ساختار لیست ها است با این تفاوت که برای تعریف لیست از [] و برای تعریف tuple در پایتون از () استفاده می کنیم. اگر یادتان باشد در لیست ها می توانستیم یک ایندکس خاص را گرفته و مقدارش را تغییر بدهیم اما اگر این کار را با tuple ها انجام بدهیم چطور؟

my_tuple = (1,2,3,4,5)

my_tuple[1] = "Amir"

در این کد می خواهیم ایندکس ۱ را گرفته و مقدارش را با رشته Amir جایگزین کنیم. با اجرای این کد خطای زیر را دریافت می کنید:

Traceback (most recent call last):
  File "main.py", line 3, in <module>
    my_tuple[1] = "Amir"
TypeError: 'tuple' object does not support item assignment

یعنی اشیاء tuple در پایتون پس از تعریف شدن قابلیت انتساب ندارند. شاید بپرسید چرا «اشیاء»؟ باید بدانید که در زبان پایتون همه چیز شیء است (رشته ها، اعداد و الی آخر) و در طول دوره به این موضوع عادت خواهید کرد. سوال بعدی اینجاست که آیا ما می توانیم یک ایندکس خاص را بدون ویرایش دریافت کنیم؟

my_tuple = (1,2,3,4,5)

print(my_tuple[0])

با اجرای این کد عدد 1 را دریافت می کنیم بنابراین دریافت یک مقدار خاص مجاز است، حتی استفاده از اپراتور in نیز مجاز است. بنابراین تنها تفاوت اینجاست که نمی توانیم tuple ها را ویرایش کنیم.

استفاده از tuple ها در موارد مختلفی کاربردی است. به طور مثال اگر داده هایی را داشته باشیم و نخواهیم کسی آن ها را تغییر دهد می توانیم از tuple ها استفاده کنیم. اگر در یک تیم کار می کنیم و نیازی به تغییر داده های درون لیست نداریم بهتر است از tuple استفاده کنیم از تا کسی به اشتباه این داده ها را تغییر ندهد. همچنین کد قابل پیش بینی تر شده و احتمال نوشتن کد اشتباه توسط ما به شدت پایین می آید اما در عین حال مسائلی مانند انعطاف پذیری کد را نیز از دست می دهیم. چرا؟ به دلیل اینکه انجام عملیات هایی مانند sort (مرتب کردن) یا reverse (برعکس کردن) در tuple ها غیر ممکن است. البته به دلیل این سخت گیری ها، tuple ها کمی از لیست ها سریع تر هستند بنابراین اگر نیازی به تغییر داده های درون یک لیست ندارید از لیست ها استفاده کنید.

یک مثال ساده از چنین موقعیتی دریافت مکان کاربر است. تصور کنید یک شرکت تاکسی اینترنتی داریم و می خواهیم موقعیت کاربر را در یک لیست یا tuple ذخیره کنیم. از آنجایی که موقعیت کاربر تغییر زیادی نمی کند می توانیم آن را در tuple ها ذخیره کنیم و اگر بعدا تغییر کرد یک tuple جدید می سازیم. از طرف دیگر موقعیت راننده تاکسی نباید از نوع tuple باشد چرا که هر لحظه در حال تغییر است (راننده در حال رانندگی در شهر می باشد).

اگر یادتان باشد در جلسه قبل کدی را برای دریافت آیتم های یک دیکشنری داشتیم:

user = {
  "name" : "Amir",
  "website" : "roxo.ir",
  "occupation" : "Developer"
}

print(user.items())

زمانی که ما این دستور را پرینت را اجرا می کردیم نتیجه زیر را می گرفتیم:

dict_items([('name', 'Amir'), ('website', 'roxo.ir'), ('occupation', 'Develop-er')])

با این حساب متد items هر کدام از آیتم های دیکشنری را به صورت یک tuple برمی گرداند، البته تمام این tuple ها درون یک لیست قرار دارند. در ضمن اگر یادتان باشد در همان جلسه گفتم که کلید های یک دیکشنری در زبان پایتون باید immutable باشند و از آنجایی که tuple ها نیز immutable هستند می توانیم از آن ها به عنوان کلید استفاده کنیم:

user = {
  "name" : "Amir",
  "website" : "roxo.ir",
  (1, 2) : "Developer"
}

print(user[(1,2)])

با اجرای این کد نتیجه Developer را دریافت می کنیم. به همین سادگی می توانیم از یک tuple به عنوان کلید یک دیکشنری استفاده کنیم! حالا از شما سوال دیگری دارم. آیا می توانیم از slicing در tuple ها استفاده کنیم؟

my_tuple = (1,2,3,4,5)

new_tuple = my_tuple[:3]

print(new_tuple)

به نظر شما نتیجه اجرای کد بالا خطا خواهد بود یا یک مقدار واقعی؟ با اجرای کد بالا نتیجه زیر را می گیرید:

(1, 2, 3)

چطور؟ اگر یادتان باشد slicing داده اصلی را ویرایش نمی کرد بلکه یک کپی از آن ایجاد کرده و آن کپی ویرایش شده را به شما برمی گرداند. در ضمن توجه داشته باشید که یک tuple برای ما برگردانده شده است.

مسئله بعدی استخراج داده های یک tuple در متغیرهای مختلف است. دو راه برای انجام این کار وجود دارد؛ روش اول روش دستی و ساده انجام این کار است که دقیقا مانند لیست ها عمل می کند:

my_tuple = (1,2,3,4,5)

X = my_tuple[0]
Y = my_tuple[1]


print(X, Y)

در واقع با انجام این کار عضو اول و دوم tuple را درون متغیر های X و Y قرار داده و سپس آن ها را چاپ کرده ایم (شما می توانید هر تعداد متغیری را به print بدهید). با اجرای این کد نتیجه 2 1 را دریافت می کنید. روش دوم انجام این کار که بسیار بهتر است استفاده از همان روش میانبری است که در جلسات قبل نیز مشاهده کرده بودیم:

my_tuple = (1,2,3,4,5)

x, y, z, *other = my_tuple

print(x, y, z, other)

به عبارت ساده تر ما سه مقدار اول را دریافت کرده و بقیه مقادیر را در متغیری به نام other قرار داده ایم. با اجرای کد بالا نتیجه زیر را می گیریم:

1 2 3 [4, 5]

اگر با دقت به این کدها نگاه کنید متوجه می شوید که other به یک لیست تبدیل شده است! این ماهیت زبان پایتون است که اگر چندین داده را از یک tuple در پایتون به شکل بالا استخراج کنید، آن ها را در یک لیست دسته بندی خواهد کرد.

نهایتا به بحث متدهای tuple در پایتون می رسیم اما اصلا جای نگرانی نیست چرا که tuple ها کلا دو متد دارند که برای ما مهم هستند. متد اول count است که تعداد حضور یک داده درون tuple را می شمارد. به طور مثال:

my_tuple = (1,2,5,3,4,5)

print(my_tuple.count(5))

من در کد بالا پرسیده ام که عدد ۵ چند بار در tuple ما حضور دارد. با اجرای این کد عدد ۲ را دریافت می کنیم چرا که عدد ۵ دو بار در tuple بالا تکرار شده است. متد بعدی ما نیز index است که یک مقدار را گرفته و سپس ایندکس آن مقدار در tuple را برایمان برمی گرداند:

my_tuple = (1,2,5,3,4,5)

print(my_tuple.index(5))

به نظر شما کد بالا ایندکس کدام ۵ را برمی گرداند؟ با اجرای این کد عدد ۲ را دریافت خواهیم کرد بنابراین متوجه می شویم که متد index ایندکس اولین مقداری را که پیدا کند برمی گرداند. طبیعتا بسیاری از متدها بین لیست ها و tuple در پایتون مشترک هستند و اینطور نیست که این دو متد تنها متدهای tuple ها باشند. به طور مثال شما می توانید از تابع len نیز استفاده کنید.

آشنایی با Set ها

آخرین نوع داده ای که با آن آشنا خواهیم شد، نوع داده set است. احتمالا تا این قسمت از کار کمی خسته شده باشید چرا که مباحث تئوری در ابتدای کار زیاد هستند اما باید یادتان باشد که این فصل مخصوص مبانی پایه ای زبان پایتون است و بدون یادگیری انواع داده ها مانند string یا integer یا list و غیره اصلا نمی توانید با زبان پایتون کار کنید. معمولا شروع یک کار، قسمت سخت تر آن است بنابراین توصیه می کنم که ادامه داده و این قسمت آخر را نیز به دقت مطالعه کنید.

همانطور که tuple در پایتون شباهت بسیار زیادی به لیست ها داشتند، set ها نیز شباهت بسیار زیادی به دیکشنری ها دارند با این تفاوت که به جای داشتن جفت های key:value فقط value یا مقدار را داریم. به عبارت دیگر set ها ترکیب عجیبی بین دیکشنری ها و لیست ها هستند اما اگر بخواهیم به صورت فنی set ها را تعریف کنیم می گوییم: set ها مجموعه ای نامنظم از اشیاء یکتا هستند. این جمله یعنی چه؟ قسمت «نامنظم» را که قبلا هم توضیح داده ام؛ نامنظم بودن دیکشنری ها و set ها یعنی مقادیر آن ها در مموری در یک محل خاص نیستند بلکه پراکنده هستند اما در مورد «اشیاء یکتا» باید بگویم که در set ها اجازه داشتن مقادیر تکراری را نداریم. به مثال زیر توجه کنید:

my_set = {1,2,3,4,5}

این یک set معمولی است و هیچ مشکلی ندارد اما اگر یکی از مقادیر را تکراری قرار بدهیم چطور؟

my_set = {1,2,3,4,5,2}

print(my_set)

با اجرای این کد نتیجه زیر را می گیریم:

{1, 2, 3, 4, 5}

به عبارت دیگر مقادیر تکراری را حذف کرده و فقط مقادیر یکتا را برایمان چاپ کرده است! به طور مثال ما می توانیم با استفاده از متد add چندین مقدار دیگر را به set خودمان اضافه کنیم اما نتیجه باز هم همین خواهد شد؛ یعنی در نهایت فقط مقادیر غیرتکراری درون set قرار می گیرند. به مثال زیر توجه کنید:

my_set = {1,2,3,4,5,2}

my_set.add(100)
my_set.add(100)
my_set.add(4)
my_set.add(5)
my_set.add(1)
my_set.add(17)
my_set.add(100)

print(my_set)

من چندین عدد تکراری و غیر تکراری را در مثال بالا به set خودمان اضافه کرده ام. با اجرای کد بالا نتیجه زیر را دریافت می کنیم:

{1, 2, 3, 4, 5, 100, 17}

من در کد بالا عدد ۱۰۰ را سه بار به set اضافه کرده بودم اما همانطور که در نتیجه می بینید فقط یک بار در set حضور دارد.

حالا اگر یک لیست را به شما بدهم و از شما بخواهم که اعضای تکراری را از آن حذف کنید، چه کار می کنید؟ طبیعتا بهترین راه تبدیل این لیست به یک set است اما چطور می توانیم این کار را انجام بدهیم؟ بهتر است خودتان به این مسئله فکر کنید و سپس به پاسخ من نگاه کنید. اگر یادتان باشد هر data type یا نوع داده ای در زبان پایتون یک تابع یا متد دارد که کارش ساخت آن نوع داده خاص است بنابراین:

my_list = [1,2,2,2,3,4,6,5,4,1]

my_set = set(my_list)

print(my_set)

با اجرای این کد نتیجه زیر را دریافت می کنیم:

{1, 2, 3, 4, 5, 6}

به عبارت دیگر تمام مقادیر تکراری از لیست ما حذف شده اند و حالا یک set داریم. در ضمن اگر بخواهید این set یکتا را دوباره به لیست تبدیل کنید می توانیم از ()list استفاده نماییم:

my_list = [1,2,2,2,3,4,6,5,4,1]

my_set = set(my_list)

my_new_list = list(my_set)

print(my_set)
print(my_new_list)

با اجرای این کد نتیجه زیر را می گیریم:

{1, 2, 3, 4, 5, 6}
[1, 2, 3, 4, 5, 6]

نتیجه اول مربوط به my_set است و نتیجه دوم my_new_list است! هر جایی که نیاز به مقادیر یکتا و غیرتکراری داشته باشیم، set ها کمک بزرگی هستند؛ به طور مثال جمع آوری ایمیل یا یوزرنیم که مقادیر یکتا هستند کار مناسبی برای set ها هستند.

مسئله بعدی دریافت یک مقدار از یک set است. آیا می توانیم به روش لیست ها به مقادیر یک set دسترسی داشته باشیم؟ بهتر است این موضوع را در یک کد امتحان کنیم:

my_set = {1,2,3,4,6,5}

print(my_set[0])

با اجرای این کد خطایی به شکل زیر دریافت می کنیم:

Traceback (most recent call last):
  File "main.py", line 3, in <module>
    print(my_set[0])
TypeError: 'set' object is not subscriptable

این خطا به ما می گوید که نمی توانیم از این روش برای دریافت مقادیر set ها استفاده کنیم چرا که set ها بیشتر شبیه دیکشنری ها هستند بنابراین ایندکس ندارند. در واقع نمی توانید یک مقدار را مستقیما از یک set دریافت کنید. برای اینکه بدانیم آیا یک مقدار در یک set حضور دارد یا خیر باید از اپراتورهایی مانند in استفاده کنیم. همچنین برای دریافت مقادیر درون set ها باید از روش های دیگری مانند صدا زدن ()pop استفاده کنیم. یعنی آن مقدار را pop (حذف) کنیم چرا که pop مقدار حذف شده را به ما برمی گرداند. روش دیگری مانند استفاده از حلقه ها و گردش در set ها نیز وجود دارد که هنوز به آن نرسیده ایم. روش بعدی تبدیل set به list برای دسترسی به مقادیر آن از طریق ایندکس است.

متدهای مخصوص set ها

متدهای کار با set ها زیاد نیستند. ما می خواهیم در این بخش با متدهای زیر آشنا شویم:

  • difference
  • discard
  • difference_update
  • intersection
  • isdisjoint
  • issubset
  • issuperset
  • union

برای کار با این متدها به دو set احتیاج داریم که با هم تفاوت داشته باشند. من نام آن ها را my_set و your_set می گذارم. متد اول یا difference تفاوت بین دو set را به ما نشان می دهد:

my_set = {1,2,3,4,5}
your_set = {4,5,6,7,8,9,10}


print(my_set.difference(your_set))

برای مقایسه این دو set باید متد difference را روی یکی از آن ها صدا زده و سپس دیگری را به آن پاس بدهیم. با این کار نتیجه زیر را می گیریم:

{1, 2, 3}

اگر به این دو set نگاه کنید متوجه می شوید که اعداد ۱ و ۲ و ۳ تنها اعدادی هستند که بین این دو set مشترک نیستند بنابراین برایمان برگردانده شده اند.

متد بعدی ما discard است و کارش حذف یک مقدار از یک set است:

my_set = {1,2,3,4,5}
your_set = {4,5,6,7,8,9,10}

my_set.discard(5)

print(my_set)

با اجرای کد بالا عدد ۵ از my_set حذف می شود. برای اثبات این موضوع من my_set را چاپ کرده ام که نتیجه زیر را به ما خواهد داد:

{1, 2, 3, 4}

متد بعدی difference_update است که روی یک set (مثلا my_set) اجرا شده و یک set دیگر (مثلا your_set) را دریافت می کند. حالا تمام مقادیر موجود در your_set را از my_set حذف می کند. به مثال زیر توجه کنید:

my_set = {1,2,3,4,5}
your_set = {4,5,6,7,8,9,10}

my_set.difference_update(your_set)

print(my_set)

کد بالا مقادیر درون your_set را گرفته و در صورتی که این مقادیر در my_set نیز وجود داشته باشند آن ها را حذف می کند. اعداد ۴ و ۵ در my_set و your_set مشترک هستند بنابراین از my_set حذف شده اند. تفاوت difference و difference_update در این است که difference تنها تفاوت بین دو set را برایتان توضیح می داد و به set اصلی دست نمی زد در حالی که  difference_update تفاوت ها را پیدا کرده و حذف می کرد.

متد بعدی intersection است که موارد مشترک بین دو set را برایتان نمایش می دهد:

my_set = {1,2,3,4,5}
your_set = {4,5,6,7,8,9,10}

print(my_set.intersection(your_set))

با اجرای کد بالا نتیجه زیر را می گیرید:

{4, 5}

چرا؟ به دلیل اینکه اعدا ۴ و ۵ بین این دو set مشترک هستند. متد بعدی ما isdisjoint است که به یک سوال ساده پاسخ می دهد: آیا این دو set کاملا از هم جدا هستد؟ اگر هیچ اشتراکی بین این دو set نباشد مقدار true و در غیر این صورت مقدار false برگردانده می شود:

my_set = {1,2,3,4,5}
your_set = {4,5,6,7,8,9,10}

print(my_set.isdisjoint(your_set))

با اجرای کد بالا false را دریافت می کنید چرا که این دو set مقادیر مشترکی دارند. اگر اعداد ۴ و ۵ را از یکی از این set ها حذف کرده و کد بالا را دوباره اجرا کنیم مقدار true را دریافت خواهیم کرد.

متد بعدی ما union است که کارش اجتماع بین دو set است:

my_set = {1,2,3}
your_set = {4,5,6,7,8,9,10}

print(my_set.union(your_set))

با اجرای کد بالا نتیجه زیر را می گیرید:

{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

به عبارت دیگر دو set را با هم ترکیب کرده ایم. روش خلاصه استفاده از union استفاده از علامت pipe (علامت |) است:

print(my_set | your_set)

دو متد نهایی نیز issubset و issuperset هستند که به ترتیب «زیر سِت» و «رو سِت» را مشخص می کنند. به طور مثال:

my_set = {1,2,3}
your_set = {4,5,6,7,8,9,10}

print(my_set.issubset(your_set))

با اجرای این کد false را دریافت می کنیم. چرا؟ به دلیل اینکه my_set زیرست یا زیرمجموعه your_set نیست اما کد زیر چطور؟

my_set = {4,5}
your_set = {4,5,6,7,8,9,10}

print(my_set.issubset(your_set))

این بار مقدار true را می گیریم. چرا؟ به دلیل اینکه my_set زیرست یا زیرمجموعه your_set است. چرا؟ به دلیل اینکه می توانیم کل مقادیر درون my_set را درون your_set پیدا کنیم. معنی subset یا زیرمجموعه در زبان پایتون این است. حالا حالت برعکس این ماجرا superset است:

my_set = {4,5}
your_set = {4,5,6,7,8,9,10}

print(your_set.issuperset(my_set))

با اجرای کد بالا true می گیریم چرا که your_set یک super set برای my_set است. به set بزرگ تر superset و به set کوچک تر subset می گوییم. این پایان فصل مبانی اولیه پایتون بود، از فصل بعدی وارد مبانی ثانویه پایتون می شویم، یعنی مباحثی که هنوز ابتدایی هستند اما از این فصل پیشرفته تر خواهند بود.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.