فه

فهرست در Python

96 تمرین

درباره‌ی فهرست

یک list مجموعه‌ای تغییرپذیر از عنصرها به ترتیب است. مانند بیشتر مجموعه‌ها (نگاه کنید به نوع‌های درونی tuple، dict و set)، لیست‌ها می‌توانند ارجاعی به هر (یا چند) نوع داده داشته باشند، از جمله لیست‌های دیگر. مانند هر دنباله‌ی دیگری، می‌توان به عنصرها با شماره‌ی 0-based index از سمت چپ و -1-based index از سمت راست دسترسی پیدا کرد. لیست‌ها را می‌توان به‌صورت کامل یا بخشی با نماد برش یا <list>.copy() کپی کرد.

لیست‌ها هم از عملیات دنباله‌ی معمولی و هم از عملیات دنباله‌ی تغییرپذیر پشتیبانی می‌کنند، مانند min()/max()، <list>.index()، .append() و .reverse(). می‌توان روی عنصرهای لیست با ساختار for item in <list> پیمایش کرد. وقتی هم اندیس عنصر و هم مقدار عنصر لازم باشد، می‌توان از for index, item in enumerate(<list>) استفاده کرد.

لیست‌ها به‌صورت آرایه‌های پویا پیاده‌سازی شده‌اند، مشابه نوع Arraylist در جاوا، و بیشتر برای ذخیره‌ی گروه‌هایی از داده‌های مشابه (رشته‌ها، اعداد، مجموعه‌ها و غیره) با طول نامعلوم به کار می‌روند (تعداد عنصرها می‌تواند خودسرانه افزایش یا کاهش پیدا کند).

دسترسی به عنصرها، بررسی عضویت با in، یا افزودن عنصرها به سمت «راست» لیست، همگی بسیار کارآمد هستند. افزودن از ابتدا (افزودن به سمت «چپ») یا درج در میانه‌ی لیست بسیار کم‌کارآمدتر است، چون این عملیات نیاز دارند عنصرها جابه‌جا شوند تا ترتیبشان حفظ شود. برای ساختار داده‌ی مشابهی که appends/pops بهینه از نظر حافظه را از هر دو سمت پشتیبانی می‌کند، collections.deque را ببینید که تقریباً از هر دو سو عملکرد O(1) یکسانی دارد.

چون لیست‌ها تغییرپذیرند و می‌توانند ارجاعی به اشیاء دلخواه پایتون داشته باشند، فضای بیشتری هم در حافظه نسبت به array.array یا tuple (که تغییرناپذیر است) با همان طول ظاهری اشغال می‌کنند. با این وجود، لیست‌ها ساختار داده‌ای بسیار انعطاف‌پذیر و کاربردی هستند و بسیاری از متدها و عملیات درونی پایتون لیست را به‌عنوان خروجی تولید می‌کنند.

ساخت

یک list را می‌توان به‌صورت لیترال با کروشه‌های [] و ویرگول بین عنصرها تعریف کرد:

>>> no_elements = []

>>> no_elements
[]

>>> one_element = ["Guava"]

>>> one_element
['Guava']

>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]

>>> elements_separated_with_commas
['Parrot', 'Bird', 334782]

برای خوانایی، وقتی عنصرها یا ساختارهای داده‌ی تودرتوی زیادی در یک لیست وجود دارد، می‌توان از شکستن خطوط استفاده کرد:

>>> lots_of_entries = [
...    "Rose",
...    "Sunflower",
...    "Poppy",
...    "Pansy",
...    "Tulip",
...    "Fuchsia",
...    "Cyclamen",
...    "Lavender"
... ]

>>> lots_of_entries
['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']


# Each data structure is on its own line to help clarify what they are.
>>> nested_data_structures = [
...    {"fish": "gold", "monkey": "brown", "parrot": "grey"},
...    ("fish", "mammal", "bird"),
...    ['water', 'jungle', 'sky']
... ]

>>> nested_data_structures
[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]

سازنده‌ی list() را می‌توان خالی یا با یک تکرارپذیر به‌عنوان آرگومان به کار برد. سازنده روی عنصرهای تکرارپذیر یکی‌یکی می‌چرخد و آن‌ها را به ترتیب به لیست اضافه می‌کند:

>>> no_elements = list()
>>> no_elements
[]

# The tuple is unpacked and each element is added.
>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))

>>> multiple_elements_from_tuple
['Parrot', 'Bird', 334782]

# The set is unpacked and each element is added.
>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})

>>> multiple_elements_from_set
[2, 3, 5, 7, 11]

