المسارات
/
Python
Python
/
المنهج
/
فك الحزم والإسناد المتعدد
فك

فك الحزم والإسناد المتعدد في Python

3 تمارين

نبذة عن فك الحزم والإسناد المتعدد

يشير فك الحزم إلى استخراج عناصر مجموعة مثل list أو tuple أو dict باستخدام التكرار. ويمكن بعد ذلك إسناد القيم التي جرى فكّها إلى متغيرات ضمن العبارة نفسها. ومن الأمثلة الشائعة جدًا على ذلك for item in list، حيث يأخذ item قيمة كل عنصر من عناصر list واحدًا تلو الآخر خلال التكرار.

الإسناد المتعدد هو القدرة على إسناد عدة متغيرات إلى قيم مفكوكة الحزم ضمن عبارة واحدة. وهذا يسمح بأن يكون الكود أكثر إيجازًا ووضوحًا، ويُنجز بفصل المتغيرات المراد إسنادها بفاصلة، مثل first, second, third = (1,2,3) أو for index, item in enumerate(iterable).

غالبًا ما يُستخدم العاملان الخاصان * و** في سياقات فك الحزم. يمكن استخدام * لدمج عدة list/tuple في list/tuple واحد، عبر فك حزمة كل منها في list/tuple جديد مشترك. ويمكن استخدام ** لدمج عدة قواميس في قاموس واحد، عبر فك حزمة كل منها في dict جديد مشترك.

وعندما يُستخدم العامل * دون مجموعة، فإنه يحزم عددًا من القيم في list. ويُستخدم هذا غالبًا في الإسناد المتعدد لتجميع كل العناصر "المتبقية" التي ليس لها إسنادات فردية في متغير واحد.

ومن الشائع في Python أيضًا الاستفادة من سلوك فك الحزم والحزم هذا عند استخدام أو تعريف دوال تقبل أي عدد من الوسائط الموضعية أو المفتاحية. كثيرًا ما سترى هذه المعاملات "الخاصة" معرَّفة بالشكل def some_function(*args, **kwargs)، وهذه الوسائط "الخاصة" مستخدَمة بالشكل some_function(*some_tuple, **some_dict).

Caution

لا ينبغي الخلط بين *<variable_name> و**<variable_name> وبين * و**. فبينما يُستخدم * و** للضرب والرفع إلى أُس على الترتيب، يُستخدم *<variable_name> و**<variable_name> كعاملي حزم وفك حزم.

الإسناد المتعدد

في الإسناد المتعدد، يجب أن يتطابق عدد المتغيرات على يسار عامل الإسناد (=) مع عدد القيم على يمينه. ولفصل القيم، استخدم فاصلة ,:

>>> a, b = 1, 2
>>> a
1

إذا تلقى الإسناد المتعدد عددًا غير صحيح من المتغيرات مقابل القيم المعطاة، فسيُرفع استثناء ValueError:

>>> x, y, z = 1, 2

ValueError: too many values to unpack (expected 3, got 2)

لا يقتصر الإسناد المتعدد على نوع بيانات واحد:

>>> x, y, z = 1, "Hello", True
>>> x
1

>>> y
'Hello'

>>> z
True

يمكن استخدام الإسناد المتعدد لتبديل العناصر في lists. وهذا الأسلوب شائع جدًا في خوارزميات الترتيب. على سبيل المثال:

>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]

ولأن tuples غير قابلة للتغيير، فلا يمكنك تبديل العناصر في tuple.

فك الحزم

Note

تستخدم الأمثلة أدناه lists، لكن المفاهيم نفسها تنطبق على tuples.

في Python، يمكنك فك حزمة عناصر list/tuple/dict إلى متغيرات منفصلة. ولأن القيم تظهر داخل lists/tuples بترتيب معيّن، فإنها تُفكّ حزمتها إلى متغيرات بالترتيب نفسه:

>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"

إذا كانت هناك قيم لا تحتاج إليها، يمكنك استخدام _ للإشارة إليها:

>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"

فك الحزم العميق

يعمل فك حزمة القيم وإسنادها من list/tuple داخل list أو tuple (وتُعرف أيضًا بالمصفوفات/الصفوف المتداخلة) بالطريقة نفسها التي يعمل بها فك الحزم السطحي، لكنه غالبًا يحتاج إلى محددات توضّح سياق القيمة أو موضعها:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"

>>> d
"potato"

يمكنك أيضًا فك حزمة جزء من list/tuple متداخل فكًّا عميقًا:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]

>>> c
"carrot"

إذا كان في فك الحزم متغيرات في مواضع غير صحيحة و/أو عدد غير صحيح من القيم، فستحصل على ValueError:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables

ValueError: too many values to unpack (expected 1)

فك حزمة مصفوفة/صف باستخدام *

