91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python內建類型bytes實例代碼分析

發布時間:2022-05-18 17:22:50 來源:億速云 閱讀:242 作者:iii 欄目:開發技術

這篇文章主要講解了“Python內建類型bytes實例代碼分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python內建類型bytes實例代碼分析”吧!

1 bytes和str之間的關系

不少語言中的字符串都是由字符數組(或稱為字節序列)來表示的,例如C語言:

char str[] = "Hello World!";

由于一個字節最多只能表示256種字符,要想覆蓋眾多的字符(例如漢字),就需要通過多個字節來表示一個字符,即多字節編碼。但由于原始字節序列中沒有維護編碼信息,操作不慎就很容易導致各種亂碼現象。

Python提供的解決方法是使用Unicode對象(也就是str對象),Unicode口語表示各種字符,無需關心編碼。但是在存儲或者網絡通訊時,字符串對象需要序列化成字節序列。為此,Python額外提供了字節序列對象——bytes。

str和bytes的關系如圖所示:

Python內建類型bytes實例代碼分析


str對象統一表示一個字符串,不需要關心編碼;計算機通過字節序列與存儲介質和網絡介質打交道,字節序列用bytes對象表示;存儲或傳輸str對象時,需要將其序列化成字節序列,序列化過程也是編碼的過程。

2 bytes對象的結構:PyBytesObject

C源碼:

typedef struct {
    PyObject_VAR_HEAD
    Py_hash_t ob_shash;
    char ob_sval[1];
    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     */
} PyBytesObject;

源碼分析:

字符數組ob_sval存儲對應的字符,但是ob_sval數組的長度并不是ob_size,而是ob_size + 1.這是Python為待存儲的字節序列額外分配了一個字節,用于在末尾處保存’\0’,以便兼容C字符串。

ob_shash:用于保存字節序列的哈希值。由于計算bytes對象的哈希值需要遍歷其內部的字符數組,開銷相對較大。因此Python選擇將哈希值保存起來,以空間換時間(隨處可見的思想,hh),避免重復計算。

圖示如下:

Python內建類型bytes實例代碼分析

3 bytes對象的行為

3.1 PyBytes_Type

C源碼:

PyTypeObject PyBytes_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "bytes",
    PyBytesObject_SIZE,
    sizeof(char),
    // ...
    &bytes_as_number,                           /* tp_as_number */
    &bytes_as_sequence,                         /* tp_as_sequence */
    &bytes_as_mapping,                          /* tp_as_mapping */
    (hashfunc)bytes_hash,                       /* tp_hash */
    // ...
};

數值型操作bytes_as_number:

static PyNumberMethods bytes_as_number = {
    0,              /*nb_add*/
    0,              /*nb_subtract*/
    0,              /*nb_multiply*/
    bytes_mod,      /*nb_remainder*/
};

bytes_mod:

static PyObject *
bytes_mod(PyObject *self, PyObject *arg)
{
    if (!PyBytes_Check(self)) {
        Py_RETURN_NOTIMPLEMENTED;
    }
    return _PyBytes_FormatEx(PyBytes_AS_STRING(self), PyBytes_GET_SIZE(self),
                             arg, 0);
}

可以看到,bytes對象只是借用%運算符實現字符串格式化,并不是真正意義上的數值運算(這里其實和最開始的分類標準是有點歧義的,按標準應該再分一個“格式型操作”,不過靈活處理也是必須的):

>>> b'msg: a = %d, b = %d' % (1, 2)
b'msg: a = 1, b = 2'

序列型操作bytes_as_sequence:

static PySequenceMethods bytes_as_sequence = {
    (lenfunc)bytes_length, /*sq_length*/
    (binaryfunc)bytes_concat, /*sq_concat*/
    (ssizeargfunc)bytes_repeat, /*sq_repeat*/
    (ssizeargfunc)bytes_item, /*sq_item*/
    0,                  /*sq_slice*/
    0,                  /*sq_ass_item*/
    0,                  /*sq_ass_slice*/
    (objobjproc)bytes_contains /*sq_contains*/
};

bytes支持的序列型操作包括以下5個:

  • bytes_length:查詢序列長度

  • bytes_concat:將兩個序列合并為一個

  • bytes_repeat:將序列重復多次

  • bytes_item:取出給定下標的序列元素

  • bytes_contains:包含關系判斷

關聯型操作bytes_as_mapping:

static PyMappingMethods bytes_as_mapping = {
    (lenfunc)bytes_length,
    (binaryfunc)bytes_subscript,
    0,
};

可以看到bytes支持獲取長度和切片兩個操作。

3.2 bytes_as_sequence

這里我們主要介紹以下bytes_as_sequence相關的操作