نتیجه‌ی استفاده از سازنده‌ی لیست با یک رشته یا یک دیکشنری ممکن است غافلگیرکننده باشد:

# String elements (Unicode code points) are iterated through and added *individually*.
>>> multiple_elements_string = list("Timbuktu")

>>> multiple_elements_string
['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']

# Unicode separators and positioning code points are also added *individually*.
>>> multiple_code_points_string = list('अभ्यास')

>>> multiple_code_points_string
['अ', 'भ', '्', 'य', 'ा', 'स']

# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
>>> source_data = {"fish": "gold", "monkey": "brown"}
>>> list(source_data)
['fish', 'monkey']

چون سازنده‌ی list() فقط تکرارپذیرها (یا هیچ‌چیز) را به‌عنوان آرگومان می‌پذیرد، اشیائی که تکرارپذیر نیستند باعث بروز TypeError می‌شوند. در نتیجه، ساخت یک لیست تک‌عنصری با روش لیترال بسیار ساده‌تر است.

# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
>>> one_element = list(16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
>>> one_element_from_iterable = list((16,))

>>> one_element_from_iterable
[16]

دسترسی به عنصرها

می‌توان به عنصرهای داخل لیست‌ها (و همچنین عنصرهای انواع دنباله‌ی دیگر مانند str و tuple) با نماد کروشه دسترسی پیدا کرد. اندیس‌ها می‌توانند از left --> right (شروع از صفر) یا از right --> left (شروع از ۱-) باشند.

اندیس از چپ ⟹






۰
👇🏾
۱
👇🏾
۲
👇🏾
۳
👇🏾
۴
👇🏾
۵
👇🏾
P y t h o n
👆🏾
۶-
👆🏾
۵-
👆🏾
۴-
👆🏾
۳-
👆🏾
۲-
👆🏾
۱-





⟸ اندیس از راست
>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]

# Oatmeal is at index 0 or index -4.
>>> breakfast_foods[0]
'Oatmeal'

>>> breakfast_foods[-4]
'Oatmeal'

# Eggs are at index -2 or 2
>>> breakfast_foods[-2]
'Eggs'

>>> breakfast_foods[2]
'Eggs'

# Toast is at -1
>>> breakfast_foods[-1]
'Toast'

می‌توان به بخشی از یک لیست با نماد برش (<list>[<start>:<stop>]) دسترسی پیدا کرد. یک برش به‌عنوان دنباله‌ای از عنصرها در موقعیت index تعریف می‌شود، به‌طوری که start <= index < stop باشد. برش یک نسخه از عنصرهای «برش‌خورده» را برمی‌گرداند و list اصلی را تغییر نمی‌دهد.

می‌توان در برش از پارامتر step هم استفاده کرد (<list>[<start>:<stop>:<step>]) تا از عنصرهای برگشتی «بپرد» یا آن‌ها را فیلتر کند (برای نمونه، step با مقدار ۲ هر یک عنصر دیگر در آن بخش را انتخاب می‌کند):

>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]

# If there is no step parameter, the step is assumed to be 1.
>>> middle_colors = colors[2:6]

>>> middle_colors
['Green', 'Yellow', 'Orange', 'Pink']

# If the start or stop parameters are omitted, the slice will
# start at index zero, and will stop at the end of the list.
>>> primary_colors = colors[::3]

>>> primary_colors
['Red', 'Yellow', 'Blue']

کار با لیست‌ها

لیست‌ها یک تکرارگر فراهم می‌کنند و می‌توان مانند سایر انواع دنباله روی آن‌ها حلقه زد، با استفاده از for item in <list> یا for index, item in enumerate(<list>):

# Make a list, and then loop through it to print out the elements
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for item in colors:
...     print(item)

Orange
Green
Grey
Blue


# Print the same list, but with the indexes of the colors included
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for index, item in enumerate(colors):
...     print(item, ":", index)

Orange : 0
Green : 1
Grey : 2
Blue : 3


# Start with a list of numbers and then loop through and print out their cubes.
>>> numbers_to_cube = [5, 13, 12, 16]
>>> for number in numbers_to_cube:
...     print(number**3)

125
2197
1728
4096

یک روش رایج برای ساختن لیستی از مقادیر، استفاده از <list>.append() درون یک حلقه است:

>>> cubes_to_1000 = []
>>> for number in range(11):
...    cubes_to_1000.append(number**3)

>>> cubes_to_1000
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

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

# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]

>>> new_via_concatenate
['George', 5, 'cat', 'Tabby']

