الفرق بين المراجعتين لصفحة: «Python/pickle»
أنشأ الصفحة ب'تطبّق وحدة pickle بروتوكولات ثنائية لغرض سَلسلَة وإلغاء سَلسَلَة بنية كائنات بايثون. تطلق تسمي...' |
لا ملخص تعديل |
||
(4 مراجعات متوسطة بواسطة نفس المستخدم غير معروضة) | |||
سطر 1: | سطر 1: | ||
تطبّق وحدة pickle بروتوكولات ثنائية لغرض سَلسلَة وإلغاء سَلسَلَة بنية كائنات بايثون. تطلق تسمية Pickling على العملية التي يتحوّل فيها تسلسل هرمي لكائن بايثون إلى تدفق بايتات byte stream، وتطلق تسمية Unpickling على العملية العكسية والتي يتحوّل فيها تدفّق بايتات (من ملف ثنائي أو كائن شبيه بالبايتات) إلى تسلسل هرمي لكائن بايثون. | <noinclude>{{DISPLAYTITLE:الوحدة <code>pickle</code> في بايثون}}</noinclude> | ||
تطبّق وحدة <code>pickle</code> بروتوكولات ثنائية لغرض سَلسلَة وإلغاء سَلسَلَة بنية كائنات بايثون. تطلق تسمية Pickling على العملية التي يتحوّل فيها تسلسل هرمي لكائن بايثون إلى تدفق بايتات byte stream، وتطلق تسمية Unpickling على العملية العكسية والتي يتحوّل فيها تدفّق بايتات (من ملف ثنائي أو كائن شبيه بالبايتات) إلى تسلسل هرمي لكائن بايثون. | |||
تحمل هاتان العمليتان (Pickling و Unpickling) أسماءً أخرى مثل السَلسَلَة "serialization"، والترتيب "marshalling" (ليس المقصود هنا وحدة marshal) والتسطيح "falttening". | تحمل هاتان العمليتان (Pickling و Unpickling) أسماءً أخرى مثل السَلسَلَة "serialization"، والترتيب "marshalling" (ليس المقصود هنا وحدة <code>[[Python/marshal|marshal]]</code>) والتسطيح "falttening". | ||
سنستخدم مصطلحي السلسلة وإلغاء السلسلة في هذا التوثيق كتعريب لمصطلحي pickling و unpickling. | سنستخدم مصطلحي السلسلة وإلغاء السلسلة في هذا التوثيق كتعريب لمصطلحي pickling و unpickling. | ||
تحذير | '''تحذير:''' وحدة <code>pickle</code> غير محمية تجاه البيانات المبنيّة على نحو خاطئ أو البيانات الخبيثة؛ لذا لا تلغِ سَلسَلة أي بيانات قادمة من مصادر غير موثوقة. | ||
== العلاقة التي تربط هذه الوحدة مع وحدات بايثون الأخرى == | == العلاقة التي تربط هذه الوحدة مع وحدات بايثون الأخرى == | ||
سطر 12: | سطر 12: | ||
=== مقارنة الوحدة مع وحدة marshal === | === مقارنة الوحدة مع وحدة marshal === | ||
تمتلك بايثون وحدة سلسلة أكثر بساطة من وحدة pickle وتدعى وحدة | تمتلك بايثون وحدة سلسلة أكثر بساطة من وحدة <code>pickle</code> وتدعى وحدة <code>[[Python/marshal|marshal]]</code>، ولكن يفضّل استخدام وحدة <code>pickle</code> بصورة عامة لإلغاء سَلسَلة كائنات بايثون، أما الهدف الرئيسي من وجود وحدة <code>[[Python/marshal|marshal]]</code> هو دعم ملفات بايثون ذات الامتداد <code>.pyc</code>. | ||
تختلف وحدة pickle عن وحدة marshal في عدة نقاط: | تختلف وحدة <code>pickle</code> عن وحدة <code>[[Python/marshal|marshal]]</code> في عدة نقاط: | ||
* تتابع وحدة pickle الكائنات التي تقوم بسلسلتها، وبهذا لن تؤدي الإشارة إلى الكائن نفسه إلى إعادة عملية السلسلة مرة أخرى. أما وحدة marshal لا تدعم ذلك. | * تتابع وحدة <code>pickle</code> الكائنات التي تقوم بسلسلتها، وبهذا لن تؤدي الإشارة إلى الكائن نفسه إلى إعادة عملية السلسلة مرة أخرى. أما وحدة <code>[[Python/marshal|marshal]]</code> لا تدعم ذلك. لهذه الميزة تطبيقات في الكائنات التعاودية recursive وفي مشاركة الكائنات. الكائنات التعاودية هي كائنات تتضمن إشارات إلى نفسها، ولا يمكن التعامل مع مثل هذه الكائنات بواسطة وحدة <code>[[Python/marshal|marshal]]</code>، وفي الواقع يؤدي استخدام هذه الوحدة مع الكائنات التعاودية إلى انهيار مفسّر بايثون. تحدث عملية مشاركة الكائن عند وجود إشارات متعددة لنفس الكائن في مواضع مختلفة من تسلسل الكائن الذي تجري سلسلته. تخزّن وحدة <code>pickle</code> مثل هذه الكائنات مرة واحدة، وتضمن أنّ جميع الإشارات الأخرى موجّهة نحو النسخة الأساسية. الكائنات المشتركة تبقى مشتركة، وهو أمر قد يكون في غاية الأهمية للكائنات القابلة للتعديل. | ||
لهذه الميزة تطبيقات في الكائنات التعاودية recursive وفي مشاركة الكائنات. | |||
الكائنات التعاودية هي كائنات تتضمن إشارات إلى نفسها، ولا يمكن التعامل مع مثل هذه الكائنات بواسطة وحدة | |||
* لا يمكن استخدام وحدة marshal لسلسلة الأصناف المعرّفة من قبل المستخدم إضافة إلى النسخ التابعة لها، أما وحدة pickle فبمقدورها أن تحفظ وتسترجع نسخ الصنف بطريقة واضحة، ولكن يجب أن يكون تعريف الصنف قابلًا للاستيراد وأن يكون موجودًا في نفس الوحدة كما هو الحال عند حفظ الكائن. | * لا يمكن استخدام وحدة <code>[[Python/marshal|marshal]]</code> لسلسلة الأصناف المعرّفة من قبل المستخدم إضافة إلى النسخ التابعة لها، أما وحدة <code>pickle</code> فبمقدورها أن تحفظ وتسترجع نسخ الصنف بطريقة واضحة، ولكن يجب أن يكون تعريف الصنف قابلًا للاستيراد وأن يكون موجودًا في نفس الوحدة كما هو الحال عند حفظ الكائن. لا يمكن ضمان نقل صيغة السَلسَلة المعتمدة في وحدة <code>[[Python/marshal|marshal]]</code> بين إصدارات بايثون المختلفة، والسبب في ذلك يعود إلى أنّ المهمّة الرئيسية لهذه الوحدة هو دعم ملفات <code>.pyc</code>، وما من شيء يمكنه أن يفرض على مستخدمي بايثون عدم تغيير صيغة السَلسَلة بطريقة لا تكون متوافقة مع الإصدارات السابقة. في حين يمكن ضمان توافقية صيغة السَلسَلة المعتمدة في وحدة <code>pickle</code> مع الإصدارات السابقة من بايثون. | ||
=== مقارنة <code>pickle</code> مع json === | |||
هناك عدد من الفروقات الجوهرية بين بروتوكول <code>pickle</code> وJSON (اختصار JavaScript Object Notation): | |||
* JSON هي صيغة لسلسلة النصوص (مخرجاتها هي نصوص بترميز unicode، وترمّز لاحقًا في معظم الأحيان إلى الترميز utf-8) في حين أنّ <code>pickle</code> هي صيغة لسلسلة البيانات الثنائية. | |||
* JSON هي صيغة لسلسلة النصوص (مخرجاتها هي نصوص بترميز unicode، وترمّز لاحقًا في معظم الأحيان إلى الترميز utf-8) في حين أنّ pickle هي صيغة لسلسلة البيانات الثنائية. | |||
* JSON قابلة للقراءة من قبل البشر، أما pickle فلا. | * JSON قابلة للقراءة من قبل البشر، أما pickle فلا. | ||
* JSON واسعة الانتشار خارج بيئة بايثون، أما pickle فمخصّصة للغة بايثون فقط. | * JSON واسعة الانتشار خارج بيئة بايثون، أما <code>pickle</code> فمخصّصة للغة بايثون فقط. | ||
* يمكن لـ JSON أن تمثّل -على نحو افتراضي- جزءًا من أنواع بايثون الداخلية، ولا تدعم الأصناف المخصصة، في حين أنّ بمقدور وحدة pickle أن تمثّل عددًا كبيرًا جدًا من أنواع بايثون (يمكن تمثيل الكثير من الأنواع تلقائيًا بواسطة بعض الأدوات التي تقدّمها بايثون، ويمكن تمثيل الحالات المعقّدة عن طريق تطبيق واجهات برمجية خاصّة ببعض الكائنات). | * يمكن لـ JSON أن تمثّل -على نحو افتراضي- جزءًا من أنواع بايثون الداخلية، ولا تدعم الأصناف المخصصة، في حين أنّ بمقدور وحدة <code>pickle</code> أن تمثّل عددًا كبيرًا جدًا من أنواع بايثون (يمكن تمثيل الكثير من الأنواع تلقائيًا بواسطة بعض الأدوات التي تقدّمها بايثون، ويمكن تمثيل الحالات المعقّدة عن طريق تطبيق واجهات برمجية خاصّة ببعض الكائنات). | ||
صيغة تدفق البيانات | == صيغة تدفق البيانات == | ||
إنّ صيغة البيانات التي تستخدمها وحدة pickle خاصّة ببايثون، ومن حسنات ذلك عدم وجود أي قيود يمكن أن تفرض من مصادر خارجية مثل JSON أو XDR (والذي لا يمكنه تمثيل عملية مشاركة المؤشر)، ولكن ذلك يعني عدم قدرة البرامج المكتوبة بلغات أخرى من إعادة بناء كائنات بايثون المسَلسَلة. | إنّ صيغة البيانات التي تستخدمها وحدة <code>pickle</code> خاصّة ببايثون، ومن حسنات ذلك عدم وجود أي قيود يمكن أن تفرض من مصادر خارجية مثل JSON أو XDR (والذي لا يمكنه تمثيل عملية مشاركة المؤشر)، ولكن ذلك يعني عدم قدرة البرامج المكتوبة بلغات أخرى من إعادة بناء كائنات بايثون المسَلسَلة. | ||
تستخدم صيغة بيانات pickle تمثيلًا ثنائيًا مضغوطًا نسبيًا، وإن كنت مهتمًا بمسألة الحجم فيمكن ضغط البيانات المسَلسَلة بكفاءة عالية. | تستخدم صيغة بيانات <code>pickle</code> تمثيلًا ثنائيًا مضغوطًا نسبيًا، وإن كنت مهتمًا بمسألة الحجم فيمكن ضغط البيانات المسَلسَلة بكفاءة عالية. | ||
تتضمن وحدة pickletools أدوات لتحليل تدفق البيانات الناتجة من وحدة | تتضمن وحدة <code>[[Python/pickletools|pickletools]]</code> أدوات لتحليل تدفق البيانات الناتجة من وحدة <code>pickle</code>، وتحتوي الشيفرة المصدرية لوحدة <code>[[Python/pickletools|pickletools]]</code> على تعليقات مكثفة حول شيفرات العمليات المستخدمة في بروتوكول <code>pickle</code>. | ||
هناك في الوقت الحاضر خمسة بروتوكولات متنوعة يمكن استخدامها في عملية السلسلة. وكلما كان البروتوكول المستخدم أعلى، كان إصدار بايثون المطلوب لقراءة البيانات المسلسلة الناتجة أحدث. | هناك في الوقت الحاضر خمسة بروتوكولات متنوعة يمكن استخدامها في عملية السلسلة. وكلما كان البروتوكول المستخدم أعلى، كان إصدار بايثون المطلوب لقراءة البيانات المسلسلة الناتجة أحدث. | ||
* البروتوكول رقم 0 هو البروتوكول الأصلي الذي يمكن قراءته من قبل البشر وهو متوافق مع النسخ الأولى من بايثون. | |||
* البروتوكول رقم 1 هو صيغة ثنائية قديمة، وهو متوافق أيضًا مع النسخ الأولى من بايثون. | |||
* ُقدّم الإصدار 2.3 من بايثون البروتوكول رقم 2، ويوفّر هذا البروتوكول أصنافًا ذات شكل جديد لإجراء عمليات سلسلة ذات كفاءة أعلى. يمكن الرجوع إلى '''[https://www.python.org/dev/peps/pep-0307 PEP 307]''' للاطلاع على المزيد من المعلومات حول التحسينات التي أتى بها البروتوكول رقم 2. | |||
* أضيف البروتوكول رقم 3 في الإصدار 3.0 من بايثون. يدعم هذا البروتوكول كائنات bytes صراحةً، ولا يمكن إلغاء سلسلته في إصدارات 2.x من اللغة، وهو البروتوكول الافتراضي وينصح باستخدامه عندما يكون التوافق مع إصدارات بايثون 3 الأخرى أمرًا مطلوبًا. | |||
* أضيف البروتوكول رقم 4 في الإصدار 3.4 من بايثون، ويدعم الكائنات ذات الأحجام الكبيرة، ويُتيح سَلسَلة أنواع إضافية من الكائنات، ويقدّم بعض التحسينات على صيغة البيانات. يمكن مراجعة '''[https://www.python.org/dev/peps/pep-3154 PEP 3154]''' للاطلاع على التحسينات التي أتى بها البروتوكول رقم 4. | |||
'''ملاحظة:''' إنّ مفهوم السَلسَلة Serialization مفهوم أكثر بدائية من الاستمرارية persistence، فبالرغم من أن <code>pickle</code> تقرأ كائنات الملفات وتكتب فيها، إلّا أنّها لا تعالج مسألة تسمية الكائنات المستمرة، ولا تعالج مشكلة الوصول المتزامن للكائنات المستمرة (وهي مشكلة أعقد بكثير). | |||
يمكن لوحدة <code>pickle</code> أن تحوّل كائنًا معقدًا إلى تدفق بايتات وبمقدورها أن تحول تدفق بايتات إلى كائن يحمل البنية الداخلية نفسها، ولعلّ أوضح ما يمكن فعله بتدفق البايتات هذا هو كتابته في ملف، ولكن يمكن إرساله عبر شبكة معيّنة أو تخزينه في قاعدة بيانات. تقدّم وحدة shelve واجهة بسيطة لسَلسَلة وإلغاء سلسلة الكائنات في ملف قاعدة بيانات بنمط DBM. | |||
== الواجهة البرمجية للوحدة == | |||
لا تتطلّب عملية سَلسَلة كائن معيّن سوى استدعاء الدالة <code>dumps()</code>، وكذلك الأمر في عملية إلغاء سَلسَلة تدفق بيانات معيّن، إذ يكفي استدعاء الدالة <code>loads()</code>. ولكن إن كنت بحاجة إلى التحكم بصورة أكبر على عملية السلسلة وإلغاء السلسلة، فيمكن إنشاء كائن <code>Pickler</code> أو <code>Unpickler</code> على التوالي. | |||
تقدّم وحدة <code>pickle</code> الثوابت التالية: | |||
<code>pickle.HIGHEST_PROTOCOL</code> | |||
هذا الثابت هو عدد صحيح يعرض أعلى رقم متاح للبروتوكول، ويمكن تمرير هذا الثابت كقيمة للمعامل protocol في الدالتين <code>dump()</code> و <code>dumps()</code> إلى جانب الدالة البانية للكائن <code>Pickler</code>. | |||
<code>pickle.DEFAULT_PROTOCOL</code> | |||
هذا الثابت هو عدد صحيح يمثّل النسخة الافتراضية من البروتوكول المستخدم في عملية السلسلة، ويمكن أن تكون قيمته أقل من قيمة الثابت <code>HIGHEST_PROTOCOL</code>. البروتوكول الافتراضي الحالي هو الرقم 3، وهو بروتوكول جديد صمّم للإصدار 3 من بايثون. | |||
== استثناءات الوحدة pickle == | |||
تعرّف وحدة <code>pickle</code> ثلاثة استثناءات هي: | |||
=== الاستثناء <code>pickle.PickleError</code> === | |||
صنف أساسي مشترك لجميع الاستثناءات الأخرى في عملية السلسلة. يتفرّع هذا الصنف من الصنف Exception. | |||
=== الاستثناء <code>pickle.PicklingError</code> === | |||
يُطلَق هذا الاستثناء عند ما يُقابل المُسَلسِل <code>Pickler</code> كائنٌ غير قابل للسَلسَلة. هذا الصنف متفرّع من الصنف <code>PickleError</code>. | |||
يمكن الرجوع إلى قسم "ما هي الكائنات القابلة للسلسلة؟" للتعرف على الأنواع التي يمكن سلسلتها بواسطة هذه الوحدة. | |||
=== الاستثناء <code>pickle.UnpicklingError</code> === | |||
يُطلق هذا الاستثناء عند حدوث مشكلة في عملية إلغاء سلسلة كائن معين، كحدوث خلل في البيانات أو حصول خرق أمني. هذا الصنف متفرّع من الصنف <code>PickleError</code>. | |||
يجب الانتباه إلى أنّ هناك استثناءات أخرى قد تُطلق أثناء عملية إلغاء السلسلة، منها (على سبيل المثال وليس الحصر) الاستثناءات <code>AttributeError</code> و <code>EOFError</code> و <code>ImportError</code> و <code>IndexError</code>. | |||
== أصناف الوحدة pickle == | |||
تصدّر الوحدة pickle صنفين هما: | |||
=== [[Python/Pickler|الصنف <code>Pickler</code>]] === | |||
يستخدم هذا الصنف ملفًّا يكتب فيه تدفق البيانات المسلسلة. | |||
=== [[Python/Unpickler|الصنف <code>Unpickler</code>]] === | |||
يستخدم هذا الصنف ملفًّا ثنائيًا يقرأ منه تدفق البيانات المسلسلة. | |||
== ما هي الكائنات القابلة للسَلسَلة؟ == | |||
يمكن سَلسَلة الأنواع التالية: | |||
* None و True و False. | |||
* [[Python/int|الأعداد الصحيحة]]، و<nowiki/>[[Python/float|الأعداد ذات الفاصلة العائمة]]، و<nowiki/>[[Python/complex|الأعداد المركبة]]. | |||
* [[Python/str|السلاسل النصية]]، و<nowiki/>[[Python/bytes|البايتات]]، و<nowiki/>[[Python/bytearray|مصفوفات البايتات]]. | |||
* [[Python/tuples|الصفوف]]، و<nowiki/>[[Python/list|القوائم]]، و<nowiki/>[[Python/set|المجموعات]]، و<nowiki/>[[Python/dict|القواميس]]، التي تحتوي على كائنات قابلة للسلسلة. | |||
* [[Python/functions|الدوال]] المعرّفة في المستوى العلوي من الوحدة (باستخدام الكلمة المفتاحية <code>def</code> وليس [[Python/lambda expressions|lambda]]). | |||
* الدوال الداخلية المعرّفة في المستوى العلوي من الوحدة. | |||
* [[Python/class|الأصناف]] المعرّفة في المستوى العلوي من الوحدة. | |||
* نسخ الأصناف التي تكون فيها <code>__dict__</code> أو تكون نتيجة استدعاء التابع <code>__getstate__()</code> قابلة للسَلسَلة (راجع قسم سلسلة نسخ الأصناف للمزيد من التفاصيل). | |||
تؤدي محاولة سَلسَلة كائنات غير قابلة للسَلسَلة إلى إطلاق الاستثناء <code>PicklingError</code>، وعند حدوث ذلك يمكن أن يكون هناك بعض البايتات التي جرى كتابتها في الملف المحدّد. | |||
تؤدي كذلك محاولة سَلسَلة بيانات ذات تعاودية كبيرة جدًّا إلى درجة تتجاوز عمق التعاود المسموح به، إلى إطلاق الاستثناء <code>RecursionError</code>. يمكن زيادة عمق التعاود باستخدام الدالة <code>[[Python/sys/setrecursionlimit|sys.setrecursionlimit()]]</code> ولكن يجب توخّي الحذر عند القيام بذلك. | |||
يجب الانتباه إلى أن الدوال (سواء أكانت داخلية أم معرّفة من قبل المستخدم) تُسلسل بواسطة اسم الإشارة "المؤهّل بالكامل" لا بواسطة القيمة (لهذا السبب لا يمكن سلسلة [[Python/lambda expressions|دوال lambda]]، إذ تشترك جميع هذه الدوال بالاسم <code><lambda></code>). وهذا يعني أنّ السَلسَلة تشمل اسم الدالة فقط إضافة إلى اسم الوحدة التي جرى تعريف الدالة فيها، أما الشيفرة أو الخصائص التي تعرّفها الدالة فلا تُسلسل على الإطلاق؛ ولهذا يجب أن تكون الوحدة التي تعرّف هذه الدوال قابلة للاستيراد في البيئة غير المسلسلة، ويجب أن تحتوي الوحدة على كائنات مسماة، وإلا فإنّ اللغة ستطلق استثناءً. (تطلق اللغة على الأرجح الاستثناء <code>ImportError</code> أو <code>AttributeError</code> ولكن قد تطلق استثناءات أخرى). | |||
تُسَلسَل [[Python/class|الأصناف]] بنفس الطريقة بواسطة الإشارات المسماة؛ لذا تُفرض القيود ذاتها في البيئة غير المسلسلة. ويجب التنبيه إلى أنّ شيفرة الصنف أو بياناته لا تُسلسل إطلاقًا، ولهذا لا يمكن استرجاع الخاصية attr في المثال التالي ضمن البيئة غير المسلسلة:<syntaxhighlight lang="python3"> | |||
class Foo: | |||
attr = 'A class attribute' | |||
picklestring = pickle.dumps(Foo) | |||
</syntaxhighlight>تفرض هذه القيود تعريف الدوال الأصناف القابلة للسلسلة في المستوى العلوي من الوحدة. | |||
وبنفس الطريقة، لا تُسلسل شيفرة الصنف أو بياناته عند سلسلة نسخ الصنف، وما يتم سلسلته هو بيانات النسخة فقط، والهدف من ذلك هو إمكانية إصلاح الأخطاء في صنف معين أو إضافة توابع جديدة إليه مع إمكانية تحميل الكائنات التي جرى إنشاؤها بواسطة النسخة الأقدم من ذلك الصنف. إن كنت تخطط لاستخدام كائنات على المدى الطويل ترتبط بإصدارات متعددة من صنف معيّن، فمن الأجدر بك أن تضيف رقم إصدار إلى الكائنات ليكون بالإمكان إجراء التحويلات المناسبة بواسطة التابع <code>__setstate__()</code>. | |||
== سلسلة نسخ الأصناف == | |||
يصف هذا القسم الآلية العامة المتاحة لك لإنشاء وتخصيص نسخ الصنف والتحكم في طريقة سلسلتها وإلغاء سلسلتها. | |||
لن تكون بحاجة إلى شيفرات إضافية لإنشاء نسخ قابلة للسلسلة في معظم الحالات. فوحدة <code>pickle</code> ستسترجع افترضيًا الصنف والخصائص المرتبطة عن طريق الفحص الذاتي. عند إلغاء سلسلة نسخة صنف معيّن لا يجري تنفيذ التابع <code>__init__()</code> في العادة، ويكون السلوك الافتراضي هو إنشاء نسخة غير مهيّئة ثم استرجاع الخصائص المحفوظة. يبيّن المثال التالي تطبيقًا لهذا السلوك:<syntaxhighlight lang="python3"> | |||
def save(obj): | |||
return (obj.__class__, obj.__dict__) | |||
def load(cls, attributes): | |||
obj = cls.__new__(cls) | |||
obj.__dict__.update(attributes) | |||
return obj | |||
</syntaxhighlight>يمكن للأصناف أن تغيّر السلوك الافتراضي عن طريق تقديم تابع خاصّ أو أكثر: | |||
التابع <code>object.__getnewargs_ex__()</code> | |||
في البروتوكول رقم 2 وما بعده، يمكن للأصناف التي تطبّق التابع <code>__getnewargs_ex__()</code> أن تملي القيم الممرّرة على التابع <code>__new__()</code> خلال عملية إلغاء السلسلة. | |||
يجب أن يعيد التابع زوج (معاملات موقعية، معاملات مفتاحية) (<code>args, kwargs</code>) تكون فيه <code>args</code> [[Python/tuples|صفًّا]] من المعاملات الموقعية، و<code>kwargs</code> [[Python/dict|قاموسًا]] يتضمن معاملات مسمّاة تستخدم لبناء الكائن، وتمرر المعاملات هذه بنوعيها إلى التابع <code>__new__()</code> أثناء عملية إلغاء السلسلة. | |||
يجب تطبيق هذا التابع إن كان تابع <code>__new__()</code> يتطلّب استخدام معاملات مفتاحية فقط، أما فيما عدا ذلك فينصح بتطبيق التابع <code>__getnewargs__()</code> لضمان التوافق مع بقية إصدارات بايثون. | |||
'''ملاحظة:''' في الإصدار 3.6 من بايثون أصبح التابع __getnewargs_ex__() مستخدمًا في البروتوكولين رقم 2 ورقم 3. | |||
'''التابع <code>object.__getnewargs__()</code>''' | |||
يؤدي هذا التابع نفس الوظيفة التي يؤدّيها التابع <code>__getnewargs_ex__()</code> ولكنّه يدعم المعاملات الموقعية فقط. يجب أن يعيد التابع [[Python/tuples|صفًّا]] من المعاملات الموقعية والتي ستمرّر إلى التابع <code>__new__()</code> أثناء عملية إلغاء السلسلة. | |||
لن يُستدعى التابع <code>__getnewargs__()</code> إن كان التابع <code>__getnewargs_ex__()</code> معرّفًا. | |||
'''ملاحظة:''' قبل الإصدار 3.6 من بايثون، كان التابع <code>__getnewargs__()</code> يُستدعى عوضًا عن التابع <code>__getnewargs_ex__()</code> في البروتوكولين رقم 2 و 3. | |||
'''التابع <code>object.__getstate__()</code>''' | |||
يمكن للأصناف أن تؤثّر في طريقة المتّبعة لسلسلة نسخها، فإن عرّف الصنف التابع <code>__getstate__()</code> فإنّ التابع سيُستدعى وستجري سلسلة الكائن المعاد كمحتوى لنسخة الصنف عوضًا عن محتوى القاموس الخاصّ بنسخة الصنف. وفي حال غياب التابع <code>__getstat__()</code> تجري سَلسَلة <code>__dict__</code> الخاصّ بنسخة الصنف. | |||
'''التابع <code>object.__setstate__(state)</code>''' | |||
إنّ عرف الصنف التابع <code>__setstate__()</code> أثناء عملية إلغاء السلسلة، فإنّه سيستدعى في حالة إلغاء السلسلة، وليس من الضروري في مثل هذه الحالة أن يكون كائن <code>state</code> [[Python/dict|قاموسًا]]. أما في الحالات الأخرى فيجب أن تكون الحالة المُسلسَلة [[Python/list|قاموسًا]] تُسند عناصره إلى قاموس نسخة الصنف الجديدة. | |||
'''ملاحظة:''' | |||
إن أعاد التابع <code>__getstat__()</code> القيمة <code>False</code>، فلن يُستدعى التابع <code>__setstate__()</code> أثناء عملية إلغاء السلسلة. | |||
راجع قسم "التعامل مع الكائنات ذات الحالة" للمزيد من المعلومات حول كيفية استخدام التابعين __getstate__() و __setstate__(). | |||
'''ملاحظة:''' | |||
عند إجراء عملية إلغاء السلسلة، قد تُستدعى بعض التوابع مثل <code>__getattr__()</code> أو <code>__getattribute__()</code> أو <code>__setattr__()</code> على نسخة الصنف. وفي حال اعتماد هذه التوابع على كون بعض الثوابت الداخلية ذات قيمة صحيحة، فيجب على النوع حينئذٍ أن يطبّق التابع <code>__getnewargs__()</code> أو <code>__getnewargs_ex__()</code> لإنشاء مثل هذه الثوابت، وإلّا فلن يُستدعى التابع <code>__new__()</code> أو <code>__init__()</code>. | |||
لا تستخدم وحدة <code>pickle</code> كما سنرى لاحقًا التوابع الموصوفة أعلاه على نحو مباشر. وهذه التوابع في الواقع هي جزء من من بروتوكول النسخ الذي يطبّق التابع الخاص <code>__reduce__()</code>. | |||
يقدّم بروتوكول النسخ واجهة محدّة لاسترجاع البيانات اللازمة لسَلسَلة الكائنات ونسخها، وتستخدم وحدة <code>[[Python/list|copy]]</code> هذا البروتوكول لإجراء عمليات النسخ السطحية والعميقة. | |||
إنّ من الجيد استخدام التابع <code>__reduce__()</code> في الأصناف مباشرة ولكنّه في الوقت نفسه قد يكون سببًا في حدوث الأخطاء؛ ولهذا يجدر بمصمّمي الأصناف أنّ يستخدموا الواجهة ذات المستوى العالي (أي التوابع <code>__getnewargs_ex__()</code> و <code>__getstate__()</code> و <code>__setstate__()</code>) ما دام ذلك ممكنًا. ولكن سنعرض بعض الحالات التي يكون فيها استخدام التابع <code>__reduce__()</code> الخيار الوحيد، أو يؤدي استخدامه إلى زيادة كفاءة عملية السَلسَلة، أو الحالتين معًا. | |||
'''التابع <code>object.__reduce__()</code>''' | |||
تعمل الواجهة في الوقت الحاضر بالشكل التالي. لا يأخذ التابع <code>__reduce__()</code> أي معاملات ويجب أن يعيد إما سلسلة نصية أو صفًّا (وهو الأفضل). يُطلق على الكائن المعاد في أغلب الأحيان تسمية "قيمة الاختزال reduce value". | |||
إن كانت القيمة المعادة [[Python/str|سلسلة نصية]]، فيجب حينئذ تفسيرها كاسم لمتغير عام، والذي يجب أن يكون اسم الكائن بالنسبة إلى الوحدة التي ينتمي إليها. تبحث وحدة <code>pickle</code> في مجال أسماء الوحدة لتحديد الوحدة التي ينتمي إليه الكائن، وهذه الطريقة مفيدة في العادة مع القيم المفردة. | |||
عندما تكون القيمة المعادة صفًّا، فيجب أن يتضمن الصفّ عنصرين إلى خمسة عناصر على الأكثر. يمكن حذف العناصر الاختيارية، أو إعطائها القيمة <code>None</code>. | |||
تمتلك العناصر الخمسة في الصفّ الدلالات التالية وبحسب الترتيب: | |||
* كائن <code>callable</code> سيُستدعى لإنشاء النسخة الأوّلية من الكائن. | |||
* صفّ من المعاملات الخاصّة بكائن <code>callable</code>. يجب إعطاء صفّ فارغ إن كان كائن <code>callable</code> لا يستقبل أيّ معاملات. | |||
* عنصر اختياري، يمثّل حالة الكائن والتي ستمرّر إلى التابع <code>__setstate__()</code> الخاصّ بالكائن كما هو مبيّن سابقًا. يجب أن تكون قيمة هذا العنصر قاموسًا إن لم يمتلك الكائن مثل هذا التابع، وستضاف القيمة إلى الخاصية <code>__dict__</code> في الكائن. | |||
* عنصر اختياري، وهو مكرِّر (وليس تسلسل) ينتج عناصر متعاقبة ستُلحق بالكائن إمّا بواسطة التابع <code>obj.append(item)</code>، أو دفعة واحدة بواسطة التابع <code>obj.extend(list_of_items)</code>. يستخدم هذا العنصر بصورة أساسية مع الأصناف المتفرّعة من القوائم، ولكن يمكن استخدامه مع الأصناف الأخرى ما دامت تملك التابعين <code>append()</code> و <code>extend()</code> مع التوقيع المناسب. (يعتمد الاختيار بين التابعين <code>append()</code> و <code>extend()</code> لإضافة العناصر على رقم بروتوكول السلسلة المستخدم، وعلى عدد العناصر المضافة؛ لذا يجب توفّر التابعين في الأصناف الأخرى). | |||
* عنصر اختياري، وهو مكرّر (وليس تسلسلًا) ينتج أزواج مفتاح-قيمة متعاقبة. تُخزّن هذه العناصر في الكائن بواسطة التعبير <code>obj[key] = value</code>. يستخدم هذا العنصر بصورة اساسية مع الأصناف المتفرعة من القواميس، ولكن يمكن استخدامه مع الأصناف الأخرى ما دامت تستخدم التابع <code>__setitem__()</code>. | |||
'''التابع object.__reduce_ex__(protocol)''' | |||
يمكن استخدام التابع <code>__reduce_ex__()</code> عوضًا عن التابع <code>__reduce__()</code>، والفرق الوحيد بينهما هو أنّ الأوّل يأخذ معاملًا عدديًا واحدًا فقط، وهو رقم البروتوكول المستخدم. تستخدم الوحدة <code>pickle</code> هذا التابع في حال تعريفه مع التابع <code>__reduce__()</code>، ويصبح الأخير مرادفًا للنسخة الموسّعة من التابع. إن الاستخدام الأساسي لهذا التابع هو تقديم قيمة اختزال متوافقة مع الإصدارات القديمة من بايثون. | |||
=== استمرارية الكائنات الخارجية === | |||
تدعم وحدة <code>pickle</code> الإشارة إلى كائن خارج تدفق البيانات المسلسلة وذلك لتحقيق استمرارية الكائن object persistence. ويشار إلى مثل هذه الكائنات بمعرّف مستمر persistent ID، والذي يجب أن يكون سلسلة نصية مكوّنة من محارف حرفية-رقمية alphanumeric characters (عند استخدام البروتوكول رقم 0) أو كائن عادي (للبروتوكولات الأحدث). | |||
'''ملاحظة''': إن سبب تحديد السلسلة النصية بالمحارف الحرفية-الرقمية يعود إلى أنّ المعرّفات المستمرة في البروتوكول رقم 0 تُفصل عن بعضها البعض بواسطة محرف السطر الجديد. لذا، يؤدي وجود أيّ نوع من أنواع محارف السطر الجديد في المعرّفات المستمرة إلى جعل نتيجة السَلسَلة غير قابلة للقراءة. | |||
لا تعرّف عملية تحليل المعرّفات المستمرة بواسطة الوحدة <code>pickle</code>، بل تفوّض الوحدة هذه العملية إلى التوابع المعرّفة من قبل المستخدم في التابع <code>persistent_id()</code> في الكائن <code>Pickler</code>، والتابع <code>persistent_load()</code> في الكائن <code>Unpickler</code>. | |||
تتطلب سلسلة كائن يمتلك معرّفًا مستمرًّا خارجيًّا أن يمتلك المسلسِل تابع <code>persistent_id()</code> خاصًّا يأخذ كائنًا كمعامل ويعيد القيمة <code>None</code> أو المعرّف المستمرّ الخاصّ بذلك الكائن. إذا أعيدت القمية <code>None</code>، يُسلسل المسلسِل الكائن بصورة طبيعية. وإذا أعيدت سلسلة نصية تتضمن المعرّف المستمر فإنّ المسلسِل سيُسلسل ذلك الكائن مع علامة خاصة ليتمكّن ملغي السلسلة من تمييزه كمعرّف مستمرّ. | |||
يتطلّب إلغاء سلسلة الكائنات الخارجية أن يمتلك ملغي السلسلة تابع <code>persistent_load()</code> خاصًّا يأخذ كائن معرّف مستمر كمعامل ويعيد الكائن المشار إليه. | |||
يبين المثال التالي طريقة استخدام المعرّف المستمر لسلسلة كائنات خارجية عن طريق الإشارة:<syntaxhighlight lang="python3"> | |||
# مثال بسيط يبين طريقة استخدام المعرّف المستمر | |||
# لسلسلة كائنات خارجية عن طريق الإشارة | |||
import pickle | |||
import sqlite3 | |||
from collections import namedtuple | |||
# صنف بسيط يمثّل سجلًّا في قاعدة البيانات | |||
MemoRecord = namedtuple("MemoRecord", "key, task") | |||
class DBPickler(pickle.Pickler): | |||
def persistent_id(self, obj): | |||
# عوضًا عن سلسلة MemoRecord كنسخة صنف عادية، سننشئ معرّفًا مستمرًّا | |||
if isinstance(obj, MemoRecord): | |||
# المعرّف المستمر هنا هو صفّ يتضمن وسمًا ومفتاحًا | |||
# ويشير إلى سجلّ معيّن في قاعدة البيانات. | |||
return ("MemoRecord", obj.key) | |||
else: | |||
# إن لم يمتلك الكائن معرّفًا مستمرّاً، نعيد القيمة None. | |||
# هذا يعني أنه تجب سلسلة الكائن كما هو معتاد. | |||
return None | |||
class DBUnpickler(pickle.Unpickler): | |||
def __init__(self, file, connection): | |||
super().__init__(file) | |||
self.connection = connection | |||
def persistent_load(self, pid): | |||
# ينفّذ هذا التابع مع كلّ معرّف مستمر. | |||
# pid هنا هو الصفّ المعاد من DBPickler. | |||
cursor = self.connection.cursor() | |||
type_tag, key_id = pid | |||
if type_tag == "MemoRecord": | |||
# جلب السجلّ المشار إليه من قاعدة البيانات وإعادة قيمته. | |||
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),)) | |||
key, task = cursor.fetchone() | |||
return MemoRecord(key, task) | |||
else: | |||
# يجب دائمًا إطلاق خطأ في حال عدم القدرة على إعادة الكائن الصحيح. | |||
# وإلّا فإن ملغي السلسلة سيظنّ أن القيمة None هي الكائن المشار إليه | |||
# بواسطة المعرّف المستمر. | |||
raise pickle.UnpicklingError("unsupported persistent object") | |||
def main(): | |||
import io | |||
import pprint | |||
# تهيئة قاعدة البيانات وإضافة البيانات إليها | |||
conn = sqlite3.connect(":memory:") | |||
cursor = conn.cursor() | |||
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)") | |||
tasks = ( | |||
'give food to fish', | |||
'prepare group meeting', | |||
'fight with a zebra', | |||
) | |||
for task in tasks: | |||
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,)) | |||
# جلب السجل المراد سلسلته. | |||
cursor.execute("SELECT * FROM memos") | |||
memos = [MemoRecord(key, task) for key, task in cursor] | |||
# حفظ السجلات باستخدام الصنف DBPickler. | |||
file = io.BytesIO() | |||
DBPickler(file).dump(memos) | |||
print("Pickled records:") | |||
pprint.pprint(memos) | |||
# تحديث سجلّ للتأكد فقط من سير الأمور على ما يرام. | |||
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1") | |||
# تحميل السجلات من تدفق البيانات المسلسلة. | |||
file.seek(0) | |||
memos = DBUnpickler(file, conn).load() | |||
print("Unpickled records:") | |||
pprint.pprint(memos) | |||
if __name__ == '__main__': | |||
main() | |||
</syntaxhighlight> | |||
=== جداول الإرسال Dispatch Tables === | |||
قد تحتاج في بعض الأحيان إلى تخصيص عملية سلسلة بعض الأصناف دون المساس بالشيفرات الأخرى التي تعتمد على عملية السلسلة. يمكن في مثل هذه الحالات إنشاء كائن pickler مع جدول إرسال خاص. | |||
جدول الإرسال العامّ والذي تتحكّم به الوحدة <code>[[Python/dict|copyreg]]</code> متوفّر عن طريق الثابت <code>copyreg.dispatch_table</code>؛ لهذا يمكن استخدام نسخة معدّلة من <code>copyreg.dispatch_table</code> كجدول إرسال خاص. | |||
فعلى سبيل المثال:<syntaxhighlight lang="python3"> | |||
f = io.BytesIO() | |||
p = pickle.Pickler(f) | |||
p.dispatch_table = copyreg.dispatch_table.copy() | |||
p.dispatch_table[SomeClass] = reduce_SomeClass | |||
</syntaxhighlight>تنشئ الشيفرة السابقة نسخة من الصنف <code>pickle.Pickler</code> مع جدول إرسال خاص يتعامل مع الصنف <code>SomeClass</code> بصورة خاصة. | |||
أما الشيفرة التالية:<syntaxhighlight lang="python3"> | |||
class MyPickler(pickle.Pickler): | |||
dispatch_table = copyreg.dispatch_table.copy() | |||
dispatch_table[SomeClass] = reduce_SomeClass | |||
f = io.BytesIO() | |||
p = MyPickler(f) | |||
</syntaxhighlight>فتؤدي الوظيفة نفسها، ولكن ستتشارك جميع نسخ الصنف <code>MyPickler</code> جدول الإرسال ذاته افتراضيًا. | |||
فيما يلي الشيفرة المكافئة التي تستخدم وحدة <code>[[Python/tuples|copyreg]]</code>:<syntaxhighlight lang="python3"> | |||
copyreg.pickle(SomeClass, reduce_SomeClass) | |||
f = io.BytesIO() | |||
p = pickle.Pickler(f) | |||
</syntaxhighlight> | |||
=== التعامل مع الكائنات ذات الحالة Stateful objects === | |||
يبين المثال التالي طريقة تعديل عملية سلسلة الأصناف. يفتح الصنف <code>TextReader</code> ملفًّا نصّيًّا، ويعيد رقم السطر ومحتوى السطر عند كل استدعاء للتابع <code>readline()</code>. إن جرت سلسلة نسخة من الصنف <code>TextReader</code> فإنّ جميع الخصائص ستحفظ باستثناء كائن الملف. وعند إلغاء سلسلة نسخة الصنف، يُعاد فتح الملف، وتستمر عملية القراءة من الموقع الأخير. يُستخدم التابعان <code>__setstate__()</code> و <code>__getstate__()</code> لتطبيق هذا السلوك.<syntaxhighlight lang="python3"> | |||
class TextReader: | |||
"""Print and number lines in a text file.""" | |||
def __init__(self, filename): | |||
self.filename = filename | |||
self.file = open(filename) | |||
self.lineno = 0 | |||
def readline(self): | |||
self.lineno += 1 | |||
line = self.file.readline() | |||
if not line: | |||
return None | |||
if line.endswith('\n'): | |||
line = line[:-1] | |||
return "%i: %s" % (self.lineno, line) | |||
def __getstate__(self): | |||
# ينسخ التابع حالة الكائن من الخاصية self.__dict__ والتي تتضمن | |||
# جميع خصائص النسخة. استخدم التابع dict.copy() دائمًا | |||
# لتتجنّب تعديل الحالة الأصلية. | |||
state = self.__dict__.copy() | |||
# إزالة العناصر غير القابلة للسلسلة. | |||
del state['file'] | |||
return state | |||
def __setstate__(self, state): | |||
# استعادة خصائص النسخة (أي filename و lineno). | |||
self.__dict__.update(state) | |||
# استعادة الحالة السابقة للملف المفتوح. للقيام بذلك يجب | |||
# إعادة فتح الملف والقراءة منه إلى حين استرجاع رقم السطر. | |||
file = open(self.filename) | |||
for _ in range(self.lineno): | |||
file.readline() | |||
# وأخيرًا، حفظ الملف. | |||
self.file = file | |||
</syntaxhighlight>يمكن استخدام الصنف بالطريقة التالية:<syntaxhighlight lang="python3"> | |||
>>> reader = TextReader("hello.txt") | |||
>>> reader.readline() | |||
'1: Hello world!' | |||
>>> reader.readline() | |||
'2: I am line number two.' | |||
>>> new_reader = pickle.loads(pickle.dumps(reader)) | |||
>>> new_reader.readline() | |||
'3: Goodbye!' | |||
</syntaxhighlight> | |||
== تقييد النطاقات العامة Globals == | |||
تستورد عملية إلغاء السلسلة افتراضيًا أي صنف أو دالة تصادفها في البيانات المسلسلة. ولكن هذا السلوك غير مقبول في الكثير من التطبيقات، لأنّه يسمح باستيراد وتنفيذ أي نوع من الشيفرات. | |||
لاحظ ما يفعله تدفق البيانات المكتوب يدويًا في هذا المثال عند تحميله:<syntaxhighlight lang="python3"> | |||
>>> import pickle | |||
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.") | |||
hello world | |||
0 | |||
</syntaxhighlight>يستورد ملغي السلسلة في هذا المثال الدالة <code>[[Python/os/system|os.system()]]</code> ثم يطبّق السلسلة النصية <code>"echo hello world"</code>. على الرغم من أنّ هذا المثال لا يتضمن شيفرة مؤذية، إلاّ أنّه يمكن وبكلّ سهولة إضافة شيفرات تتسبب في إلحاق أضرار جسيمة بالنظام. | |||
ولهذا السبب يجب التحكم فيما يجري إلغاء سلسلته عن طريق تخصيص هذه العملية بواسطة التابع <code>Unpickler.find_class()</code>. وعلى عكس ما يوحي به اسم هذا التابع، فإنّه يُستدعى عند طلب أيّ شيء من النطاق العام (أي صنف أو دالة). وهكذا يصبح بالإمكان إلغاء النطاق العام تمامًا، أو تقييده بمجموعة آمنة. | |||
يقدّم المثال التالي ملغيَ سلسلة يسمح بتحميل بعض الأصناف الآمنة من الوحدة <code>[[Python/builtins|builtins]]</code>:<syntaxhighlight lang="python3"> | |||
import builtins | |||
import io | |||
import pickle | |||
safe_builtins = { | |||
'range', | |||
'complex', | |||
'set', | |||
'frozenset', | |||
'slice', | |||
} | |||
class RestrictedUnpickler(pickle.Unpickler): | |||
def find_class(self, module, name): | |||
# السماح باستيراد الأصناف الآمنة فقط. | |||
if module == "builtins" and name in safe_builtins: | |||
return getattr(builtins, name) | |||
# منع استيراد أي شيء آخر. | |||
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % | |||
(module, name)) | |||
def restricted_loads(s): | |||
"""Helper function analogous to pickle.loads().""" | |||
return RestrictedUnpickler(io.BytesIO(s)).load() | |||
</syntaxhighlight>يبين المثال التالي طريقة بسيطة لاستخدام ملغي السلسلة الذي أعددناه في المثال السابق:<syntaxhighlight lang="python3"> | |||
>>> restricted_loads(pickle.dumps([1, 2, range(15)])) | |||
[1, 2, range(0, 15)] | |||
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.") | |||
Traceback (most recent call last): | |||
... | |||
pickle.UnpicklingError: global 'os.system' is forbidden | |||
>>> restricted_loads(b'cbuiltins\neval\n' | |||
... b'(S\'getattr(__import__("os"), "system")' | |||
... b'("echo hello world")\'\ntR.') | |||
Traceback (most recent call last): | |||
... | |||
pickle.UnpicklingError: global 'builtins.eval' is forbidden | |||
</syntaxhighlight>يجب توخي الحذر في اختيار ما سيجري إلغاء سلسلته كما هو موضّح في المثال السابق. لذا إن كان مهتمًّا بالجانب الأمني فيجدر بك أن تستخدم بدائل أخرى مثل الواجهة البرمجية لعملية الترتيب marshalling API في <code>xmlrpc.client</code> أو استخدام حلول من طرف ثالث. | |||
== الأداء == | |||
تمتاز الإصدارات الحديثة من بروتوكولات السلسلة (البروتوكول رقم 2 وما بعده) بالكفاءة العالية في الترميز الثنائي للعديد من الخصائص والأنواع الداخلية الشائعة في بايثون، إلى جانب أنّ وحدة <code>pickle</code> تتضمّن محسّن أداء مكتوب بلغة C. | |||
== أمثلة == | |||
يمكن استخدم الدالتين dump() و load() في الشيفرات البسيطة:<syntaxhighlight lang="python3"> | |||
import pickle | |||
# مجموعة متنوعة من الأنواع القابلة للسلسلة | |||
data = { | |||
'a': [1, 2.0, 3, 4+6j], | |||
'b': ("character string", b"byte string"), | |||
'c': {None, True, False} | |||
} | |||
with open('data.pickle', 'wb') as f: | |||
# سلسلة القاموس 'data' باستخدام أعلى إصدار متوفّر من بروتوكولات السلسلة. | |||
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL) | |||
</syntaxhighlight>يقرأ المثال التالي البيانات الناتجة من عملية السلسلة:<syntaxhighlight lang="python3"> | |||
import pickle | |||
with open('data.pickle', 'rb') as f: | |||
# تكشف الدالة عن البروتوكول المستخدم تلقائيًا، ولا حاجة لتحديده. | |||
data = pickle.load(f) | |||
</syntaxhighlight> | |||
== انظر أيضًا == | |||
* [[Python/marshal/dumps|الوحدة <code>marshal</code> في بايثون.]] | |||
* [[Python/bytes|الوحدة <code>copyreg</code> في بايثون.]] | |||
* [[Python/shelve|الوحدة <code>shelve</code> في بايثون.]] | |||
pickle. | == مصادر == | ||
* [https://docs.python.org/3/library/pickle.html صفحة Python object serialization في توثيق بايثون الرسمي.] | |||
[[تصنيف:Python]] | |||
[[تصنيف:Python Modules]] |
المراجعة الحالية بتاريخ 19:27، 20 سبتمبر 2018
تطبّق وحدة pickle
بروتوكولات ثنائية لغرض سَلسلَة وإلغاء سَلسَلَة بنية كائنات بايثون. تطلق تسمية Pickling على العملية التي يتحوّل فيها تسلسل هرمي لكائن بايثون إلى تدفق بايتات byte stream، وتطلق تسمية Unpickling على العملية العكسية والتي يتحوّل فيها تدفّق بايتات (من ملف ثنائي أو كائن شبيه بالبايتات) إلى تسلسل هرمي لكائن بايثون.
تحمل هاتان العمليتان (Pickling و Unpickling) أسماءً أخرى مثل السَلسَلَة "serialization"، والترتيب "marshalling" (ليس المقصود هنا وحدة marshal
) والتسطيح "falttening".
سنستخدم مصطلحي السلسلة وإلغاء السلسلة في هذا التوثيق كتعريب لمصطلحي pickling و unpickling.
تحذير: وحدة pickle
غير محمية تجاه البيانات المبنيّة على نحو خاطئ أو البيانات الخبيثة؛ لذا لا تلغِ سَلسَلة أي بيانات قادمة من مصادر غير موثوقة.
العلاقة التي تربط هذه الوحدة مع وحدات بايثون الأخرى
مقارنة الوحدة مع وحدة marshal
تمتلك بايثون وحدة سلسلة أكثر بساطة من وحدة pickle
وتدعى وحدة marshal
، ولكن يفضّل استخدام وحدة pickle
بصورة عامة لإلغاء سَلسَلة كائنات بايثون، أما الهدف الرئيسي من وجود وحدة marshal
هو دعم ملفات بايثون ذات الامتداد .pyc
.
تختلف وحدة pickle
عن وحدة marshal
في عدة نقاط:
- تتابع وحدة
pickle
الكائنات التي تقوم بسلسلتها، وبهذا لن تؤدي الإشارة إلى الكائن نفسه إلى إعادة عملية السلسلة مرة أخرى. أما وحدةmarshal
لا تدعم ذلك. لهذه الميزة تطبيقات في الكائنات التعاودية recursive وفي مشاركة الكائنات. الكائنات التعاودية هي كائنات تتضمن إشارات إلى نفسها، ولا يمكن التعامل مع مثل هذه الكائنات بواسطة وحدةmarshal
، وفي الواقع يؤدي استخدام هذه الوحدة مع الكائنات التعاودية إلى انهيار مفسّر بايثون. تحدث عملية مشاركة الكائن عند وجود إشارات متعددة لنفس الكائن في مواضع مختلفة من تسلسل الكائن الذي تجري سلسلته. تخزّن وحدةpickle
مثل هذه الكائنات مرة واحدة، وتضمن أنّ جميع الإشارات الأخرى موجّهة نحو النسخة الأساسية. الكائنات المشتركة تبقى مشتركة، وهو أمر قد يكون في غاية الأهمية للكائنات القابلة للتعديل.
- لا يمكن استخدام وحدة
marshal
لسلسلة الأصناف المعرّفة من قبل المستخدم إضافة إلى النسخ التابعة لها، أما وحدةpickle
فبمقدورها أن تحفظ وتسترجع نسخ الصنف بطريقة واضحة، ولكن يجب أن يكون تعريف الصنف قابلًا للاستيراد وأن يكون موجودًا في نفس الوحدة كما هو الحال عند حفظ الكائن. لا يمكن ضمان نقل صيغة السَلسَلة المعتمدة في وحدةmarshal
بين إصدارات بايثون المختلفة، والسبب في ذلك يعود إلى أنّ المهمّة الرئيسية لهذه الوحدة هو دعم ملفات .pyc
، وما من شيء يمكنه أن يفرض على مستخدمي بايثون عدم تغيير صيغة السَلسَلة بطريقة لا تكون متوافقة مع الإصدارات السابقة. في حين يمكن ضمان توافقية صيغة السَلسَلة المعتمدة في وحدةpickle
مع الإصدارات السابقة من بايثون.
مقارنة pickle
مع json
هناك عدد من الفروقات الجوهرية بين بروتوكول pickle
وJSON (اختصار JavaScript Object Notation):
- JSON هي صيغة لسلسلة النصوص (مخرجاتها هي نصوص بترميز unicode، وترمّز لاحقًا في معظم الأحيان إلى الترميز utf-8) في حين أنّ
pickle
هي صيغة لسلسلة البيانات الثنائية. - JSON قابلة للقراءة من قبل البشر، أما pickle فلا.
- JSON واسعة الانتشار خارج بيئة بايثون، أما
pickle
فمخصّصة للغة بايثون فقط. - يمكن لـ JSON أن تمثّل -على نحو افتراضي- جزءًا من أنواع بايثون الداخلية، ولا تدعم الأصناف المخصصة، في حين أنّ بمقدور وحدة
pickle
أن تمثّل عددًا كبيرًا جدًا من أنواع بايثون (يمكن تمثيل الكثير من الأنواع تلقائيًا بواسطة بعض الأدوات التي تقدّمها بايثون، ويمكن تمثيل الحالات المعقّدة عن طريق تطبيق واجهات برمجية خاصّة ببعض الكائنات).
صيغة تدفق البيانات
إنّ صيغة البيانات التي تستخدمها وحدة pickle
خاصّة ببايثون، ومن حسنات ذلك عدم وجود أي قيود يمكن أن تفرض من مصادر خارجية مثل JSON أو XDR (والذي لا يمكنه تمثيل عملية مشاركة المؤشر)، ولكن ذلك يعني عدم قدرة البرامج المكتوبة بلغات أخرى من إعادة بناء كائنات بايثون المسَلسَلة.
تستخدم صيغة بيانات pickle
تمثيلًا ثنائيًا مضغوطًا نسبيًا، وإن كنت مهتمًا بمسألة الحجم فيمكن ضغط البيانات المسَلسَلة بكفاءة عالية.
تتضمن وحدة pickletools
أدوات لتحليل تدفق البيانات الناتجة من وحدة pickle
، وتحتوي الشيفرة المصدرية لوحدة pickletools
على تعليقات مكثفة حول شيفرات العمليات المستخدمة في بروتوكول pickle
.
هناك في الوقت الحاضر خمسة بروتوكولات متنوعة يمكن استخدامها في عملية السلسلة. وكلما كان البروتوكول المستخدم أعلى، كان إصدار بايثون المطلوب لقراءة البيانات المسلسلة الناتجة أحدث.
- البروتوكول رقم 0 هو البروتوكول الأصلي الذي يمكن قراءته من قبل البشر وهو متوافق مع النسخ الأولى من بايثون.
- البروتوكول رقم 1 هو صيغة ثنائية قديمة، وهو متوافق أيضًا مع النسخ الأولى من بايثون.
- ُقدّم الإصدار 2.3 من بايثون البروتوكول رقم 2، ويوفّر هذا البروتوكول أصنافًا ذات شكل جديد لإجراء عمليات سلسلة ذات كفاءة أعلى. يمكن الرجوع إلى PEP 307 للاطلاع على المزيد من المعلومات حول التحسينات التي أتى بها البروتوكول رقم 2.
- أضيف البروتوكول رقم 3 في الإصدار 3.0 من بايثون. يدعم هذا البروتوكول كائنات bytes صراحةً، ولا يمكن إلغاء سلسلته في إصدارات 2.x من اللغة، وهو البروتوكول الافتراضي وينصح باستخدامه عندما يكون التوافق مع إصدارات بايثون 3 الأخرى أمرًا مطلوبًا.
- أضيف البروتوكول رقم 4 في الإصدار 3.4 من بايثون، ويدعم الكائنات ذات الأحجام الكبيرة، ويُتيح سَلسَلة أنواع إضافية من الكائنات، ويقدّم بعض التحسينات على صيغة البيانات. يمكن مراجعة PEP 3154 للاطلاع على التحسينات التي أتى بها البروتوكول رقم 4.
ملاحظة: إنّ مفهوم السَلسَلة Serialization مفهوم أكثر بدائية من الاستمرارية persistence، فبالرغم من أن pickle
تقرأ كائنات الملفات وتكتب فيها، إلّا أنّها لا تعالج مسألة تسمية الكائنات المستمرة، ولا تعالج مشكلة الوصول المتزامن للكائنات المستمرة (وهي مشكلة أعقد بكثير).
يمكن لوحدة pickle
أن تحوّل كائنًا معقدًا إلى تدفق بايتات وبمقدورها أن تحول تدفق بايتات إلى كائن يحمل البنية الداخلية نفسها، ولعلّ أوضح ما يمكن فعله بتدفق البايتات هذا هو كتابته في ملف، ولكن يمكن إرساله عبر شبكة معيّنة أو تخزينه في قاعدة بيانات. تقدّم وحدة shelve واجهة بسيطة لسَلسَلة وإلغاء سلسلة الكائنات في ملف قاعدة بيانات بنمط DBM.
الواجهة البرمجية للوحدة
لا تتطلّب عملية سَلسَلة كائن معيّن سوى استدعاء الدالة dumps()
، وكذلك الأمر في عملية إلغاء سَلسَلة تدفق بيانات معيّن، إذ يكفي استدعاء الدالة loads()
. ولكن إن كنت بحاجة إلى التحكم بصورة أكبر على عملية السلسلة وإلغاء السلسلة، فيمكن إنشاء كائن Pickler
أو Unpickler
على التوالي.
تقدّم وحدة pickle
الثوابت التالية:
pickle.HIGHEST_PROTOCOL
هذا الثابت هو عدد صحيح يعرض أعلى رقم متاح للبروتوكول، ويمكن تمرير هذا الثابت كقيمة للمعامل protocol في الدالتين dump()
و dumps()
إلى جانب الدالة البانية للكائن Pickler
.
pickle.DEFAULT_PROTOCOL
هذا الثابت هو عدد صحيح يمثّل النسخة الافتراضية من البروتوكول المستخدم في عملية السلسلة، ويمكن أن تكون قيمته أقل من قيمة الثابت HIGHEST_PROTOCOL
. البروتوكول الافتراضي الحالي هو الرقم 3، وهو بروتوكول جديد صمّم للإصدار 3 من بايثون.
استثناءات الوحدة pickle
تعرّف وحدة pickle
ثلاثة استثناءات هي:
الاستثناء pickle.PickleError
صنف أساسي مشترك لجميع الاستثناءات الأخرى في عملية السلسلة. يتفرّع هذا الصنف من الصنف Exception.
الاستثناء pickle.PicklingError
يُطلَق هذا الاستثناء عند ما يُقابل المُسَلسِل Pickler
كائنٌ غير قابل للسَلسَلة. هذا الصنف متفرّع من الصنف PickleError
.
يمكن الرجوع إلى قسم "ما هي الكائنات القابلة للسلسلة؟" للتعرف على الأنواع التي يمكن سلسلتها بواسطة هذه الوحدة.
الاستثناء pickle.UnpicklingError
يُطلق هذا الاستثناء عند حدوث مشكلة في عملية إلغاء سلسلة كائن معين، كحدوث خلل في البيانات أو حصول خرق أمني. هذا الصنف متفرّع من الصنف PickleError
.
يجب الانتباه إلى أنّ هناك استثناءات أخرى قد تُطلق أثناء عملية إلغاء السلسلة، منها (على سبيل المثال وليس الحصر) الاستثناءات AttributeError
و EOFError
و ImportError
و IndexError
.
أصناف الوحدة pickle
تصدّر الوحدة pickle صنفين هما:
الصنف Pickler
يستخدم هذا الصنف ملفًّا يكتب فيه تدفق البيانات المسلسلة.
الصنف Unpickler
يستخدم هذا الصنف ملفًّا ثنائيًا يقرأ منه تدفق البيانات المسلسلة.
ما هي الكائنات القابلة للسَلسَلة؟
يمكن سَلسَلة الأنواع التالية:
- None و True و False.
- الأعداد الصحيحة، والأعداد ذات الفاصلة العائمة، والأعداد المركبة.
- السلاسل النصية، والبايتات، ومصفوفات البايتات.
- الصفوف، والقوائم، والمجموعات، والقواميس، التي تحتوي على كائنات قابلة للسلسلة.
- الدوال المعرّفة في المستوى العلوي من الوحدة (باستخدام الكلمة المفتاحية
def
وليس lambda). - الدوال الداخلية المعرّفة في المستوى العلوي من الوحدة.
- الأصناف المعرّفة في المستوى العلوي من الوحدة.
- نسخ الأصناف التي تكون فيها
__dict__
أو تكون نتيجة استدعاء التابع __getstate__()
قابلة للسَلسَلة (راجع قسم سلسلة نسخ الأصناف للمزيد من التفاصيل).
تؤدي محاولة سَلسَلة كائنات غير قابلة للسَلسَلة إلى إطلاق الاستثناء PicklingError
، وعند حدوث ذلك يمكن أن يكون هناك بعض البايتات التي جرى كتابتها في الملف المحدّد.
تؤدي كذلك محاولة سَلسَلة بيانات ذات تعاودية كبيرة جدًّا إلى درجة تتجاوز عمق التعاود المسموح به، إلى إطلاق الاستثناء RecursionError
. يمكن زيادة عمق التعاود باستخدام الدالة sys.setrecursionlimit()
ولكن يجب توخّي الحذر عند القيام بذلك.
يجب الانتباه إلى أن الدوال (سواء أكانت داخلية أم معرّفة من قبل المستخدم) تُسلسل بواسطة اسم الإشارة "المؤهّل بالكامل" لا بواسطة القيمة (لهذا السبب لا يمكن سلسلة دوال lambda، إذ تشترك جميع هذه الدوال بالاسم <lambda>
). وهذا يعني أنّ السَلسَلة تشمل اسم الدالة فقط إضافة إلى اسم الوحدة التي جرى تعريف الدالة فيها، أما الشيفرة أو الخصائص التي تعرّفها الدالة فلا تُسلسل على الإطلاق؛ ولهذا يجب أن تكون الوحدة التي تعرّف هذه الدوال قابلة للاستيراد في البيئة غير المسلسلة، ويجب أن تحتوي الوحدة على كائنات مسماة، وإلا فإنّ اللغة ستطلق استثناءً. (تطلق اللغة على الأرجح الاستثناء ImportError
أو AttributeError
ولكن قد تطلق استثناءات أخرى).
تُسَلسَل الأصناف بنفس الطريقة بواسطة الإشارات المسماة؛ لذا تُفرض القيود ذاتها في البيئة غير المسلسلة. ويجب التنبيه إلى أنّ شيفرة الصنف أو بياناته لا تُسلسل إطلاقًا، ولهذا لا يمكن استرجاع الخاصية attr في المثال التالي ضمن البيئة غير المسلسلة:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
تفرض هذه القيود تعريف الدوال الأصناف القابلة للسلسلة في المستوى العلوي من الوحدة.
وبنفس الطريقة، لا تُسلسل شيفرة الصنف أو بياناته عند سلسلة نسخ الصنف، وما يتم سلسلته هو بيانات النسخة فقط، والهدف من ذلك هو إمكانية إصلاح الأخطاء في صنف معين أو إضافة توابع جديدة إليه مع إمكانية تحميل الكائنات التي جرى إنشاؤها بواسطة النسخة الأقدم من ذلك الصنف. إن كنت تخطط لاستخدام كائنات على المدى الطويل ترتبط بإصدارات متعددة من صنف معيّن، فمن الأجدر بك أن تضيف رقم إصدار إلى الكائنات ليكون بالإمكان إجراء التحويلات المناسبة بواسطة التابع __setstate__()
.
سلسلة نسخ الأصناف
يصف هذا القسم الآلية العامة المتاحة لك لإنشاء وتخصيص نسخ الصنف والتحكم في طريقة سلسلتها وإلغاء سلسلتها.
لن تكون بحاجة إلى شيفرات إضافية لإنشاء نسخ قابلة للسلسلة في معظم الحالات. فوحدة pickle
ستسترجع افترضيًا الصنف والخصائص المرتبطة عن طريق الفحص الذاتي. عند إلغاء سلسلة نسخة صنف معيّن لا يجري تنفيذ التابع __init__()
في العادة، ويكون السلوك الافتراضي هو إنشاء نسخة غير مهيّئة ثم استرجاع الخصائص المحفوظة. يبيّن المثال التالي تطبيقًا لهذا السلوك:
def save(obj):
return (obj.__class__, obj.__dict__)
def load(cls, attributes):
obj = cls.__new__(cls)
obj.__dict__.update(attributes)
return obj
يمكن للأصناف أن تغيّر السلوك الافتراضي عن طريق تقديم تابع خاصّ أو أكثر:
التابع object.__getnewargs_ex__()
في البروتوكول رقم 2 وما بعده، يمكن للأصناف التي تطبّق التابع __getnewargs_ex__()
أن تملي القيم الممرّرة على التابع __new__()
خلال عملية إلغاء السلسلة.
يجب أن يعيد التابع زوج (معاملات موقعية، معاملات مفتاحية) (args, kwargs
) تكون فيه args
صفًّا من المعاملات الموقعية، وkwargs
قاموسًا يتضمن معاملات مسمّاة تستخدم لبناء الكائن، وتمرر المعاملات هذه بنوعيها إلى التابع __new__()
أثناء عملية إلغاء السلسلة.
يجب تطبيق هذا التابع إن كان تابع __new__()
يتطلّب استخدام معاملات مفتاحية فقط، أما فيما عدا ذلك فينصح بتطبيق التابع __getnewargs__()
لضمان التوافق مع بقية إصدارات بايثون.
ملاحظة: في الإصدار 3.6 من بايثون أصبح التابع __getnewargs_ex__() مستخدمًا في البروتوكولين رقم 2 ورقم 3.
التابع object.__getnewargs__()
يؤدي هذا التابع نفس الوظيفة التي يؤدّيها التابع __getnewargs_ex__()
ولكنّه يدعم المعاملات الموقعية فقط. يجب أن يعيد التابع صفًّا من المعاملات الموقعية والتي ستمرّر إلى التابع __new__()
أثناء عملية إلغاء السلسلة.
لن يُستدعى التابع __getnewargs__()
إن كان التابع __getnewargs_ex__()
معرّفًا.
ملاحظة: قبل الإصدار 3.6 من بايثون، كان التابع __getnewargs__()
يُستدعى عوضًا عن التابع __getnewargs_ex__()
في البروتوكولين رقم 2 و 3.
التابع object.__getstate__()
يمكن للأصناف أن تؤثّر في طريقة المتّبعة لسلسلة نسخها، فإن عرّف الصنف التابع __getstate__()
فإنّ التابع سيُستدعى وستجري سلسلة الكائن المعاد كمحتوى لنسخة الصنف عوضًا عن محتوى القاموس الخاصّ بنسخة الصنف. وفي حال غياب التابع __getstat__()
تجري سَلسَلة __dict__
الخاصّ بنسخة الصنف.
التابع object.__setstate__(state)
إنّ عرف الصنف التابع __setstate__()
أثناء عملية إلغاء السلسلة، فإنّه سيستدعى في حالة إلغاء السلسلة، وليس من الضروري في مثل هذه الحالة أن يكون كائن state
قاموسًا. أما في الحالات الأخرى فيجب أن تكون الحالة المُسلسَلة قاموسًا تُسند عناصره إلى قاموس نسخة الصنف الجديدة.
ملاحظة:
إن أعاد التابع __getstat__()
القيمة False
، فلن يُستدعى التابع __setstate__()
أثناء عملية إلغاء السلسلة.
راجع قسم "التعامل مع الكائنات ذات الحالة" للمزيد من المعلومات حول كيفية استخدام التابعين __getstate__() و __setstate__().
ملاحظة:
عند إجراء عملية إلغاء السلسلة، قد تُستدعى بعض التوابع مثل __getattr__()
أو __getattribute__()
أو __setattr__()
على نسخة الصنف. وفي حال اعتماد هذه التوابع على كون بعض الثوابت الداخلية ذات قيمة صحيحة، فيجب على النوع حينئذٍ أن يطبّق التابع __getnewargs__()
أو __getnewargs_ex__()
لإنشاء مثل هذه الثوابت، وإلّا فلن يُستدعى التابع __new__()
أو __init__()
.
لا تستخدم وحدة pickle
كما سنرى لاحقًا التوابع الموصوفة أعلاه على نحو مباشر. وهذه التوابع في الواقع هي جزء من من بروتوكول النسخ الذي يطبّق التابع الخاص __reduce__()
.
يقدّم بروتوكول النسخ واجهة محدّة لاسترجاع البيانات اللازمة لسَلسَلة الكائنات ونسخها، وتستخدم وحدة copy
هذا البروتوكول لإجراء عمليات النسخ السطحية والعميقة.
إنّ من الجيد استخدام التابع __reduce__()
في الأصناف مباشرة ولكنّه في الوقت نفسه قد يكون سببًا في حدوث الأخطاء؛ ولهذا يجدر بمصمّمي الأصناف أنّ يستخدموا الواجهة ذات المستوى العالي (أي التوابع __getnewargs_ex__()
و __getstate__()
و __setstate__()
) ما دام ذلك ممكنًا. ولكن سنعرض بعض الحالات التي يكون فيها استخدام التابع __reduce__()
الخيار الوحيد، أو يؤدي استخدامه إلى زيادة كفاءة عملية السَلسَلة، أو الحالتين معًا.
التابع object.__reduce__()
تعمل الواجهة في الوقت الحاضر بالشكل التالي. لا يأخذ التابع __reduce__()
أي معاملات ويجب أن يعيد إما سلسلة نصية أو صفًّا (وهو الأفضل). يُطلق على الكائن المعاد في أغلب الأحيان تسمية "قيمة الاختزال reduce value".
إن كانت القيمة المعادة سلسلة نصية، فيجب حينئذ تفسيرها كاسم لمتغير عام، والذي يجب أن يكون اسم الكائن بالنسبة إلى الوحدة التي ينتمي إليها. تبحث وحدة pickle
في مجال أسماء الوحدة لتحديد الوحدة التي ينتمي إليه الكائن، وهذه الطريقة مفيدة في العادة مع القيم المفردة.
عندما تكون القيمة المعادة صفًّا، فيجب أن يتضمن الصفّ عنصرين إلى خمسة عناصر على الأكثر. يمكن حذف العناصر الاختيارية، أو إعطائها القيمة None
.
تمتلك العناصر الخمسة في الصفّ الدلالات التالية وبحسب الترتيب:
- كائن
callable
سيُستدعى لإنشاء النسخة الأوّلية من الكائن. - صفّ من المعاملات الخاصّة بكائن
callable
. يجب إعطاء صفّ فارغ إن كان كائنcallable
لا يستقبل أيّ معاملات. - عنصر اختياري، يمثّل حالة الكائن والتي ستمرّر إلى التابع
__setstate__()
الخاصّ بالكائن كما هو مبيّن سابقًا. يجب أن تكون قيمة هذا العنصر قاموسًا إن لم يمتلك الكائن مثل هذا التابع، وستضاف القيمة إلى الخاصية __dict__
في الكائن. - عنصر اختياري، وهو مكرِّر (وليس تسلسل) ينتج عناصر متعاقبة ستُلحق بالكائن إمّا بواسطة التابع
obj.append(item)
، أو دفعة واحدة بواسطة التابعobj.extend(list_of_items)
. يستخدم هذا العنصر بصورة أساسية مع الأصناف المتفرّعة من القوائم، ولكن يمكن استخدامه مع الأصناف الأخرى ما دامت تملك التابعينappend()
وextend()
مع التوقيع المناسب. (يعتمد الاختيار بين التابعينappend()
وextend()
لإضافة العناصر على رقم بروتوكول السلسلة المستخدم، وعلى عدد العناصر المضافة؛ لذا يجب توفّر التابعين في الأصناف الأخرى). - عنصر اختياري، وهو مكرّر (وليس تسلسلًا) ينتج أزواج مفتاح-قيمة متعاقبة. تُخزّن هذه العناصر في الكائن بواسطة التعبير
obj[key] = value
. يستخدم هذا العنصر بصورة اساسية مع الأصناف المتفرعة من القواميس، ولكن يمكن استخدامه مع الأصناف الأخرى ما دامت تستخدم التابع __setitem__()
.
التابع object.__reduce_ex__(protocol)
يمكن استخدام التابع __reduce_ex__()
عوضًا عن التابع __reduce__()
، والفرق الوحيد بينهما هو أنّ الأوّل يأخذ معاملًا عدديًا واحدًا فقط، وهو رقم البروتوكول المستخدم. تستخدم الوحدة pickle
هذا التابع في حال تعريفه مع التابع __reduce__()
، ويصبح الأخير مرادفًا للنسخة الموسّعة من التابع. إن الاستخدام الأساسي لهذا التابع هو تقديم قيمة اختزال متوافقة مع الإصدارات القديمة من بايثون.
استمرارية الكائنات الخارجية
تدعم وحدة pickle
الإشارة إلى كائن خارج تدفق البيانات المسلسلة وذلك لتحقيق استمرارية الكائن object persistence. ويشار إلى مثل هذه الكائنات بمعرّف مستمر persistent ID، والذي يجب أن يكون سلسلة نصية مكوّنة من محارف حرفية-رقمية alphanumeric characters (عند استخدام البروتوكول رقم 0) أو كائن عادي (للبروتوكولات الأحدث).
ملاحظة: إن سبب تحديد السلسلة النصية بالمحارف الحرفية-الرقمية يعود إلى أنّ المعرّفات المستمرة في البروتوكول رقم 0 تُفصل عن بعضها البعض بواسطة محرف السطر الجديد. لذا، يؤدي وجود أيّ نوع من أنواع محارف السطر الجديد في المعرّفات المستمرة إلى جعل نتيجة السَلسَلة غير قابلة للقراءة.
لا تعرّف عملية تحليل المعرّفات المستمرة بواسطة الوحدة pickle
، بل تفوّض الوحدة هذه العملية إلى التوابع المعرّفة من قبل المستخدم في التابع persistent_id()
في الكائن Pickler
، والتابع persistent_load()
في الكائن Unpickler
.
تتطلب سلسلة كائن يمتلك معرّفًا مستمرًّا خارجيًّا أن يمتلك المسلسِل تابع persistent_id()
خاصًّا يأخذ كائنًا كمعامل ويعيد القيمة None
أو المعرّف المستمرّ الخاصّ بذلك الكائن. إذا أعيدت القمية None
، يُسلسل المسلسِل الكائن بصورة طبيعية. وإذا أعيدت سلسلة نصية تتضمن المعرّف المستمر فإنّ المسلسِل سيُسلسل ذلك الكائن مع علامة خاصة ليتمكّن ملغي السلسلة من تمييزه كمعرّف مستمرّ.
يتطلّب إلغاء سلسلة الكائنات الخارجية أن يمتلك ملغي السلسلة تابع persistent_load()
خاصًّا يأخذ كائن معرّف مستمر كمعامل ويعيد الكائن المشار إليه.
يبين المثال التالي طريقة استخدام المعرّف المستمر لسلسلة كائنات خارجية عن طريق الإشارة:
# مثال بسيط يبين طريقة استخدام المعرّف المستمر
# لسلسلة كائنات خارجية عن طريق الإشارة
import pickle
import sqlite3
from collections import namedtuple
# صنف بسيط يمثّل سجلًّا في قاعدة البيانات
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# عوضًا عن سلسلة MemoRecord كنسخة صنف عادية، سننشئ معرّفًا مستمرًّا
if isinstance(obj, MemoRecord):
# المعرّف المستمر هنا هو صفّ يتضمن وسمًا ومفتاحًا
# ويشير إلى سجلّ معيّن في قاعدة البيانات.
return ("MemoRecord", obj.key)
else:
# إن لم يمتلك الكائن معرّفًا مستمرّاً، نعيد القيمة None.
# هذا يعني أنه تجب سلسلة الكائن كما هو معتاد.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# ينفّذ هذا التابع مع كلّ معرّف مستمر.
# pid هنا هو الصفّ المعاد من DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# جلب السجلّ المشار إليه من قاعدة البيانات وإعادة قيمته.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# يجب دائمًا إطلاق خطأ في حال عدم القدرة على إعادة الكائن الصحيح.
# وإلّا فإن ملغي السلسلة سيظنّ أن القيمة None هي الكائن المشار إليه
# بواسطة المعرّف المستمر.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# تهيئة قاعدة البيانات وإضافة البيانات إليها
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# جلب السجل المراد سلسلته.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# حفظ السجلات باستخدام الصنف DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# تحديث سجلّ للتأكد فقط من سير الأمور على ما يرام.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# تحميل السجلات من تدفق البيانات المسلسلة.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
جداول الإرسال Dispatch Tables
قد تحتاج في بعض الأحيان إلى تخصيص عملية سلسلة بعض الأصناف دون المساس بالشيفرات الأخرى التي تعتمد على عملية السلسلة. يمكن في مثل هذه الحالات إنشاء كائن pickler مع جدول إرسال خاص.
جدول الإرسال العامّ والذي تتحكّم به الوحدة copyreg
متوفّر عن طريق الثابت copyreg.dispatch_table
؛ لهذا يمكن استخدام نسخة معدّلة من copyreg.dispatch_table
كجدول إرسال خاص.
فعلى سبيل المثال:
f = io.BytesIO()
p = pickle.Pickler(f)
p.dispatch_table = copyreg.dispatch_table.copy()
p.dispatch_table[SomeClass] = reduce_SomeClass
تنشئ الشيفرة السابقة نسخة من الصنف pickle.Pickler
مع جدول إرسال خاص يتعامل مع الصنف SomeClass
بصورة خاصة.
أما الشيفرة التالية:
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
فتؤدي الوظيفة نفسها، ولكن ستتشارك جميع نسخ الصنف MyPickler
جدول الإرسال ذاته افتراضيًا.
فيما يلي الشيفرة المكافئة التي تستخدم وحدة copyreg
:
copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)
التعامل مع الكائنات ذات الحالة Stateful objects
يبين المثال التالي طريقة تعديل عملية سلسلة الأصناف. يفتح الصنف TextReader
ملفًّا نصّيًّا، ويعيد رقم السطر ومحتوى السطر عند كل استدعاء للتابع readline()
. إن جرت سلسلة نسخة من الصنف TextReader
فإنّ جميع الخصائص ستحفظ باستثناء كائن الملف. وعند إلغاء سلسلة نسخة الصنف، يُعاد فتح الملف، وتستمر عملية القراءة من الموقع الأخير. يُستخدم التابعان __setstate__()
و __getstate__()
لتطبيق هذا السلوك.
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# ينسخ التابع حالة الكائن من الخاصية self.__dict__ والتي تتضمن
# جميع خصائص النسخة. استخدم التابع dict.copy() دائمًا
# لتتجنّب تعديل الحالة الأصلية.
state = self.__dict__.copy()
# إزالة العناصر غير القابلة للسلسلة.
del state['file']
return state
def __setstate__(self, state):
# استعادة خصائص النسخة (أي filename و lineno).
self.__dict__.update(state)
# استعادة الحالة السابقة للملف المفتوح. للقيام بذلك يجب
# إعادة فتح الملف والقراءة منه إلى حين استرجاع رقم السطر.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# وأخيرًا، حفظ الملف.
self.file = file
يمكن استخدام الصنف بالطريقة التالية:
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
تقييد النطاقات العامة Globals
تستورد عملية إلغاء السلسلة افتراضيًا أي صنف أو دالة تصادفها في البيانات المسلسلة. ولكن هذا السلوك غير مقبول في الكثير من التطبيقات، لأنّه يسمح باستيراد وتنفيذ أي نوع من الشيفرات.
لاحظ ما يفعله تدفق البيانات المكتوب يدويًا في هذا المثال عند تحميله:
>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0
يستورد ملغي السلسلة في هذا المثال الدالة os.system()
ثم يطبّق السلسلة النصية "echo hello world"
. على الرغم من أنّ هذا المثال لا يتضمن شيفرة مؤذية، إلاّ أنّه يمكن وبكلّ سهولة إضافة شيفرات تتسبب في إلحاق أضرار جسيمة بالنظام.
ولهذا السبب يجب التحكم فيما يجري إلغاء سلسلته عن طريق تخصيص هذه العملية بواسطة التابع Unpickler.find_class()
. وعلى عكس ما يوحي به اسم هذا التابع، فإنّه يُستدعى عند طلب أيّ شيء من النطاق العام (أي صنف أو دالة). وهكذا يصبح بالإمكان إلغاء النطاق العام تمامًا، أو تقييده بمجموعة آمنة.
يقدّم المثال التالي ملغيَ سلسلة يسمح بتحميل بعض الأصناف الآمنة من الوحدة builtins
:
import builtins
import io
import pickle
safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
# السماح باستيراد الأصناف الآمنة فقط.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# منع استيراد أي شيء آخر.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
يبين المثال التالي طريقة بسيطة لاستخدام ملغي السلسلة الذي أعددناه في المثال السابق:
>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
... b'(S\'getattr(__import__("os"), "system")'
... b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'builtins.eval' is forbidden
يجب توخي الحذر في اختيار ما سيجري إلغاء سلسلته كما هو موضّح في المثال السابق. لذا إن كان مهتمًّا بالجانب الأمني فيجدر بك أن تستخدم بدائل أخرى مثل الواجهة البرمجية لعملية الترتيب marshalling API في xmlrpc.client
أو استخدام حلول من طرف ثالث.
الأداء
تمتاز الإصدارات الحديثة من بروتوكولات السلسلة (البروتوكول رقم 2 وما بعده) بالكفاءة العالية في الترميز الثنائي للعديد من الخصائص والأنواع الداخلية الشائعة في بايثون، إلى جانب أنّ وحدة pickle
تتضمّن محسّن أداء مكتوب بلغة C.
أمثلة
يمكن استخدم الدالتين dump() و load() في الشيفرات البسيطة:
import pickle
# مجموعة متنوعة من الأنواع القابلة للسلسلة
data = {
'a': [1, 2.0, 3, 4+6j],
'b': ("character string", b"byte string"),
'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:
# سلسلة القاموس 'data' باستخدام أعلى إصدار متوفّر من بروتوكولات السلسلة.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
يقرأ المثال التالي البيانات الناتجة من عملية السلسلة:
import pickle
with open('data.pickle', 'rb') as f:
# تكشف الدالة عن البروتوكول المستخدم تلقائيًا، ولا حاجة لتحديده.
data = pickle.load(f)