pickle --- سریالسازی اشیای پایتون¶
کد منبع: Lib/pickle.py
ماژول pickle پروتکلهای دودویی را برای سریالسازی (serializing) و سریالزدایی (de-serializing) ساختار شیء پایتون پیادهسازی میکند. «پیکلکردن» فرآیندی است که طی آن سلسلهمراتب اشیای پایتون به یک جریان بایت تبدیل میشود، و «پیکلگشایی» عملیات معکوسی است که طی آن یک جریان بایت (از یک پرونده دودویی یا شیء شبهبایت) دوباره به سلسلهمراتب شیءها تبدیل میشود. پیکلکردن (و آنپیکلکردن) همچنین با نامهای «سریالسازی (serialization)»، «مارشالینگ »، [1] یا «تختسازی (flattening)» نیز شناخته میشود؛ با این حال، برای جلوگیری از سردرگمی، اصطلاحات استفادهشده در اینجا «پیکلکردن» و «پیکلگشایی» هستند.
هشدار
ماژول pickle امن نیست. فقط دادههای مورد اعتماد را را پیکلگشایی کنید (unpickle).
میتوان دادههای مخرب پیکل را بهگونهای ساخت که در حین بازگشایی پیکل کد دلخواه را اجرا میکنند. هرگز به بازگشایی پیکل دادههایی که ممکن است از منبعی غیرقابلاعتماد آمده باشند یا دستکاری شده باشند، اقدام نکنید.
اگر نیاز دارید اطمینان حاصل کنید که داده دستکاری نشده است، امضا کردن داده با hmac را در نظر بگیرید.
اگر دادههای غیرقابلاعتماد را پردازش میکنید، قالبهای سریالسازی ایمنتر مانند json ممکن است مناسبتر باشند. مقایسه با json را ببینید.
ارتباط با سایر ماژولهای پایتون¶
مقایسه با marshal¶
پایتون یک ماژول سریالسازی ابتداییتر به نام marshal دارد، اما بهطور کلی pickle باید همیشه روش ترجیحی برای سریالسازی اشیای پایتون باشد. marshal عمدتاً برای پشتیبانی از پروندههای .pyc پایتون وجود دارد.
ماژول pickle در چند مورد مهم با marshal تفاوت دارد:
از
marshalنمیتوان برای سریالسازی کلاسهای تعریفشده توسط کاربر و نمونههای آنها استفاده کرد.pickleمیتواند نمونههای کلاس را بهصورت شفاف ذخیره و بازیابی کند، با این حال تعریف کلاس باید قابل ایمپورت باشد و در همان ماژولی قرار داشته باشد که شیء در زمان پیکلکردن در آن قرار داشت.تضمینی وجود ندارد که قالب سریالسازی
marshalدر بین نسخههای پایتون قابل حمل باشد. از آنجا که وظیفهی اصلی آن پشتیبانی از پروندههای.pycاست، پیادهسازیکنندگان پایتون این حق را برای خود محفوظ میدارند که در صورت نیاز، قالب سریالسازی را به شکلهایی ناسازگار با عقبگرد تغییر دهند. سازگاری قالب سریالسازیpickleبا عقبگرد در بین انتشارهای پایتون تضمین شده است، مشروط بر اینکه پروتکل pickle سازگاری انتخاب شود و کد پیکلکردن و پیکلگشایی، در صورتی که دادههای شما از آن مرز زبانی تغییر ناسازگار منحصربهفرد عبور میکنند، تفاوتهای نوع بین پایتون 2 و پایتون 3 را مدیریت کند.
مقایسه با json¶
تفاوتهای بنیادینی بین پروتکلهای pickle و JSON (JavaScript Object Notation) وجود دارد:
JSON یک قالب سریالسازی متنی است (خروجی آن متن یونیکد است، اگرچه بیشتر اوقات سپس به
utf-8کدگذاری میشود)، در حالی که pickle یک قالب سریالسازی دودویی است؛JSON برای انسان قابلخواندن است، در حالی که pickle اینگونه نیست؛
JSON تعاملپذیر است و بهطور گسترده خارج از اکوسیستم پایتون استفاده میشود، در حالی که pickle مختص پایتون است؛
JSON، بهطور پیشفرض، فقط میتواند زیرمجموعهای از انواع توکار پایتون را بازنمایی کند و نمیتواند هیچ کلاس سفارشی را بازنمایی کند؛ pickle میتواند تعداد بسیار زیادی از انواع پایتون را بازنمایی کند (بسیاری از آنها بهصورت خودکار، با استفاده هوشمندانه از امکانات دروننگری پایتون بازنمایی میشوند؛ موارد پیچیده را میتوان با پیادهسازی APIهای خاص اشیاء حل کرد)؛
برخلاف pickle، سریالزدای JSON غیرقابلاعتماد بهخودیخود آسیبپذیری اجرای کد دلخواه را ایجاد نمیکند.
همچنین ملاحظه نمائید
ماژول json: ماژولی از کتابخانه استاندارد که امکان سریالسازی و سریالزدایی (deserialization) JSON را فراهم میکند.
قالب جریان داده¶
قالب دادهای که توسط pickle استفاده میشود، مختص پایتون است. این امر این مزیت را دارد که هیچ محدودیتی از سوی استانداردهای خارجی مانند JSON (که نمیتواند اشتراکگذاری اشارهگر را بازنمایی کند) اعمال نمیشود؛ با این حال، این بدان معناست که برنامههای غیرپایتونی ممکن است نتوانند اشیای پایتونی pickleشده را بازسازی کنند.
بهطور پیشفرض، قالب دادهی pickle از یک نمایش دودویی نسبتاً فشرده استفاده میکند. اگر به ویژگیهای اندازهی بهینه نیاز دارید، میتوانید دادههای pickleشده را بهطور کارآمد فشردهسازی کنید.
ماژول pickletools شامل ابزارهایی برای تحلیل جریانهای داده تولیدشده توسط pickle است. کد منبع pickletools دارای کامنتهای گستردهای درباره آپکدهای (opcodes) استفادهشده در پروتکلهای pickle است.
در حال حاضر ۶ پروتکل متفاوت وجود دارد که میتوان از آنها برای پیکلکردن استفاده کرد. هرچه پروتکل مورد استفاده بالاتر باشد، برای خواندن پیکل تولیدشده به نسخهی جدیدتری از پایتون نیاز است.
پروتکل نسخهی 0، پروتکل اصلی «قابل خواندن برای انسان» است و با نسخههای پیشین پایتون سازگار است.
پروتکل نسخهی 1 یک قالب دودویی قدیمی است که با نسخههای پیشین پایتون نیز سازگار است.
نسخهی ۲ پروتکل در پایتون 2.3 معرفی شد. این نسخه، امکان پیکلکردن بسیار کارآمدتری را برای کلاسهای جدید فراهم میکند. برای اطلاعات دربارهی بهبودهای پروتکل ۲، به PEP 307 مراجعه کنید.
نسخهی 3 پروتکل در Python 3.0 افزوده شد. این پروتکل بهطور صریح از اشیای
bytesپشتیبانی میکند و نمیتوان آن را با Python 2.x پیکلگشایی کرد (unpickle). این پروتکل، پروتکل پیشفرض در Python 3.0--3.7 بود.نسخهی 4 پروتکل در پایتون 3.4 افزوده شد. این نسخه پشتیبانی از اشیاء بسیار بزرگ، پیکلکردن انواع بیشتری از اشیاء، و برخی بهینهسازیهای قالب داده را اضافه میکند. این پروتکل، پروتکل پیشفرض در پایتون 3.8--3.13 بود. برای اطلاعات دربارهی بهبودهای ارائهشده توسط پروتکل 4، به PEP 3154 مراجعه کنید.
نسخهی 5 پروتکل در پایتون 3.8 افزوده شد. این نسخه پشتیبانی از دادههای خارج از باند و سرعتبخشی به دادههای درونباند را اضافه میکند. از پایتون 3.14 به بعد، این پروتکل پیشفرض است. برای اطلاعات دربارهی بهبودهای پروتکل 5، به PEP 574 مراجعه کنید.
توجه
سریالسازی مفهومی ابتداییتر از ماندگاری است؛ اگرچه pickle اشیاء پرونده را میخواند و مینویسد، اما نه به مسئلهی نامگذاری اشیاء ماندگار رسیدگی میکند و نه به مسئلهی دسترسی همزمان به اشیاء ماندگار (که حتی پیچیدهتر است). ماژول pickle میتواند یک شیء پیچیده را به یک جریان بایت تبدیل کند و میتواند جریان بایت را به شیءای با همان ساختار داخلی تبدیل کند. شاید واضحترین کاری که بتوان با این جریانهای بایت انجام داد، نوشتن آنها در یک پرونده باشد، اما همچنین قابل تصور است که آنها را از طریق یک شبکه ارسال کرد یا در یک پایگاه داده ذخیره کرد. ماژول shelve رابط سادهای برای pickle و unpickle کردن اشیاء در پروندههای پایگاه داده به سبک DBM فراهم میکند.
رابط ماژول¶
برای سریالسازی سلسلهمراتبی از شیءها، کافی است تابع dumps() را فراخوانی کنید. بهطور مشابه، برای سریالزدایی یک جریان داده، تابع loads() را فراخوانی میکنید. با این حال، اگر کنترل بیشتری بر سریالسازی و سریالزدایی میخواهید، میتوانید بهترتیب یک شیء Pickler یا Unpickler ایجاد کنید.
ماژول pickle ثابتهای زیر را ارائه میدهد:
- pickle.HIGHEST_PROTOCOL¶
یک عدد صحیح، بالاترین نسخهی پروتکل موجود. این مقدار را میتوان بهعنوان مقدار protocol به توابع
dump()وdumps()و همچنین به سازندهیPicklerارسال کرد.
- pickle.DEFAULT_PROTOCOL¶
یک عدد صحیح، نسخهی پروتکل پیشفرض که برای پیکلکردن استفاده میشود. ممکن است کمتر از
HIGHEST_PROTOCOLباشد. در حال حاضر پروتکل پیشفرض ۵ است، که در پایتون 3.8 معرفی شد و با نسخههای پیشین ناسازگار است. این نسخه پشتیبانی از بافرهای خارج از باند (out-of-band buffers) را معرفی میکند، که در آن دادههای سازگار با PEP 3118 میتوانند جدا از جریان اصلی پیکل (pickle stream) منتقل شوند.تغییر یافته در نسخهی 3.0: پروتکل پیشفرض ۳ است.
تغییر یافته در نسخهی 3.8: پروتکل پیشفرض ۴ است.
تغییر یافته در نسخهی 3.14: پروتکل پیشفرض ۵ است.
ماژول pickle توابع زیر را برای راحتتر کردن فرایند پیکلکردن فراهم میکند:
- pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)¶
بازنمایی پیکلشدهی شیء obj را در file object باز file بنویسید. این معادل
Pickler(file, protocol).dump(obj)است.آرگومانهای file، protocol، fix_imports و buffer_callback همان معنای موجود در سازندهی
Picklerرا دارند.تغییر یافته در نسخهی 3.8: آرگومان buffer_callback افزوده شد.
- pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)¶
نمایش pickleشدهی شیء obj را بهجای نوشتن در پرونده، بهصورت یک شیء
bytesبرمیگرداند.آرگومانهای protocol، fix_imports و buffer_callback همان معنایی را دارند که در سازندهی
Picklerدارند.تغییر یافته در نسخهی 3.8: آرگومان buffer_callback افزوده شد.
- pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
نمایش پیکلشدهی یک شیء را از file object باز file میخواند و سلسلهمراتب اشیاء بازسازیشدهای که در آن مشخص شده است را برمیگرداند. این معادل
Unpickler(file).load()است.نسخهی پروتکلِ pickle بهطور خودکار تشخیص داده میشود، بنابراین نیازی به آرگومان protocol نیست. بایتهای پس از نمایش pickleشدهی شیء نادیده گرفته میشوند.
آرگومانهای file، fix_imports، encoding، errors، strict و buffers همان معنایی را دارند که در سازندهی
Unpicklerدارند.تغییر یافته در نسخهی 3.8: آرگومان buffers اضافه شد.
- pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
سلسلهمراتب اشیاء بازسازیشده از نمایش پیکلشدهی data از یک شیء را برمیگرداند. data باید یک شیء شبهبایت باشد.
نسخهی پروتکلِ pickle بهطور خودکار تشخیص داده میشود، بنابراین نیازی به آرگومان protocol نیست. بایتهای پس از نمایش pickleشدهی شیء نادیده گرفته میشوند.
آرگومانهای fix_imports، encoding، errors، strict و buffers همان معنایی را دارند که در سازندهی
Unpicklerدارند.تغییر یافته در نسخهی 3.8: آرگومان buffers اضافه شد.
ماژول pickle سه استثنا را تعریف میکند:
- exception pickle.PickleError¶
کلاس پایه مشترک برای سایر استثناهای پیکلکردن . این کلاس از
Exceptionارثبری میکند.
- exception pickle.PicklingError¶
خطایی که هنگام مواجههی
Picklerبا یک شیء غیرقابل پیکل (unpicklable) پرتاب میشود. این خطا ازPickleErrorارثبری میکند.برای یادگیری اینکه چه انواعی از اشیاء میتوانند پیکل شوند، به چه چیزهایی را میتوان pickle و unpickle کرد؟ مراجعه کنید.
- exception pickle.UnpicklingError¶
خطایی که هنگام بروز مشکلی در پیکلگشایی یک شیء، مانند خرابی داده یا نقض امنیت، پرتاب میشود. این خطا از
PickleErrorارث میبرد.توجه داشته باشید که در حین پیکلگشایی ممکن است استثناهای دیگری نیز پرتاب شوند، از جمله (اما نه لزوماً محدود به) AttributeError، EOFError، ImportError و IndexError.
ماژول pickle سه کلاس Pickler، Unpickler و PickleBuffer را اکسپورت میکند:
- class pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None)¶
این یک پرونده دودویی برای نوشتن جریان دادهی pickle میپذیرد.
آرگومان اختیاری protocol، یک عدد صحیح، به پیکلساز (pickler) میگوید که از پروتکل دادهشده استفاده کند؛ پروتکلهای پشتیبانیشده از ۰ تا
HIGHEST_PROTOCOLهستند. اگر مشخص نشده باشد، پیشفرضDEFAULT_PROTOCOLاست. اگر یک عدد منفی مشخص شده باشد،HIGHEST_PROTOCOLانتخاب میشود.آرگومان file باید دارای متد write() باشد که یک آرگومان واحد از نوع bytes را بپذیرد. بنابراین میتواند یک پرونده روی دیسک باشد که برای نوشتن دودویی باز شده است، یک نمونه
io.BytesIO، یا هر شیء سفارشی دیگری که این رابط را برآورده میکند.اگر fix_imports درست باشد و protocol کمتر از ۳ باشد، pickle تلاش میکند نامهای جدید Python 3 را به نامهای قدیمی ماژول که در Python 2 استفاده میشدند نگاشت کند، تا جریان دادهی pickle با Python 2 قابل خواندن باشد.
اگر buffer_callback برابر
Noneباشد (پیشفرض)، نماهای بافر (buffer views) بهعنوان بخشی از جریان pickle در file سریالسازی میشوند.اگر buffer_callback برابر
Noneنباشد، میتوان آن را هر تعداد بار با یک نمای بافر فراخوانی کرد. اگر کالبک یک مقدار نادرست (مانندNone) برگرداند، بافر دادهشده خارج از باند است؛ در غیر این صورت بافر بهصورت درونباند سریالسازی میشود، یعنی داخل جریان pickle.اگر buffer_callback
Noneنباشد و protocolNoneیا کوچکتر از ۵ باشد، خطا است.تغییر یافته در نسخهی 3.8: آرگومان buffer_callback افزوده شد.
- dump(obj)¶
نمایش پیکلشده (pickled representation) از obj را در شیء پرونده بازی که در سازنده دادهشده است، بنویسید.
- persistent_id(obj)¶
بهطور پیشفرض هیچ کاری انجام نمیدهد. این برای آن وجود دارد که یک زیرکلاس بتواند آن را بازنویسی کند.
اگر
persistent_id()مقدارNoneرا برگرداند، obj بهطور معمول پیکل میشود. هر مقدار دیگری موجب میشودPicklerمقدار برگرداندهشده را بهعنوان یک شناسه پایا (persistent ID) برای obj نشان میدهد. معنای این شناسه پایا باید بهوسیلهیUnpickler.persistent_load()تعریف شود. توجه داشته باشید که مقدار برگرداندهشده توسطpersistent_id()خود نمیتواند یک شناسه پایا داشته باشد.برای جزئیات و نمونههای کاربرد، ماندگاری اشیاء خارجی را ببینید.
تغییر یافته در نسخهی 3.13: پیادهسازی پیشفرض این متد را در پیادهسازی C برای
Picklerاضافه کنید.
- dispatch_table¶
جدول نگاشت (dispatch table) یک شیء pickler، یک رجیستری از توابع کاهش (reduction functions) است؛ از نوعی که میتوان آنها را با استفاده از
copyreg.pickle()اعلام کرد. این یک نگاشت است که کلیدهای آن کلاسها و مقدارهای آن توابع کاهش هستند. یک تابع کاهش تنها یک آرگومان از کلاس مرتبط میگیرد و باید با همان رابط متد__reduce__()مطابقت داشته باشد.بهطور پیشفرض، یک شیء pickler دارای ویژگی
dispatch_tableنخواهد بود، و در عوض از جدول dispatch سراسری مدیریتشده توسط ماژولcopyregاستفاده میکند. با این حال، برای سفارشیسازی pickling برای یک شیء pickler خاص، میتوان ویژگیdispatch_tableرا روی یک شیء دیکشنریمانند تنظیم کرد. بهعنوان جایگزین، اگر یک زیرکلاس ازPicklerدارای ویژگیdispatch_tableباشد، این جدول بهعنوان جدول dispatch پیشفرض برای نمونههای آن کلاس استفاده خواهد شد.برای مثالهای استفاده به جدولهای اعزام (Dispatch Tables) مراجعه کنید.
اضافه شده در نسخهی 3.3.
- reducer_override(obj)¶
کاهنده ویژهای (reducer) که میتواند در زیرکلاسهای
Picklerتعریف شود. این متد بر هر کاهندهای درdispatch_tableاولویت دارد. باید از همان رابط یک متد__reduce__()پیروی کند، و میتواند بهصورت اختیاریNotImplementedرا برگرداند تا برای pickle کردنobjاز کاهندههای ثبتشده درdispatch_tableبهعنوان جایگزین استفاده شود.برای مثالی تفصیلی، کاهش سفارشی (Custom Reduction) برای انواع، توابع و اشیاء دیگر را ببینید.
اضافه شده در نسخهی 3.8.
- fast¶
منسوخ شده است. در صورت تنظیم روی مقدار درست، حالت سریع را فعال میکند. حالت سریع استفاده از یادداشت (memo) را غیرفعال میکند، بنابراین با تولید نکردن آپکدها (opcodes) اضافی PUT، به فرایند پیکلکردن سرعت میبخشد. نباید از آن با اشیای خودارجاع استفاده شود؛ در غیر این صورت باعث میشود
Picklerبهطور بینهایت بازگشتی شود.اگر به پیکلهای فشردهتری نیاز دارید، از
pickletools.optimize()استفاده کنید.
- clear_memo()¶
«memo» پیکلساز را پاک میکند.
یادداشت (memo) ساختار دادهای است که به یاد میآورد پیکلساز کدام اشیاء را از قبل دیده است، تا اشیاء مشترک یا بازگشتی بهصورت ارجاع پیکل شوند، نه بهصورت مقدار. این متد هنگام استفاده مجدد از پیکلسازها مفید است.
- class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
این یک پرونده دودویی را برای خواندن جریان داده pickle دریافت میکند.
نسخهی پروتکل pickle بهصورت خودکار شناسایی میشود، بنابراین نیازی به آرگومان protocol نیست.
آرگومان file باید سه متد داشته باشد: یک متد read() که یک آرگومان عدد صحیح میگیرد، یک متد readinto() که یک آرگومان بافر میگیرد و یک متد readline() که به هیچ آرگومانی نیاز ندارد، همانگونه که در رابط
io.BufferedIOBaseآمده است. بنابراین file میتواند یک پرونده روی دیسک باشد که برای خواندن دودویی باز شده است، یک شیءio.BytesIOباشد، یا هر شیء سفارشی دیگری که این رابط را برآورده میکند.آرگومانهای اختیاری fix_imports، encoding و errors برای کنترل پشتیبانی از سازگاری با جریان پیکل تولیدشده توسط Python 2 استفاده میشوند. اگر fix_imports درست باشد، پیکل تلاش میکند نامهای قدیمی Python 2 را به نامهای جدید بهکاررفته در Python 3 نگاشت کند. encoding و errors به پیکل میگویند که چگونه نمونههای رشتهای ۸ بیتی پیکلشده توسط Python 2 را کدگشایی کند؛ مقادیر پیشفرض این دو بهترتیب 'ASCII' و 'strict' است. encoding میتواند 'bytes' باشد تا این نمونههای رشتهای ۸ بیتی بهعنوان اشیاء bytes خوانده شوند. برای پیکلگشایی آرایههای NumPy و نمونههای
datetime،dateوtimeکه توسط Python 2 پیکلشدهاند، استفاده ازencoding='latin1'لازم است.اگر buffers برابر
Noneباشد (پیشفرض)، تمام دادههای مورد نیاز برای سریالزدایی باید در جریان pickle قرار داشته باشند. این بدان معناست که آرگومان buffer_callback هنگام ایجاد نمونهای ازPickler(یا هنگام فراخوانیdump()یاdumps()) برابرNoneبوده است.اگر buffers برابر
Noneنباشد، باید یک پیمایشپذیر از اشیاء پشتیبانیکننده از بافر باشد که هر بار که جریان pickle به یک نمای بافر خارج از باند ارجاع میدهد، مصرف میشود. چنین بافرهایی بهترتیب به buffer_callback یک شیء Pickler داده شدهاند.تغییر یافته در نسخهی 3.8: آرگومان buffers اضافه شد.
- load()¶
بازنمایی pickleشدهی یک شیء را از شیء پرونده بازِ دادهشده در سازنده میخواند و سلسلهمراتب شیء بازسازیشدهای را که در آن مشخص شده است برمیگرداند. بایتهای پس از بازنمایی pickleشدهی شیء نادیده گرفته میشوند.
- persistent_load(pid)¶
بهطور پیشفرض یک
UnpicklingErrorپرتاب میکند.اگر تعریف شده باشد،
persistent_load()باید شیء مشخصشده با شناسه پایدار pid را برگرداند. اگر با یک شناسه پایدار نامعتبر مواجه شدید، بایدUnpicklingErrorپرتاب شود.برای جزئیات و نمونههای کاربرد، ماندگاری اشیاء خارجی را ببینید.
تغییر یافته در نسخهی 3.13: پیادهسازی پیشفرض این متد را به پیادهسازی C کلاس
Unpicklerاضافه کنید.
- find_class(module, name)¶
در صورت لزوم module را ایمپورت میکند و شیء با نام name را از آن برمیگرداند، که آرگومانهای module و name اشیای
strهستند. توجه داشته باشید، برخلاف آنچه نامش پیشنهاد میدهد،find_class()همچنین برای یافتن توابع نیز استفاده میشود.زیرکلاسها میتوانند این را بازنویسی کنند تا بر نوع اشیاء و چگونگی بارگذاری آنها کنترل داشته باشند، که ممکن است خطرات امنیتی را کاهش دهد. برای جزئیات به محدود کردن متغیرهای سراسری مراجعه کنید.
یک رویداد حسابرسی
pickle.find_classرا با آرگومانهایmoduleوnameپرتاب میکند.
- class pickle.PickleBuffer(buffer)¶
پوششی برای بافری که نشاندهندهی دادههای پیکلپذیر است. buffer باید یک شیء فراهمکنندهی بافر باشد، مانند یک bytes-like object یا یک آرایهی N-بعدی.
PickleBufferخودش یک فراهمکننده بافر است، بنابراین امکان ارسال آن به APIهای دیگری که انتظار یک شیء فراهمکننده بافر را دارند، مانندmemoryviewوجود دارد.اشیاء
PickleBufferفقط با استفاده از پروتکل pickle 5 یا بالاتر قابل سریالسازی هستند. این اشیاء برای سریالسازی خارج از باند واجد شرایط هستند.اضافه شده در نسخهی 3.8.
- raw()¶
یک
memoryviewاز ناحیهی حافظهی زیربنایی این بافر برمیگرداند. شیء برگرداندهشده یک memoryview یکبعدی و پیوسته با ترتیب C (C-contiguous) با قالبB(بایتهای بدون علامت) است. اگر بافر نه پیوسته با ترتیب C و نه پیوسته با ترتیب Fortran (Fortran-contiguous) باشد،BufferErrorپرتاب میشود.
- release()¶
بافر زیرین را که توسط شیء PickleBuffer در معرض قرار گرفته است، آزاد کنید.
چه چیزهایی را میتوان pickle و unpickle کرد؟¶
انواع زیر را میتوان پیکل کرد:
ثابتهای توکار (
None،True،False،EllipsisوNotImplemented)؛اعداد صحیح، اعداد ممیز شناور، اعداد مختلط؛
رشتهها، بایتها، آرایههای بایتی؛
تاپلها، فهرستها، مجموعهها و دیکشنریهایی که فقط شامل اشیاء پیکلپذیر هستند؛
توابع (توکار و تعریفشده توسط کاربر) که از سطح بالایی یک ماژول قابل دسترسی هستند (با استفاده از
def، نهlambda)؛کلاسهای قابلدسترسی از سطح بالایی یک ماژول؛
نمونههایی از چنین کلاسهایی که نتیجهی فراخوانی
__getstate__()برای آنها پیکلپذیر باشد (برای جزئیات، بخش پیکلکردن نمونههای کلاس را ببینید).
تلاش برای پیکل کردن اشیاء غیرقابل پیکل، باعث پرتاب استثنای PicklingError میشود؛ هنگامی که این اتفاق میافتد، ممکن است تعداد نامشخصی بایت از قبل در پرونده زیرین نوشته شده باشند. تلاش برای پیکل کردن یک ساختار داده بهشدت بازگشتی ممکن است از حداکثر عمق بازگشت فراتر رود؛ در این حالت یک RecursionError پرتاب میشود. شما میتوانید این محدودیت را با sys.setrecursionlimit() با احتیاط افزایش دهید.
توجه داشته باشید که توابع (توکار و تعریفشده توسط کاربر) با نام کامل مشخص (نام کامل) پیکل میشوند، نه با مقدار. [2] این بدان معناست که فقط نام تابع پیکل میشود، بههمراه نام ماژول و کلاسهای حاوی آن. نه کد تابع و نه هیچیک از ویژگیهای تابع آن پیکل نمیشوند. بنابراین ماژول تعریفکننده باید در محیط پیکلگشایی قابل ایمپورت باشد و ماژول باید شامل شیء نامبرده باشد، در غیر این صورت استثنایی پرتاب خواهد شد. [3]
بهطور مشابه، کلاسها با نام کامل پیکل میشوند، بنابراین همان محدودیتها در محیط بارگذاری پیکل اعمال میشوند. توجه داشته باشید که هیچیک از کد یا دادههای کلاس پیکل نمیشود، بنابراین در مثال زیر ویژگی کلاس attr در محیط بارگذاری پیکل بازیابی نمیشود:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
به دلیل همین محدودیتها، توابع و کلاسهای پیکلپذیر باید در سطح بالای یک ماژول تعریف شوند.
بهطور مشابه، هنگامی که نمونههای کلاس پیکل میشوند، کد و دادههای کلاس آنها همراه با آنها پیکل نمیشوند. تنها دادههای نمونه پیکل میشوند. این کار از روی عمد انجام میشود تا بتوانید اشکالات یک کلاس را رفع کنید یا متدهایی به آن کلاس اضافه کنید و همچنان اشیایی را که با نسخهی پیشین آن کلاس ایجاد شدهاند بارگذاری کنید. اگر قصد دارید اشیایی با عمر طولانی داشته باشید که نسخههای زیادی از یک کلاس را تجربه خواهند کرد، ممکن است ارزش داشته باشد که یک شماره نسخه در آن شیءها قرار دهید تا متد __setstate__() کلاس بتواند تبدیلهای مناسب را انجام دهد.
پیکلکردن نمونههای کلاس¶
در این بخش، سازوکارهای کلی در دسترس شما را برای تعریف، سفارشیسازی و کنترل چگونگی پیکلکردن و پیکلگشایی (unpickle) نمونههای کلاس را شرح میدهیم.
در بیشتر موارد، برای پیکلپذیر کردن نمونهها به کد اضافی نیازی نیست. بهطور پیشفرض، pickle کلاس و ویژگیهای یک نمونه را از طریق دروننگری دریافت میکند. هنگامی که یک نمونه از کلاس از پیکل خارج میشود، متد __init__() آن معمولاً فراخوانی نمیشود. رفتار پیشفرض ابتدا یک نمونه مقداردهینشده ایجاد میکند و سپس ویژگیهای ذخیرهشده را بازگردانی میکند. کد زیر پیادهسازی این رفتار را نشان میدهد:
def save(obj):
return (obj.__class__, obj.__dict__)
def restore(cls, attributes):
obj = cls.__new__(cls)
obj.__dict__.update(attributes)
return obj
کلاسها میتوانند با ارائه یک یا چند متد ویژه، رفتار پیشفرض را تغییر دهند:
- object.__getnewargs_ex__()¶
در پروتکلهای ۲ و جدیدتر، کلاسهایی که متد
__getnewargs_ex__()را پیادهسازی میکنند، میتوانند مقادیری را که هنگام بازگردانی به متد__new__()ارسال میشوند، تعیین کنند. این متد باید یک جفت(args, kwargs)برگرداند که در آن args یک تاپل از آرگومانهای جایگاهی و kwargs یک دیکشنری از آرگومانهای نامدار برای ساخت شیء است. این مقادیر هنگام بازگردانی به متد__new__()ارسال خواهند شد.اگر متد
__new__()کلاس شما به آرگومانهای فقط کلیدواژهای نیاز دارد، باید این متد را پیادهسازی کنید. در غیر این صورت، برای سازگاری، پیادهسازی__getnewargs__()توصیه میشود.تغییر یافته در نسخهی 3.6:
__getnewargs_ex__()اکنون در پروتکلهای ۲ و ۳ استفاده میشود.
- object.__getnewargs__()¶
این متد هدفی مشابه
__getnewargs_ex__()دارد، اما فقط از آرگومانهای جایگاهی پشتیبانی میکند. این متد باید یک تاپل از آرگومانهاargsرا برگرداند که هنگام بارگذاری از پیکل به متد__new__()پاس داده خواهد شد.اگر
__getnewargs_ex__()تعریفشده باشد،__getnewargs__()فراخوانی نخواهد شد.تغییر یافته در نسخهی 3.6: پیش از پایتون 3.6، در پروتکلهای ۲ و ۳،
__getnewargs__()بهجای__getnewargs_ex__()فراخوانی میشد.
- object.__getstate__()¶
کلاسها میتوانند با بازنویسی متد
__getstate__()، بیشتر بر چگونگی پیکل شدن نمونههای خود تأثیر بگذارند. این متد فراخوانی میشود و شیء برگرداندهشده بهجای وضعیت پیشفرض، بهعنوان محتوای نمونه پیکل میشود. چندین حالت وجود دارد:برای کلاسی که
__dict__نمونه و__slots__ندارد، وضعیت پیشفرضNoneاست.برای کلاسی که دارای
__dict__نمونه و فاقد__slots__است، وضعیت پیشفرضself.__dict__است.برای کلاسی که نمونههای آن دارای
__dict__و__slots__هستند، وضعیت پیشفرض یک تاپلشامل دو دیکشنری است:self.__dict__، و دیکشنریای که نامهای جایگاه را به مقادیر جایگاه نگاشت میکند. فقط جایگاههایی که مقدار دارند، در دیکشنری دوم گنجانده میشوند.برای کلاسی که
__slots__دارد و نمونههای آن__dict__ندارند، وضعیت پیشفرض یک تاپل است که اولین آیتم آنNoneاست و دومین آیتم آن دیکشنریای است که نام جایگاهها را به مقادیر جایگاهِ توصیفشده در مورد قبلی نگاشت میکند.
تغییر یافته در نسخهی 3.11: پیادهسازی پیشفرض متد
__getstate__()به کلاسobjectافزوده شد.
- object.__setstate__(state)¶
هنگام خارجسازی از pickle، اگر کلاس
__setstate__()را تعریف کرده باشد، این متد با وضعیت خارجشده از pickle فراخوانی میشود. در این صورت، نیازی نیست که شیء وضعیت یک دیکشنری باشد. در غیر این صورت، وضعیت pickleشده باید یک دیکشنری باشد و آیتمهای آن به دیکشنری نمونه جدید اختصاص مییابند.توجه
اگر
__reduce__()در زمان پیکلکردن وضعیتی با مقدارNoneبرگرداند، متد__setstate__()در زمان از پیکل درآوردن فراخوانی نخواهد شد.
برای اطلاعات بیشتر در مورد چگونگی استفاده از متدهای __getstate__() و __setstate__() به بخش مدیریت اشیای حالتدار مراجعه کنید.
توجه
در زمان پیکلگشایی، ممکن است برخی متدها مانند __getattr__()، __getattribute__() یا __setattr__() بر روی نمونه فراخوانی شوند. در صورتی که این متدها به برقرار بودن یک ناوردایی ثابت داخلی (invariant) متکی باشند، نوع باید __new__() را پیادهسازی کند تا چنین ناوردایی ثابتی را برقرار کند، زیرا __init__() هنگام پیکلگشایی یک نمونه فراخوانی نمیشود.
همانطور که خواهیم دید، pickle بهطور مستقیم از متدهای توصیفشده در بالا استفاده نمیکند. در واقع، این متدها بخشی از پروتکل کپی هستند که متد ویژه __reduce__() را پیادهسازی میکند. پروتکل کپی رابط یکپارچهای برای بازیابی دادههای لازم برای پیکلکردن و کپی کردن اشیاء فراهم میکند. [4]
با وجود قدرت بالا، پیادهسازی مستقیم __reduce__() در کلاسهای شما مستعد خطا است. به همین دلیل، طراحان کلاس باید هر جا که ممکن است از رابط سطح بالا (یعنی __getnewargs_ex__()، __getstate__() و __setstate__()) استفاده کنند. با این حال، مواردی را نشان خواهیم داد که در آنها استفاده از __reduce__() تنها گزینه است یا به پیکلکردن کارآمدتر منجر میشود، یا هر دو.
- object.__reduce__()¶
این رابط در حال حاضر به صورت زیر تعریف شده است. متد
__reduce__()هیچ آرگومانی نمیگیرد و باید یا یک رشته یا ترجیحاً یک تاپل برگرداند (شیء برگرداندهشده اغلب بهعنوان «مقدار reduce» شناخته میشود).اگر یک رشته برگردانده شود، آن رشته باید بهعنوان نام یک متغیر سراسری تفسیر شود. این نام باید نام محلی شیء نسبت به ماژول آن باشد؛ ماژول pickle فضای نام ماژول را جستوجو میکند تا ماژول شیء را تعیین کند: برای یک
objمشخص که قرار است pickle شود، ویژگی__module__مستقیماً رویobjجستوجو میشود و اگر ویژگی نمونه__module__تنظیم نشده باشد، جستوجو به نوعobjبازمیگردد. این رفتار معمولاً برای اشیاء تکنمونه مفید است.هنگامی که یک تاپل برگردانده میشود، طول آن باید بین ۲ تا ۶ آیتم باشد. آیتمهای اختیاری را میتوان حذف کرد، یا میتوان
Noneرا بهعنوان مقدار آنها ارائه داد. معنای هر آیتم به ترتیب عبارت است از:یک شیء فراخوانیپذیر که برای ایجاد نسخهی اولیهی شیء فراخوانی میشود.
یک تاپلاز آرگومانها برای شیء فراخوانیپذیر. اگر شیء فراخوانیپذیر هیچ آرگومانی را نپذیرد، باید یک تاپلخالی داده شود.
بهصورت اختیاری، وضعیت شیء، که همانطور که پیشتر توضیح داده شد، به متد
__setstate__()شیء داده خواهد شد. اگر شیء چنین متدی نداشته باشد، مقدار باید یک دیکشنری باشد و به ویژگی__dict__شیء افزوده خواهد شد.بهصورت اختیاری، یک پیمایشگر (و نه یک دنباله) که آیتمهای متوالی را تولید میکند. این آیتمها یا با استفاده از
obj.append(item)یا بهصورت دستهای با استفاده ازobj.extend(list_of_items)به شیء افزوده میشوند. این مورد عمدتاً برای زیرکلاسهای فهرست استفاده میشود، اما ممکن است در کلاسهای دیگر نیز به کار رود، مشروط بر اینکه این کلاسها متدهایappend()وextend()را با امضای مناسب داشته باشند. (اینکه ازappend()استفاده میشود یاextend()، به نسخهی پروتکل pickle مورد استفاده و همچنین تعداد آیتمهایی که باید افزوده شوند بستگی دارد، بنابراین هر دو باید پشتیبانی شوند.)بهاختیار، یک پیمایشگر (نه یک دنباله) که جفتهای کلید-مقدار پیاپی را تولید میکند. این آیتمها با استفاده از
obj[key] = valueدر شیء ذخیره خواهند شد. این مورد عمدتاً برای زیرکلاسهای دیکشنری استفاده میشود، اما میتواند توسط کلاسهای دیگر نیز استفاده شود، مشروط بر اینکه آنها__setitem__()را پیادهسازی کنند.بهصورت اختیاری، یک شیء فراخوانیپذیر با امضای
(obj, state). این شیء فراخوانیپذیر به کاربر اجازه میدهد تا بهصورت برنامهای رفتار بهروزرسانی وضعیت یک شیء مشخص را بهجای استفاده از متد ایستا__setstate__()مربوط بهobjکنترل کند. اگرNoneنباشد، این شیء فراخوانیپذیر بر__setstate__()مربوط بهobjاولویت خواهد داشت.اضافه شده در نسخهی 3.8: ششمین آیتم اختیاری تاپل،
(obj, state)، افزوده شد.
- object.__reduce_ex__(protocol)¶
بهعنوان جایگزین، میتوان متد
__reduce_ex__()را تعریف کرد. تنها تفاوت این است که این متد باید یک آرگومان عدد صحیح بگیرد، یعنی نسخهی پروتکل. در صورتی که تعریف شود، pickle آن را به متد__reduce__()ترجیح میدهد. علاوه بر این،__reduce__()بهطور خودکار به مترادفی برای نسخهی توسعهیافته تبدیل میشود. کاربرد اصلی این متد، فراهم کردن مقادیر reduce سازگار با نسخههای قدیمیتر پایتون است.
ماندگاری اشیاء خارجی¶
برای بهرهمندی از ماندگاری شیء، ماژول pickle از مفهوم ارجاع به یک شیء خارج از جریان دادهی pickleشده پشتیبانی میکند. به چنین اشیایی با یک شناسهی ماندگار ارجاع داده میشود، که باید یا رشتهای از نویسههای الفباییعددی (برای پروتکل 0) [5] باشد یا صرفاً یک شیء دلخواه (برای هر پروتکل جدیدتر).
حل چنین شناسههای ماندگاری در ماژول pickle تعریف نشده است؛ این ماژول آن را به متدهای تعریفشده توسط کاربر در pickler و unpickler، بهترتیب persistent_id() و persistent_load() واگذار میکند.
برای پیکلکردن اشیایی که شناسهی ماندگار خارجی (persistent ID) دارند، پیکلساز باید متدی سفارشی persistent_id() داشته باشد که یک شیء را بهعنوان آرگومان دریافت میکند و None یا شناسهی ماندگار آن شیء را برمیگرداند. هنگامی که None برگردانده شود، پیکلساز بهسادگی شیء را مانند حالت عادی پیکل میکند. هنگامی که یک رشتهی شناسهی ماندگار برگردانده شود، پیکلساز آن شیء را بههمراه یک نشانگر پیکل میکند تا پیکلگشا آن را بهعنوان یک شناسهی ماندگار تشخیص دهد.
برای پیکلگشایی (unpickle)، پیکلگشا باید یک متد سفارشی persistent_load() داشته باشد که یک شیء شناسهی ماندگار را دریافت میکند و شیء ارجاعشده را بازمیگرداند.
در اینجا، مثالی جامع ارائه شده است که نشان میدهد چگونه میتوان از شناسهی ماندگار (persistent ID) برای پیکل کردن اشیاء خارجی بهصورت ارجاعی استفاده کرد.
# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.
import pickle
import sqlite3
from collections import namedtuple
# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Instead of pickling MemoRecord as a regular class instance, we emit a
# persistent ID.
if isinstance(obj, MemoRecord):
# Here, our persistent ID is simply a tuple, containing a tag and a
# key, which refers to a specific record in the database.
return ("MemoRecord", obj.key)
else:
# If obj does not have a persistent ID, return None. This means obj
# needs to be pickled as usual.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# This method is invoked whenever a persistent ID is encountered.
# Here, pid is the tuple returned by DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Fetch the referenced record from the database and return it.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Always raises an error if you cannot return the correct object.
# Otherwise, the unpickler will think None is the object referenced
# by the persistent ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Initialize and populate our database.
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,))
# Fetch the records to be pickled.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Save the records using our custom DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Update a record, just for good measure.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Load the records from the pickle data stream.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
جدولهای اعزام (Dispatch Tables)¶
اگر بخواهید پیکلکردن برخی کلاسها را بدون اخلال در هیچ کد دیگری که به پیکلکردن وابسته است سفارشیسازی کنید، میتوانید یک پیکلساز با یک جدول نگاشت خصوصی ایجاد کنید.
جدول نگاشت سراسری که ماژول 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 را با یک جدول نگاشت خصوصی (dispatch table) ایجاد میکند که کلاس SomeClass را بهطور ویژه مدیریت میکند. بهعنوان جایگزین، کد
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
همین کار را انجام میدهد، اما تمام نمونههای MyPickler بهطور پیشفرض جدول نگاشت خصوصی را به اشتراک خواهند گذاشت. از سوی دیگر، کد
copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)
جدول نگاشت سراسری را که میان همهی کاربران ماژول copyreg مشترک است، تغییر میدهد.
مدیریت اشیای حالتدار¶
در اینجا مثالی آورده شده است که نحوهی اصلاح رفتار پیکلکردن برای یک کلاس را نشان میدهد. کلاس 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):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
state = self.__dict__.copy()
# Remove the unpicklable entries.
del state['file']
return state
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
# Restore the previously opened file's state. To do so, we need to
# reopen it and read from it until the line count is restored.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Finally, save the file.
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!'
کاهش سفارشی (Custom Reduction) برای انواع، توابع و اشیاء دیگر¶
اضافه شده در نسخهی 3.8.
گاهی ممکن است dispatch_table بهاندازه کافی انعطافپذیر نباشد. بهویژه ممکن است بخواهیم پیکلکردن را بر اساس معیاری غیر از نوع شیء سفارشیسازی کنیم، یا ممکن است بخواهیم پیکلکردن توابع و کلاسها را سفارشیسازی کنیم.
برای این موارد، میتوان زیرکلاسی از کلاس Pickler ساخت و متد reducer_override() را پیادهسازی کرد. این متد میتواند یک تاپل کاهش دلخواه (reduction tuple) برگرداند (به __reduce__() مراجعه کنید). همچنین میتواند NotImplemented را برگرداند تا به رفتار سنتی بازگردد.
اگر هر دو dispatch_table و reducer_override() تعریف شده باشند، آنگاه متد reducer_override() اولویت دارد.
توجه
به دلایل عملکردی، ممکن است reducer_override() برای اشیاء زیر فراخوانی نشود: None، True، False و نمونههای دقیق int، float، bytes، str، dict، set، frozenset، list و tuple.
در اینجا یک مثال ساده آمده است که در آن پیکلکردن و بازسازی یک کلاس مشخص را امکانپذیر میکنیم:
import io
import pickle
class MyClass:
my_attribute = 1
class MyPickler(pickle.Pickler):
def reducer_override(self, obj):
"""Custom reducer for MyClass."""
if getattr(obj, "__name__", None) == "MyClass":
return type, (obj.__name__, obj.__bases__,
{'my_attribute': obj.my_attribute})
else:
# For any other object, fallback to usual reduction
return NotImplemented
f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)
del MyClass
unpickled_class = pickle.loads(f.getvalue())
assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1
بافرهای خارج از باند (Out-of-band Buffers)¶
اضافه شده در نسخهی 3.8.
در برخی موارد، ماژول pickle برای انتقال مقادیر عظیمی از دادهها استفاده میشود. بنابراین، کاهش تعداد کپیهای حافظهای میتواند برای حفظ عملکرد و مصرف منابع مهم باشد. با این حال، عملکرد عادی ماژول pickle، از آنجا که ساختار گرافمانندی از شیءها را به یک جریان متوالی از بایتها تبدیل میکند، ذاتاً شامل کپی کردن دادهها به جریان pickle و از آن است.
اگر هر دو ارائهدهنده (پیادهسازی انواع اشیایی که قرار است منتقل شوند) و مصرفکننده (پیادهسازی سیستم ارتباطی) از امکانات انتقال خارج از باند (out-of-band) که توسط پروتکل pickle 5 و بالاتر فراهم شده است پشتیبانی کنند، میتوان از این محدودیت اجتناب کرد.
API ارائهدهنده¶
اشیای دادهای بزرگی که باید پیکل شوند، باید یک متد __reduce_ex__() اختصاصیشده برای پروتکل ۵ و بالاتر را پیادهسازی کنند که برای هر داده بزرگی، نمونهای از PickleBuffer را (بهجای مثلاً یک شیء bytes) برمیگرداند.
یک شیء PickleBuffer نشان میدهد که بافر زیربنایی واجد شرایط برای انتقال دادهی خارج از باند است. آن شیءها همچنان با استفادهی عادی از ماژول pickle سازگار باقی میمانند. با این حال، مصرفکنندگان همچنین میتوانند بهصورت اختیاری به pickle اطلاع دهند که آن بافرها را خودشان مدیریت خواهند کرد.
API مصرفکننده¶
یک سیستم ارتباطی میتواند امکان مدیریت سفارشی اشیای PickleBuffer را که هنگام سریالسازی یک گراف شیء تولید میشوند، فراهم کند.
در سمت فرستنده، لازم است یک آرگومان buffer_callback به Pickler (یا به تابع dump() یا dumps()) ارسال شود، که به ازای هر PickleBuffer تولیدشده هنگام پیکلکردن گراف اشیاء فراخوانی میشود. دادهی بافرهای جمعآوریشده توسط buffer_callback در جریان پیکل کپی نخواهد شد و تنها یک نشانگر کمهزینه درج خواهد شد.
در سمت دریافتکننده، باید یک آرگومان buffers، که یک پیمایشپذیر از بافرهایی است که به buffer_callback داده شدهاند، به Unpickler (یا به تابع load() یا loads()) داده شود. آن پیمایشپذیر باید بافرها را به همان ترتیبی تولید کند که به buffer_callback داده شدهاند. این بافرها دادههای مورد انتظار بازسازندههای اشیایی را فراهم میکنند که پیکلکردن آنها اشیاء اصلی PickleBuffer را تولید کرده است.
بین طرف فرستنده و طرف گیرنده، سیستم ارتباطی آزاد است سازوکار انتقال خود را برای بافرهای خارج از باند (out-of-band buffers) پیادهسازی کند. بهینهسازیهای بالقوه شامل استفاده از حافظه مشترک یا فشردهسازی وابسته به نوع داده است.
مثال¶
در اینجا یک مثال ساده آورده شده است که در آن یک زیرکلاس از bytearray را پیادهسازی میکنیم که میتواند در پیکلکردن خارج از باند بافر (out-of-band buffer pickling) مشارکت کند:
class ZeroCopyByteArray(bytearray):
def __reduce_ex__(self, protocol):
if protocol >= 5:
return type(self)._reconstruct, (PickleBuffer(self),), None
else:
# PickleBuffer is forbidden with pickle protocols <= 4.
return type(self)._reconstruct, (bytearray(self),)
@classmethod
def _reconstruct(cls, obj):
with memoryview(obj) as m:
# Get a handle over the original buffer object
obj = m.obj
if type(obj) is cls:
# Original buffer object is a ZeroCopyByteArray, return it
# as-is.
return obj
else:
return cls(obj)
بازسازنده (متد کلاس _reconstruct) در صورتی که نوع مناسبی داشته باشد، شیء فراهمکنندهی بافر را برمیگرداند. این روشی آسان برای شبیهسازی رفتار بدون کپی (zero-copy) در این مثال ساده است.
در سمت مصرفکننده، میتوانیم آن شیءها را به روش معمول پیکل کنیم؛ این شیءها هنگامی که از پیکل خارج (unserialized) شوند، یک کپی از شیء اصلی به ما میدهند:
b = ZeroCopyByteArray(b"abc")
data = pickle.dumps(b, protocol=5)
new_b = pickle.loads(data)
print(b == new_b) # True
print(b is new_b) # False: a copy was made
اما اگر یک buffer_callback را ارسال کنیم و سپس هنگام سریالزدای، بافرهای انباشتهشده را بازگردانیم، میتوانیم شیء اصلی را بازیابی کنیم:
b = ZeroCopyByteArray(b"abc")
buffers = []
data = pickle.dumps(b, protocol=5, buffer_callback=buffers.append)
new_b = pickle.loads(data, buffers=buffers)
print(b == new_b) # True
print(b is new_b) # True: no copy was made
این مثال به این دلیل محدود است که bytearray حافظهی خود را تخصیص میدهد: شما نمیتوانید نمونهای از bytearray ایجاد کنید که بر پایهی حافظهی شیء دیگری باشد. با این حال، انواع دادهی شخص ثالث مانند آرایههای NumPy این محدودیت را ندارند و هنگام انتقال بین فرایندها یا سیستمهای مجزا، امکان استفاده از پیکلکردن بدون کپی (zero-copy pickling) (یا ایجاد کمترین تعداد کپی ممکن) را فراهم میکنند.
همچنین ملاحظه نمائید
PEP 574 -- پروتکل ۵ Pickle با دادههای خارج از باند (out-of-band data)
محدود کردن متغیرهای سراسری¶
بهطور پیشفرض، از پیکل درآوردن هر کلاس یا تابعی را که در دادههای پیکل پیدا کند، ایمپورت میکند. این رفتار برای بسیاری از برنامهها غیرقابلقبول است، زیرا به از پیکلگشا اجازه میدهد کد دلخواه را ایمپورت و فراخوانی کند. فقط در نظر بگیرید که این جریان دادههای پیکل دستساز هنگام بارگذاری چه کاری انجام میدهد:
>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0
در این مثال، پیکلگشا تابع os.system() را ایمپورت میکند و سپس آرگومان رشتهای "echo hello world" را به آن اعمال میکند. اگرچه این مثال بیضرر است، تصور مثالی که بتواند به سیستم شما آسیب برساند دشوار نیست.
به همین دلیل، ممکن است بخواهید با سفارشیسازی Unpickler.find_class() کنترل کنید که چه چیزی unpickle شود. برخلاف آنچه نامش نشان میدهد، 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):
# Only allow safe classes from builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Forbid everything else.
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()
نمونهای از کاربرد unpickler ما که مطابق انتظار کار میکند:
>>> 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
همانطور که مثالهای ما نشان میدهند، باید مراقب باشید که اجازه میدهید چه چیزی از پیکل خارج شود . بنابراین، اگر امنیت مطرح است، ممکن است بخواهید جایگزینهایی مانند API مارشالینگ در xmlrpc.client یا راهحلهای شخص ثالث را در نظر بگیرید.
کارایی¶
نسخههای اخیر پروتکل pickle (از پروتکل 2 به بالا) دارای کدگذاریهای دودویی کارآمدی برای چندین قابلیت رایج و انواع توکار هستند. همچنین، ماژول pickle دارای یک بهینهساز شفاف نوشتهشده به زبان C است.
مثالها¶
برای سادهترین کد، از توابع dump() و load() استفاده کنید.
import pickle
# An arbitrary collection of objects supported by pickle.
data = {
'a': [1, 2.0, 3+4j],
'b': ("character string", b"byte string"),
'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:
# Pickle the 'data' dictionary using the highest protocol available.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
مثال زیر دادههای pickled حاصل را میخواند.
import pickle
with open('data.pickle', 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
data = pickle.load(f)
رابط خط فرمان¶
ماژول pickle را میتوان بهصورت یک اسکریپت از خط فرمان فراخوانی کرد؛ این کار محتویات پروندههای pickle را نمایش خواهد داد. با این حال، هنگامی که پرونده pickle که میخواهید بررسی کنید از منبعی نامطمئن آمده باشد، -m pickletools گزینه امنتری است، زیرا بایتکد pickle را اجرا نمیکند؛ استفاده از خط فرمان pickletools را ببینید.
python -m pickle pickle_file [pickle_file ...]
گزینهی زیر پذیرفته میشود:
- pickle_file¶
یک پرونده pickle برای خواندن، یا
-برای نشان دادن اینکه از ورودی استاندارد خوانده شود.
همچنین ملاحظه نمائید
- ماژول
copyreg ثبت سازندهی رابط Pickle برای انواع توسعهای.
- ماژول
pickletools ابزارهایی برای کار با دادههای pickleشده و تحلیل آنها.
- ماژول
shelve پایگاههای دادهی اندیسشده از اشیاء؛ از
pickleاستفاده میکند.- ماژول
copy کپیسازی کمعمق و عمیق شیء.
- ماژول
marshal سریالسازی انواع توکار با کارایی بالا.
پانویسها