bytes_as_sequence中的操作都不復雜,但是會有一個“陷阱”,這里我們以bytes_concat操作來認識一下這個問題。C源碼如下:

/* This is also used by PyBytes_Concat() */
static PyObject *
bytes_concat(PyObject *a, PyObject *b)
{
    Py_buffer va, vb;
    PyObject *result = NULL;
    va.len = -1;
    vb.len = -1;
    if (PyObject_GetBuffer(a, &va, PyBUF_SIMPLE) != 0 ||
        PyObject_GetBuffer(b, &vb, PyBUF_SIMPLE) != 0) {
        PyErr_Format(PyExc_TypeError, "can't concat %.100s to %.100s",
                     Py_TYPE(b)->tp_name, Py_TYPE(a)->tp_name);
        goto done;
    }
    /* Optimize end cases */
    if (va.len == 0 && PyBytes_CheckExact(b)) {
        result = b;
        Py_INCREF(result);
        goto done;
    }
    if (vb.len == 0 && PyBytes_CheckExact(a)) {
        result = a;
        Py_INCREF(result);
        goto done;
    }
    if (va.len > PY_SSIZE_T_MAX - vb.len) {
        PyErr_NoMemory();
        goto done;
    }
    result = PyBytes_FromStringAndSize(NULL, va.len + vb.len);
    if (result != NULL) {
        memcpy(PyBytes_AS_STRING(result), va.buf, va.len);
        memcpy(PyBytes_AS_STRING(result) + va.len, vb.buf, vb.len);
    }
  done:
    if (va.len != -1)
        PyBuffer_Release(&va);
    if (vb.len != -1)
        PyBuffer_Release(&vb);
    return result;
}

bytes_concat源碼大家可自行分析,這里直接以圖示形式來展示,主要是為了說明其中的“陷阱”。圖示如下:

Python內建類型bytes實例代碼分析

  • Py_buffer提供了一套操作對象緩沖區的統一接口,屏蔽不同類型對象的內部差異

  • bytes_concat則將兩個對象的緩沖區拷貝到一起,形成新的bytes對象

上述的拷貝過程是比較清晰的,但是這里隱藏著一個問題——數據拷貝的陷阱。

以合并3個bytes對象為例:

>>> a = b'abc'
>>> b = b'def'
>>> c = b'ghi'
>>> result = a + b + c
>>> result
b'abcdefghi'

本質上這個過程會合并兩次

>>> t = a + b
>>> result = t + c

在這個過程中,a和b的數據都會被拷貝兩遍,圖示如下:

Python內建類型bytes實例代碼分析


不難推出,合并n個bytes對象,頭兩個對象需要拷貝n - 1次,只有最后一個對象不需要重復拷貝,平均下來每個對象大約要拷貝n/2次。因此,下面的代碼:

>>> result = b''
>>> for b in segments:
    	result += s

效率是很低的。我們可以使用join()來優化:

>>> result = b''.join(segments)

join()方法是bytes對象提供的一個內建方法,可以高效合并多個bytes對象。join方法對數據拷貝進行了優化:先遍歷待合并對象,計算總長度;然后根據總長度創建目標對象;最后再遍歷待合并對象,逐一拷貝數據。這樣一來,每個對象只需要拷貝一次,解決了重復拷貝的陷阱。(具體源碼大家可以自行去查看)

4 字符緩沖池

和小整數一樣,字符對象(即單字節的bytes對象)數量也很少,只有256個,但使用頻率非常高,因此以空間換時間能明顯提升執行效率。字符緩沖池源碼如下:

static PyBytesObject *characters[UCHAR_MAX + 1];

下面我們從創建bytes對象的過程來看一下字符緩沖池的使用:PyBytes_FromStringAndSize()函數是負責創建bytes對象的通用接口,源碼如下:

PyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
{
    PyBytesObject *op;
    if (size < 0) {
        PyErr_SetString(PyExc_SystemError,
            "Negative size passed to PyBytes_FromStringAndSize");
        return NULL;
    }
    if (size == 1 && str != NULL &&
        (op = characters[*str & UCHAR_MAX]) != NULL)
    {
#ifdef COUNT_ALLOCS
        one_strings++;
#endif
        Py_INCREF(op);
        return (PyObject *)op;
    }
    op = (PyBytesObject *)_PyBytes_FromSize(size, 0);
    if (op == NULL)
        return NULL;
    if (str == NULL)
        return (PyObject *) op;
    memcpy(op->ob_sval, str, size);
    /* share short strings */
    if (size == 1) {
        characters[*str & UCHAR_MAX] = op;
        Py_INCREF(op);
    }
    return (PyObject *) op;
}