عند فك حزمة list/tuple يمكنك استخدام العامل * لالتقاط القيم "المتبقية". وهذا أوضح من تقطيع list/tuple (وهو أقل قابلية للقراءة في بعض الحالات). على سبيل المثال، يمكننا استخراج العنصر الأول وحزم القيم المتبقية في list جديد لا يحتوي على العنصر الأول:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"

>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]

يمكننا أيضًا استخراج القيم في بداية list ونهايتها مع تجميع كل القيم في الوسط:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"

>>> middle
["banana", "cherry", "orange", "kiwi"]

يمكننا أيضًا استخدام * في فك الحزم العميق:

>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"

>>> rest
["banana", "melon"]

فك حزمة قاموس

فك حزمة قاموس مختلف قليلًا عن فك حزمة list/tuple. التكرار على القواميس يعتمد افتراضيًا على المفاتيح. لذا عند فك حزمة dict، يمكنك فك حزمة المفاتيح فقط، وليس القيم:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"

إذا أردت فك حزمة القيم، يمكنك استخدام الطريقة <dict>.values():

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6

إذا احتجت إلى المفاتيح والقيم معًا، فاستخدم الطريقة <dict>.items(). تُولّد <dict>.items() عرضًا قابلًا للتكرار يحتوي على أزواج مفتاح-قيمة. ويمكن فك حزمة هذه الأزواج إلى tuple:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)

الحزم

الحزم هو القدرة على تجميع عدة قيم في list واحد يُسند إلى متغير. وهذا مفيد عندما تريد فك حزمة القيم وإجراء تغييرات ثم حزم النتائج مرة أخرى في متغير. كما يجعل من الممكن إجراء عمليات دمج على lists/tuples/dicts أو أكثر.

حزم مصفوفة/صف باستخدام *

يمكن حزم list/tuple باستخدام العامل *. سيؤدي ذلك إلى حزم كل القيم في list/tuple.

>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]

# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits

# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")

# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma. 
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

لمزيد من التفاصيل حول استخدام * و**، اطّلع على PEP 3132 وPEP 448.

حزم قاموس باستخدام **

يتم حزم قاموس باستخدام العامل **. سيؤدي ذلك إلى حزم كل أزواج مفتاح-قيمة من قاموس إلى قاموس آخر، أو دمج قاموسين معًا.

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}

# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}

# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}

استخدام * و** مع الدوال

الحزم باستخدام معاملات الدالة

عند إنشاء دالة تقبل أي عدد من الوسائط، يمكنك استخدام *args أو **kwargs في تعريف الدالة. يُستخدم *args لحزم أي عدد من الوسائط الموضعية (غير المفتاحية)، ويُستخدم **kwargs لحزم أي عدد من الوسائط المفتاحية.

استخدام *args:

# This function is defined to take any number of positional arguments

>>> def my_function(*args):
...   print(args)

# Arguments given to the function are packed into a tuple

>>> my_function(1, 2, 3)
(1, 2, 3)

>>> my_function("Hello")
("Hello")

>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")

استخدام **kwargs:

# This function is defined to take any number of keyword arguments

>>> def my_function(**kwargs):
...   print(kwargs)

# Arguments given to the function are packed into a dictionary

>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}

يمكن أيضًا استخدام *args و**kwargs معًا:

>>> def my_function(*args, **kwargs):
...   print(sum(args))
...   for key, value in kwargs.items():
...       print(str(key) + " = " + str(value))

>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3

يمكنك أيضًا كتابة معاملات قبل *args للسماح بوسائط موضعية محددة. وعندئذ يجب أن تظهر الوسائط المفتاحية الفردية قبل **kwargs.

Caution

يجب أن تكون الوسائط مُهيكلة بهذا الشكل:

def my_function(<positional_args>, *args, <key-word_args>, **kwargs)

إذا لم تتبع هذا الترتيب فستحصل على خطأ.

>>> def my_function(a, b, *args):
...   print(a)
...   print(b)
...   print(args)

>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

كتابة الوسائط بترتيب غير صحيح ستؤدي إلى خطأ:

>>> def my_function(*args, a, b):
...   print(args)

>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
  File "c:\something.py", line 3, in <module>
    my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'

فك الحزم في استدعاءات الدوال

يمكنك استخدام * لفك حزمة list/tuple من الوسائط في استدعاء دالة. وهذا مفيد جدًا للدوال التي لا تقبل iterable:

>>> def my_function(a, b, c):
...   print(c)
...   print(b)
...   print(a)

numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1

يُعد استخدام فك الحزم بـ * مع الدالة المدمجة zip() حالة استخدام شائعة أخرى. تأخذ الدالة zip() عدة كائنات قابلة للتكرار وتُرجع list من tuples تجمع القيم من كل iterable:

>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم فك الحزم والإسناد المتعدد

التدريب مقفل

افتح 3 تمارين إضافية لممارسة فك الحزم والإسناد المتعدد