# Likewise, using the multiplication operator * is the equivalent of using + n times.
>>> first_group = ["cat", "dog", "elephant"]
>>> multiplied_group = first_group * 3

>>> multiplied_group
['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']

# Another method for combining 2 lists is to use slice assignment or a loop-append.
# This assigns the second list to index 0 in the first list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
>>> first_one[0:0] = second_one

>>> first_one
['George', 5, 'cat', 'Tabby']

# This loops through the first list and appends its items to the end of the second list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]

>>> for item in first_one:
...      second_one.append(item)

>>> second_one
['George', 5, 'cat', 'Tabby']

چند هشدار

به یاد بیاورید که متغیرها در پایتون برچسب‌هایی هستند که به اشیاء زیرین اشاره می‌کنند. lists یک لایه‌ی دیگر هم به‌عنوان اشیاء ظرف اضافه می‌کنند، یعنی ارجاع‌هایی به اشیاء را برای عنصرهای جمع‌آوری‌شده‌شان نگه می‌دارند. اگر درست مدیریت نشود، این می‌تواند در کار با لیست‌ها به مشکلات متعدد منجر شود.

نسبت دادن بیش از یک اسم متغیر

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

ساختن یک shallow_copy با list.copy() یا برش، از این پیچیدگی ارجاع سطح اول جلوگیری می‌کند. یک shallow_copy یک شیء list جدید می‌سازد، اما برای عنصرهای لیست درون آن اشیاء جدید نمی‌سازد. این نوع کپی معمولاً برایتان کافی است تا عنصرها را به‌طور مستقل از دو شیء list اضافه یا حذف کنید و به‌طور مؤثر دو لیست «جدا» داشته باشید.

>>> actual_names = ["Tony", "Natasha", "Thor", "Bruce"]

# Assigning a new variable name does not make a copy of the container or its data.
>>> same_list = actual_names

#  Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

>>> actual_names
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

#  Likewise, altering the data in the list via the original name will also alter the data under the new name.
>>> actual_names[0] = "Wanda"
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

# If you copy the list, there will be two separate list objects which can be changed independently.
>>> copied_list = actual_names.copy()
>>> copied_list[0] = "Tony"

>>> actual_names
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

>>> copied_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

این پیچیدگی ارجاع وقتی با لیست‌های تودرتو یا ضرب‌شده کار می‌کنید شدیدتر هم می‌شود (نمونه‌های زیر از پست وبلاگ عالی Names and values: making a game board نوشته‌ی Ned Batchelder در سال ۲۰۱۳ گرفته شده‌اند):

from pprint import pprint

# This will produce a game grid that is 8x8, pre-populated with zeros.
>>> game_grid = [[0]*8]*8

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# This attempt doesn't work because all the rows are referencing the same underlying list object.
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

اما در این شرایط، یک shallow_copy کافی است تا رفتار مورد نظر ما ممکن شود:

from pprint import pprint

# This loop will safely produce a game grid that is 8x8, pre-populated with zeros
>>> game_grid = []
>>> filled_row = [0] * 8
>>> for row in range(8):
...    game_grid.append(filled_row.copy()) # This is making a new shallow copy of the inner list object each iteration.

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# The game grid now works the way we expect it to!
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

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

from pprint import pprint

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

# We'd like a new board, so we make a shallow copy.
>>> new_game_grid = game_grid.copy()

# But a shallow copy doesn't copy the contained references or objects.
>>> new_game_grid[0][0] = 'X'

# So changing the items in the copy also changes the originals items.
>>>  pprint(game_grid)
[['X', 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

انواع داده‌ی مرتبط

از لیست‌ها اغلب به‌عنوان پشته و صف استفاده می‌شود، اگرچه پیاده‌سازی زیرین‌شان افزودن از ابتدا و درج را کند می‌کند. ماژول collections گونه‌ای از deque را ارائه می‌کند که برای افزودن و حذف سریع از هر دو سر بهینه شده و به‌صورت یک لیست پیوندی دوطرفه پیاده‌سازی شده است. از لیست‌های تودرتو هم برای مدل‌سازی ماتریس‌های کوچک استفاده می‌شود، هرچند کتابخانه‌های Numpy و Pandas برای دستکاری کارآمد ماتریس و داده‌های جدولی بسیار قوی‌تر هستند. ماژول collections یک نوع UserList هم ارائه می‌کند که می‌توان آن را برای نیازهای تخصصی لیست سفارشی‌سازی کرد.

ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود

فهرست را یاد بگیرید

تمرین کردن قفل شده است

برای تمرین فهرست قفل 5 تمرین دیگر را باز کنید