其中涉及字符緩沖區維護的關鍵步驟如下:

第10~17行:如果創建的對象為單字節對象,會先在characters數組的對應序號判斷是否已經有相應的對象存儲在了緩沖區中,如果有則直接取出

第28~31行:如果創建的對象為單字節對象,并且之前已經判斷了不在緩沖區中,則將其放入字符緩沖池的對應位置

由此可見,當Python程序開始運行時,字符緩沖池是空的。隨著單字節bytes對象的創建,緩沖池中的對象就慢慢多了起來。當緩沖池已緩存b&rsquo;1&rsquo;、b&rsquo;2&rsquo;、b&rsquo;3&rsquo;、b&rsquo;a&rsquo;、b&rsquo;b&rsquo;、b&rsquo;c&rsquo;這幾個字符時,內部結構如下:

Python內建類型bytes實例代碼分析

示例:

注:這里大家可能在IDLE和PyCharm中獲得的結果不一致,這個問題在之前的博客中也提到過,查閱資料后得到的結論是:IDLE運行和PyCharm運行的方式不同。這里我將PyCharm代碼對應的代碼對象反編譯的結果展示給大家,但我對IDLE的認識還比較薄弱,以后有機會再給大家詳細補充這個知識(抱拳~)。這里大家還是先以認識字符緩沖區這個概念為主,當然字節碼的相關知識掌握好了也是很有幫助的。以下是PyCharm運行的結果:

以下操作的相關講解可以看這篇博客:

Python源碼學習筆記:Python程序執行過程與字節碼

示例1:

Python內建類型bytes實例代碼分析

Python內建類型bytes實例代碼分析

下面我們來看一下反編譯的結果:(下面的文件路徑我省略了,大家自己試驗的時候要輸入正確的路徑)

>>> text = open('D:\\...\\test2.py').read()
>>> result= compile(text,'D:\\...\\test2.py', 'exec')
>>> import dis
>>> dis.dis(result)
  1           0 LOAD_CONST               0 (b'a')
              2 STORE_NAME               0 (a)
  2           4 LOAD_CONST               0 (b'a')
              6 STORE_NAME               1 (b)
  3           8 LOAD_NAME                2 (print)
             10 LOAD_NAME                0 (a)
             12 LOAD_NAME                1 (b)
             14 IS_OP                    0
             16 CALL_FUNCTION            1
             18 POP_TOP
             20 LOAD_CONST               1 (None)
             22 RETURN_VALUE

可以很清晰地看到,第5行和第8行的LOAD_CONST指令操作的都是下標為0的常量b&rsquo;a&rsquo;,因此此時a和b對應的是同一個對象,我們打印看一下:

>>> result.co_consts[0]
b'a'

示例2:

為了確認只會緩存單字節的bytes對象,我在這里又嘗試了多字節的bytes對象,同樣還是在PyCharm環境下嘗試:

Python內建類型bytes實例代碼分析

Python內建類型bytes實例代碼分析

結果是比較出乎意料的:多字節的bytes對象依然是同一個。為了驗證這個想法,我們先來看一下對代碼對象的反編譯結果:

>>> text = open('D:\\...\\test3.py').read()
>>> result= compile(text,'D:\\...\\test3.py', 'exec')
>>> import dis
>>> dis.dis(result)
  1           0 LOAD_CONST               0 (b'abc')
              2 STORE_NAME               0 (a)
  2           4 LOAD_CONST               0 (b'abc')
              6 STORE_NAME               1 (b)
  3           8 LOAD_NAME                2 (print)
             10 LOAD_NAME                0 (a)
             12 LOAD_NAME                1 (b)
             14 IS_OP                    0
             16 CALL_FUNCTION            1
             18 POP_TOP
             20 LOAD_CONST               1 (None)
             22 RETURN_VALUE
>>> result.co_consts[0]
b'abc'

可以看到,反編譯的結果和單字節的bytes對象沒有區別。

感謝各位的閱讀,以上就是“Python內建類型bytes實例代碼分析”的內容了,經過本文的學習后,相信大家對Python內建類型bytes實例代碼分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

集安市| 苏州市| 志丹县| 新沂市| 卓尼县| 治县。| 东光县| 二连浩特市| 扎囊县| 和田县| 平和县| 泰来县| 博乐市| 色达县| 河北区| 五常市| 中江县| 彰化县| 肇东市| 万载县| 海南省| 汝城县| 吉木萨尔县| 汾阳市| 启东市| 顺义区| 香格里拉县| 宣武区| 比如县| 台前县| 阜南县| 合作市| 灵川县| 曲周县| 许昌县| 望都县| 株洲县| 新巴尔虎左旗| 克山县| 广河县| 鸡东县|