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 في Java، وغالبًا ما تُستخدم لتخزين مجموعات من البيانات المتشابهة (سلاسل نصية، أعداد، مجموعات، إلخ) ذات طول غير معروف (قد يزداد عدد العناصر أو ينقص بشكل اعتباطي).
الوصول إلى العناصر، أو التحقق من العضوية عبر in، أو إضافة عناصر إلى الجانب "الأيمن" من المصفوفة، كلها عمليات فعّالة جدًا. أما الإضافة إلى المقدمة (الإضافة إلى الجانب "الأيسر") أو الإدراج في منتصف المصفوفة فهي أقل فعّالية بكثير، لأن هذه العمليات تتطلب إزاحة العناصر للحفاظ على تسلسلها. وللاطلاع على بنية بيانات مشابهة تدعم appends/pops من الجانبين بكفاءة في الذاكرة، انظر إلى collections.deque، الذي يقدّم أداءً يقارب O(1) في كلا الاتجاهين.
ولأن المصفوفات قابلة للتغيير ويمكن أن تحتوي على مراجع إلى كائنات Python عشوائية، فهي تشغل مساحة أكبر في الذاكرة من array.array أو tuple (غير القابل للتغيير) بالطول الظاهري نفسه. ورغم ذلك، تبقى المصفوفات بنية بيانات مرنة ومفيدة للغاية، وكثير من الطرق والعمليات المدمجة في Python تُنتج مصفوفات كنتيجة لها.
يمكن تعريف 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 (بدءًا من -1).
| الفهرس من اليسار ⟹ |
|
⟸ الفهرس من اليمين |
>>> 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 بقيمة 2 سيختار عنصرًا من كل عنصرين في هذا الجزء):
>>> 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']
تذكّر أن المتغيرات في Python هي تسميات تشير إلى كائنات أساسية.
وتضيف 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"]
تزداد هذه المشكلة المرجعية سوءًا عند التعامل مع المصفوفات المتداخلة أو المضاعفة (الأمثلة التالية مأخوذة من المقالة الرائعة لعام 2013 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 الذي يمكن تخصيصه لتلبية احتياجات خاصة بالمصفوفات.