پروتکل بافر¶
برخی از اشیاء موجود در پایتون، دسترسی به یک آرایه حافظه یا بافر زیرین را پوشش میدهند. چنین اشیایی شامل bytes و bytearray توکار و برخی نوعهای توسعهای مانند array.array هستند. کتابخانههای شخص ثالث ممکن است برای مقاصد خاص، مانند پردازش تصویر یا تحلیل عددی، نوعهای خود را تعریف کنند.
هرچند هر یک از این نوعها معناشناسی خاص خود را دارند، اما ویژگی مشترک آنها این است که مبتنی بر یک بافر حافظه — که ممکن است بزرگ باشد — هستند. از این رو، در برخی موارد مطلوب است که به آن بافر مستقیماً و بدون کپیکردن میانی دسترسی داشته باشید.
پایتون چنین امکانی را در سطح C و پایتون به شکل پروتکل بافر فراهم میکند. این پروتکل دو سمت دارد:
در سمت تولیدکننده، یک نوع میتواند «رابط بافر» را ارائه کند که به اشیاء آن نوع اجازه میدهد تا اطلاعاتی دربارهی بافر زیرین خود را آشکار کنند. این رابط در بخش ساختارهای شیء بافر شرح داده شده است؛ برای پایتون به شبیهسازی انواع بافر مراجعه کنید.
در سمت مصرفکننده، چندین روش برای به دست آوردن اشارهگر به دادههای خام زیربنایی یک شیء (برای مثال، یک پارامتر متد) در دسترس است. برای پایتون به
memoryviewمراجعه کنید.
اشیاء سادهای مانند bytes و bytearray بافر زیرین خود را به شکل بایتمحور در دسترس قرار میدهند. شکلهای دیگری نیز ممکن است؛ برای مثال، عناصری که یک array.array در دسترس قرار میدهد میتوانند مقادیر چندبایتی باشند.
نمونهای از مصرفکنندگان رابط بافر، متد write() در شیءهای پرونده است: هر شیئی که بتواند دنبالهای از بایتها را از طریق رابط بافر اکسپورت کند، میتواند در پروندهای نوشته شود. در حالی که write() تنها به دسترسی فقطخواندنی به محتوای درونی شیئی که به آن پاس داده شده نیاز دارد، متدهای دیگری مانند readinto() به دسترسی نوشتن به محتوای آرگومان خود نیاز دارند. رابط بافر به شیءها اجازه میدهد تا اکسپورت کردن بافرهای خواندنی-نوشتنی و فقطخواندنی را بهصورت انتخابی بپذیرند یا رد کنند.
دو راه وجود دارد که مصرفکنندهی رابط بافر بتواند بافری را روی یک شیء هدف به دست آورد:
PyObject_GetBuffer()را با پارامترهای درست فراخوانی کنید؛تابع
PyArg_ParseTuple()(یا یکی از توابع همخانوادهی آن) را با یکی از کدهای قالبy*،w*یاs*فراخوانی کنید.
در هر دو مورد، PyBuffer_Release() باید زمانی که دیگر به بافر نیازی نیست فراخوانی شود. عدم انجام این کار میتواند به مشکلات مختلفی مانند نشت منابع منجر شود.
اضافه شده در نسخهی 3.12: پروتکل بافر اکنون در پایتون دسترسیپذیر است؛ به شبیهسازی انواع بافر و memoryview مراجعه کنید.
ساختار بافر¶
ساختارهای بافر (یا بهسادگی «بافرها») بهعنوان راهی برای افشای دادههای دودویی از یک شیء دیگر به برنامهنویس پایتون مفید هستند. همچنین میتوان از آنها بهعنوان سازوکاری برای اسلایسکردن بدون کپی استفاده کرد. با استفاده از توانایی آنها در ارجاع به یک بلوک حافظه، میتوان هر دادهای را بهسادگی به برنامهنویس پایتون افشا کرد. این حافظه میتواند یک آرایهی بزرگ و ثابت در یک توسعه C باشد، میتواند یک بلوک خام حافظه برای دستکاری پیش از انتقال به یک کتابخانهی سیستمعامل باشد، یا میتواند برای بهگردش درآوردن دادههای ساختاریافته در قالب بومی و درونحافظهای خود استفاده شود.
برخلاف بیشتر نوعهای دادهای که مفسر پایتون ارائه میکند، بافرها اشارهگرهای PyObject نیستند، بلکه ساختارهای سادهی C هستند. این امر امکان میدهد که آنها بهسادگی ساخته و کپی شوند. وقتی به یک دربرگیرندهی عام برای بافر نیاز باشد، میتوان یک شیء memoryview ایجاد کرد.
برای دستورالعملهای کوتاه دربارهی نحوهی نوشتن یک شیء اکسپورتکننده (exporting object)، به ساختارهای شیء بافر مراجعه کنید. برای به دست آوردن یک بافر، به PyObject_GetBuffer() مراجعه کنید.
-
type Py_buffer¶
- قسمتی از ABI پایدار شامل تمام اعضا از نسخهی 3.11.
-
void *buf¶
اشارهگری به ابتدای ساختار منطقیای که توسط فیلدهای بافر توصیف میشود. این میتواند هر مکانی درون بلوک حافظه فیزیکی زیرین اکسپورتکننده باشد. برای مثال، با
stridesمنفی، مقدار ممکن است به انتهای بلوک حافظه اشاره کند.برای آرایههای پیوسته، مقدار به ابتدای بلوک حافظه اشاره میکند.
-
PyObject *obj¶
ارجاعی جدید به شیء اکسپورتکننده. این ارجاع در مالکیت مصرفکننده است و بهطور خودکار توسط
PyBuffer_Release()آزاد میشود (یعنی شمارش ارجاع کاهش مییابد) و بهNULLتنظیم میشود. این فیلد معادل مقدار بازگشتی هر تابع استاندارد C-API است.بهعنوان یک مورد خاص، برای بافرهای موقتی که توسط
PyMemoryView_FromBuffer()یاPyBuffer_FillInfo()پوشش داده میشوند، این فیلدNULLاست. بهطور کلی، اشیاء اکسپورتکننده به هیچ وجه نباید از این طرح استفاده کنند.
-
Py_ssize_t len¶
product(shape) * itemsize. برای آرایههای پیوسته، این مقدار طول بلوک حافظهی زیرین است. برای آرایههای ناپیوسته، این مقدار طولی است که ساختار منطقی در صورت کپی شدن به یک نمایش پیوسته خواهد داشت.دسترسی به
((char *)buf)[0] up to ((char *)buf)[len-1]تنها زمانی معتبر است که بافر از طریق درخواستی که پیوستگی را تضمین میکند به دست آمده باشد. در بیشتر موارد، چنین درخواستیPyBUF_SIMPLEیاPyBUF_WRITABLEخواهد بود.
-
int readonly¶
نشانگری که مشخص میکند بافر فقطخواندنی است یا خیر. این فیلد توسط پرچم
PyBUF_WRITABLEکنترل میشود.
-
Py_ssize_t itemsize¶
اندازهی آیتم به بایت برای یک عنصر منفرد. همانند مقدار
struct.calcsize()است که روی مقادیر غیرNULLformatفراخوانی میشود.استثنای مهم: اگر مصرفکنندهای بافر را بدون پرچم
PyBUF_FORMATدرخواست کند،formatبرابرNULLقرار میگیرد، اماitemsizeهمچنان مقدار مربوط به قالب اصلی را دارد.اگر
shapeموجود باشد، برابریproduct(shape) * itemsize == lenهمچنان برقرار است و مصرفکننده میتواند ازitemsizeبرای پیمایش بافر استفاده کند.اگر
shapeدر نتیجهی درخواستPyBUF_SIMPLEیاPyBUF_WRITABLEبرابرNULLباشد، مصرفکننده بایدitemsizeرا نادیده بگیرد و فرض کند کهitemsize == 1است.
-
char *format¶
رشتهای خاتمهیافته با NULL به سبک سینتکس ماژول
structکه محتویات یک آیتم منفرد را توصیف میکند. اگر اینNULLباشد،"B"(بایتهای بدون علامت) فرض میشود.این فیلد توسط پرچم
PyBUF_FORMATکنترل میشود.
-
int ndim¶
تعداد ابعادی که حافظه بهصورت یک آرایهی n-بعدی بازنمایی میکند. اگر
0باشد،bufبه یک آیتم واحد اشاره میکند که نمایانگر یک اسکالر است. در این حالت،shape،stridesوsuboffsetsبایدNULLباشند. حداکثر تعداد ابعاد توسطPyBUF_MAX_NDIMتعیین میشود.
-
Py_ssize_t *shape¶
آرایهای از
Py_ssize_tبه طولndimکه شکل حافظه را بهعنوان یک آرایهی n-بعدی نشان میدهد. توجه داشته باشید کهshape[0] * ... * shape[ndim-1] * itemsizeحتماً باید باlenبرابر باشد.مقادیر شکل به
shape[n] >= 0محدود هستند. موردshape[n] == 0نیاز به توجه ویژه دارد. برای اطلاعات بیشتر به complex arrays مراجعه کنید.آرایهی شکل برای مصرفکننده فقطخواندنی است.
-
Py_ssize_t *strides¶
آرایهای از
Py_ssize_tبه طولndimکه مشخص میکند برای رسیدن به عنصر جدید در هر بُعد باید چند بایت را رد کرد.مقادیر گام (stride) میتوانند هر عدد صحیحی باشند. برای آرایههای معمولی، گامها معمولاً مثبت هستند، اما مصرفکننده باید حتماً بتواند مورد
strides[n] <= 0را مدیریت کند. برای اطلاعات بیشتر به complex arrays مراجعه کنید.آرایهی strides برای مصرفکننده فقطخواندنی است.
-
Py_ssize_t *suboffsets¶
آرایهای از
Py_ssize_tبه طولndim. اگرsuboffsets[n] >= 0باشد، مقادیر ذخیرهشده در راستای بعد n-اُم اشارهگر هستند و مقدار زیرآفست تعیین میکند که پس از ارجاعزدایی (de-referencing)، چند بایت باید به هر اشارهگر افزوده شود. مقدار منفیِ زیرآفست نشان میدهد که نباید ارجاعزدایی انجام شود (گامبرداری (striding) در یک بلوک حافظهی پیوسته).اگر همهی suboffsets منفی باشند (یعنی نیازی به ارجاعزدایی نیست)، آنگاه این فیلد باید
NULLباشد (مقدار پیشفرض).این نوع نمایش آرایه توسط Python Imaging Library (PIL) استفاده میشود. برای اطلاعات بیشتر دربارهی نحوهی دسترسی به عناصر چنین آرایهای، به complex arrays مراجعه کنید.
آرایهی suboffsets برای مصرفکننده فقطخواندنی است.
-
void *internal¶
این مورد برای استفاده داخلی توسط شیء اکسپورتکننده است. برای مثال، اکسپورتکننده ممکن است آن را به یک عدد صحیح قالبریزی مجدد کند و از آن برای ذخیره پرچمهایی استفاده کند که مشخص میکنند آرایههای shape، strides و suboffsets هنگام رها شدن بافر باید آزاد شوند یا خیر. مصرفکننده به هیچ وجه نباید این مقدار را تغییر دهد.
-
void *buf¶
ثابتها:
-
PyBUF_MAX_NDIM¶
- قسمتی از ABI پایدار از نسخهی 3.11.
حداکثر تعداد ابعادی که حافظه بازنمایی میکند. اکسپورتکنندگان باید (MUST) این محدودیت را رعایت کنند و بهتر است (SHOULD) مصرفکنندگان بافرهای چندبعدی بتوانند تا
PyBUF_MAX_NDIMبعد را مدیریت کنند. در حال حاضر روی ۶۴ تنظیم شده است.
انواع درخواست بافر¶
بافرها معمولاً با ارسال درخواست بافر به یک شیء اکسپورتکننده از طریق PyObject_GetBuffer() به دست میآیند. از آنجا که پیچیدگی ساختار منطقی حافظه میتواند بهشدت متفاوت باشد، مصرفکننده از آرگومان پرچمها برای تعیین نوع دقیق بافری که میتواند آن را مدیریت کند استفاده میکند.
همهی فیلدهای Py_buffer بهطور بیابهام توسط نوع درخواست تعریف میشوند.
فیلدهای مستقل از درخواست¶
فیلدهای زیر تحت تأثیر پرچمها قرار نمیگیرند و همیشه باید با مقادیر صحیح پر شوند: obj، buf، len، itemsize، ndim.
فقطخواندنی، قالب¶
- PyBUF_WRITABLE¶
- قسمتی از ABI پایدار از نسخهی 3.11.
فیلد
readonlyرا کنترل میکند. اگر تنظیم شده باشد، اکسپورتکننده باید یا بافر نوشتنی فراهم کند یا شکست را گزارش دهد. در غیر این صورت، اکسپورتکننده میتواند بافر فقطخواندنی یا نوشتنی فراهم کند، اما این انتخاب باید برای همهی مصرفکنندگان یکسان باشد. برای مثال، میتوان از PyBUF_SIMPLE | PyBUF_WRITABLE برای درخواست یک بافر نوشتنی ساده استفاده کرد.
- PyBUF_WRITEABLE¶
این یک نام مستعار برای
PyBUF_WRITABLEاست.منسوخسازی نرم <Soft deprecated> از نسخهی 3.13.
- PyBUF_FORMAT¶
- قسمتی از ABI پایدار از نسخهی 3.11.
فیلد
formatرا کنترل میکند. اگر تنظیم شده باشد، این فیلد باید بهدرستی پر شود. در غیر این صورت، این فیلد بایدNULLباشد.
میتوان PyBUF_WRITABLE را با هر یک از پرچمهای بخش بعدی از طریق عملگر | ترکیب کرد. از آنجا که PyBUF_SIMPLE به صورت 0 تعریف شده است، میتوان از PyBUF_WRITABLE به عنوان یک پرچم مستقل برای درخواست یک بافر سادهی نوشتنی استفاده کرد.
PyBUF_FORMAT باید با عملگر | به هر یک از پرچمها بهجز PyBUF_SIMPLE اضافه شود، زیرا مورد دوم از قبل قالب B (بایتهای بدون علامت) را در بر میگیرد. PyBUF_FORMAT را نمیتوان بهتنهایی استفاده کرد.
شکل، گامها (strides)، زیرآفستها (suboffsets)¶
پرچمهایی که ساختار منطقی حافظه را کنترل میکنند، به ترتیب نزولی پیچیدگی فهرست شدهاند. توجه داشته باشید که هر پرچم، تمام بیتهای پرچمهای پایینتر از آن را در بر میگیرد.
درخواست |
شکل |
گامها |
suboffsets |
|---|---|---|---|
|
بله |
بله |
در صورت نیاز |
|
بله |
بله |
NULL |
|
بله |
NULL |
NULL |
|
NULL |
NULL |
NULL |
درخواستهای پیوستگی¶
میتوان پیوستگی به سبک C یا Fortran را بهطور صریح درخواست کرد، هم به همراه اطلاعات گام (stride) و هم بدون آن. بدون اطلاعات گام، بافر باید پیوسته به سبک C (C-contiguous) باشد.
درخواست |
شکل |
گامها |
suboffsets |
پیوسته |
|---|---|---|---|---|
|
بله |
بله |
NULL |
C |
|
بله |
بله |
NULL |
F |
|
بله |
بله |
NULL |
C یا F |
بله |
NULL |
NULL |
C |
درخواستهای ترکیبی¶
تمامی درخواستهای ممکن بهطور کامل با ترکیبی از پرچمهای بخش قبلی تعریف میشوند. برای راحتی، پروتکل بافر ترکیبهای پرکاربرد را بهصورت پرچمهای تکی فراهم میکند.
در جدول زیر U نمایانگر پیوستگی تعریفنشده است. مصرفکننده برای تعیین پیوستگی باید PyBuffer_IsContiguous() را فراخوانی کند.
درخواست |
شکل |
گامها |
suboffsets |
پیوسته |
فقطخواندنی |
قالب |
|---|---|---|---|---|---|---|
|
بله |
بله |
در صورت نیاز |
U |
0 |
بله |
|
بله |
بله |
در صورت نیاز |
U |
۱ یا ۰ |
بله |
|
بله |
بله |
NULL |
U |
0 |
بله |
|
بله |
بله |
NULL |
U |
۱ یا ۰ |
بله |
|
بله |
بله |
NULL |
U |
0 |
NULL |
|
بله |
بله |
NULL |
U |
۱ یا ۰ |
NULL |
|
بله |
NULL |
NULL |
C |
0 |
NULL |
|
بله |
NULL |
NULL |
C |
۱ یا ۰ |
NULL |
آرایههای پیچیده¶
به سبک NumPy: شکل و گامها¶
ساختار منطقی آرایههای به سبک NumPy توسط itemsize، ndim، shape و strides تعریف میشود.
اگر ndim == 0 باشد، مکان حافظهای که buf به آن اشاره میکند، بهعنوان یک اسکالر با اندازهی itemsize تفسیر میشود. در این حالت، هر دو shape و strides برابر NULL هستند.
اگر strides برابر NULL باشد، آرایه بهعنوان یک آرایهی C استاندارد n-بعدی تفسیر میشود. در غیر این صورت، مصرفکننده باید به یک آرایهی n-بعدی به شرح زیر دسترسی داشته باشد:
ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);
همانطور که در بالا ذکر شد، buf میتواند به هر مکانی درون بلوک حافظهی واقعی اشاره کند. یک اکسپورتکننده (exporter) میتواند اعتبار یک بافر را با این تابع بررسی کند:
def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
"""Verify that the parameters represent a valid array within
the bounds of the allocated memory:
char *mem: start of the physical memory block
memlen: length of the physical memory block
offset: (char *)buf - mem
"""
if offset % itemsize:
return False
if offset < 0 or offset+itemsize > memlen:
return False
if any(v % itemsize for v in strides):
return False
if ndim <= 0:
return ndim == 0 and not shape and not strides
if 0 in shape:
return True
imin = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] <= 0)
imax = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] > 0)
return 0 <= offset+imin and offset+imax+itemsize <= memlen
به سبک PIL: shape، strides و suboffsets¶
علاوه بر آیتمهای معمولی، آرایههای به سبک PIL میتوانند شامل اشارهگرهایی باشند که برای رسیدن به عنصر بعدی در یک بعد باید دنبال شوند. برای مثال، آرایه سهبعدی معمولی C یعنی char v[2][2][3] را میتوان بهصورت آرایهای از ۲ اشارهگر به ۲ آرایه دوبعدی نیز در نظر گرفت: char (*v[2])[2][3]. در نمایش زیرآفستها (suboffsets)، این دو اشارهگر میتوانند در ابتدای buf تعبیه شوند و به دو آرایه char x[2][3] اشاره کنند که میتوانند در هر جای حافظه قرار داشته باشند.
تابع زیر وقتی که هم گامها و هم زیرآفستها (suboffsets) غیر NULL باشند، اشارهگر به عنصری در آرایهی N-بعدی را که یک اندیس N-بعدی به آن اشاره میکند برمیگرداند:
void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
Py_ssize_t *suboffsets, Py_ssize_t *indices) {
char *pointer = (char*)buf;
int i;
for (i = 0; i < ndim; i++) {
pointer += strides[i] * indices[i];
if (suboffsets[i] >=0 ) {
pointer = *((char**)pointer) + suboffsets[i];
}
}
return (void*)pointer;
}