自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

剖析字節(jié)碼指令,以及 Python 賦值語句的原理

開發(fā) 前端
雖然在 Python 里面用于比較的魔法方法有多個,比如 __eq__、__le__、__gt__ 等等。但在底層,它們都對應(yīng) tp_richcompare,至于具體是哪一種,則由參數(shù)控制。所以我們實現(xiàn)任意一個用于比較的魔法方法,底層都會實現(xiàn) tp_richcompare。

楔子

前面我們考察了虛擬機執(zhí)行字節(jié)碼指令的原理,那么本篇文章就來看看這些指令對應(yīng)的邏輯是怎樣的,每個指令都做了哪些事情。當然啦,由于字節(jié)碼指令有兩百多個,我們沒辦法逐一分析,這里會介紹一些常見的。至于其它的指令,會隨著學習的深入,慢慢揭曉。

介紹完常見指令之后,我們會探討 Python 賦值語句的背后原理,并分析它們的差異。

常用指令

有一部分指令出現(xiàn)的頻率極高,非常常用,我們來看一下。

我們舉例說明:

import dis

name = "古明地覺"

def foo():
    age = 16
    print(age)
    global name
    print(name)
    name = "古明地戀"

dis.dis(foo)
"""
  1           0 RESUME                   0

  2           2 LOAD_CONST               1 (16)
              4 STORE_FAST               0 (age)

  3           6 LOAD_GLOBAL              1 (NULL + print)
             16 LOAD_FAST                0 (age)
             18 CALL                     1
             26 POP_TOP

  5          28 LOAD_GLOBAL              1 (NULL + print)
             38 LOAD_GLOBAL              2 (name)
             48 CALL                     1
             56 POP_TOP

  6          58 LOAD_CONST               2 ('古明地戀')
             60 STORE_GLOBAL             1 (name)
             62 RETURN_CONST             0 (None)
"""

我們看到 age = 16 對應(yīng)兩條字節(jié)碼指令。

  • LOAD_CONST:加載一個常量,這里是 16;
  • STORE_FAST:在局部作用域中創(chuàng)建一個局部變量,這里是 age;

print(age) 對應(yīng)四條字節(jié)碼指令。

  • LOAD_GLOBAL:在局部作用域中加載一個全局變量或內(nèi)置變量,這里是 print;
  • LOAD_FAST:在局部作用域中加載一個局部變量,這里是 age;
  • CALL:函數(shù)調(diào)用;
  • POP_TOP:從棧頂彈出返回值;

print(name) 對應(yīng)兩條字節(jié)碼指令。

  • LOAD_GLOBAL:在局部作用域中加載一個全局變量或內(nèi)置變量,這里是 print;
  • LOAD_GLOBAL:在局部作用域中加載一個全局變量或內(nèi)置變量,這里是 name;
  • CALL:函數(shù)調(diào)用;
  • POP_TOP:從棧頂彈出返回值;

name = "古明地戀" 對應(yīng)兩條字節(jié)碼指令。

  • LOAD_CONST:加載一個常量,這里是 "古明地戀";
  • STORE_GLOBAL:在局部作用域中創(chuàng)建一個 global 關(guān)鍵字聲明的全局變量,這里是 name;

這些指令非常常見,因為它們和常量、變量的加載,以及變量的定義密切相關(guān),你寫的任何代碼在反編譯之后都少不了它們的身影。

注:不管加載的是常量、還是變量,得到的永遠是指向?qū)ο蟮闹羔槨?/p>

變量賦值的具體細節(jié)

這里再通過變量賦值感受一下字節(jié)碼的執(zhí)行過程,首先關(guān)于變量賦值,你平時是怎么做的呢?

圖片圖片

這些賦值語句背后的原理是什么呢?我們通過字節(jié)碼來逐一回答。

1)a, b = b, a 的背后原理是什么?

想要知道背后的原理,查看它的字節(jié)碼是我們最好的選擇。

0 RESUME                   0

     2 LOAD_NAME                0 (b)
     4 LOAD_NAME                1 (a)
     6 SWAP                     2
     8 STORE_NAME               1 (a)
    10 STORE_NAME               0 (b)
    12 RETURN_CONST             0 (None)

里面關(guān)鍵的就是 SWAP 指令,雖然我們還沒看這個指令,但也能猜出來它負責交換棧里面的兩個元素。假設(shè) a 和 b 的值分別為 22、33,看一下運行時棧的變化過程。

圖片圖片

示意圖還是很好理解的,關(guān)鍵就在于 SWAP 指令,它是怎么交換元素的呢?

TARGET(SWAP) {
    // 獲取棧頂元素
    PyObject *top = stack_pointer[-1];
    // oparg 表示交換的元素個數(shù)
    // 所以 stack_pointer[-oparg] 表示獲取棧底元素
    PyObject *bottom = stack_pointer[-(2 + (oparg-2))];
    #line 3389 "Python/bytecodes.c"
    assert(oparg >= 2);
    #line 4680 "Python/generated_cases.c.h"
    // 將棧頂元素和棧頂元素進行交換
    stack_pointer[-1] = bottom;
    stack_pointer[-(2 + (oparg-2))] = top;
    DISPATCH();
}

執(zhí)行 SWAP 指令之前,棧里有兩個元素,棧頂元素是 a,棧底元素是 b。執(zhí)行 SWAP 指令之后,棧頂元素是 b,棧底元素是 a。然后后面的兩個 STORE_NAME 會將棧里面的元素 b、a 依次彈出,賦值給 a、b,從而完成變量交換。

2)a, b, c = c, b, a 的背后原理是什么?

老規(guī)矩,還是查看字節(jié)碼,因為一切真相都隱藏在字節(jié)碼當中。

0 RESUME                   0

     2 LOAD_NAME                0 (c)
     4 LOAD_NAME                1 (b)
     6 LOAD_NAME                2 (a)
     8 SWAP                     3
    10 STORE_NAME               2 (a)
    12 STORE_NAME               1 (b)
    14 STORE_NAME               0 (c)
    16 RETURN_CONST             0 (None)

整個過程和 a, b = b, a 是相似的,首先按照從左往右的順序,將等號右邊的變量依次壓入棧中,然后調(diào)用 SWAP 指令交換棧頂和棧底的元素。最后將棧里的元素彈出,按照從左往右的順序,依次賦值給等號左邊的變量。

所以 SWAP 適用于兩個或三個變量之間的交換,兩個變量交換很好理解,關(guān)鍵是三個變量交換,依舊只需要一個 SWAP 指令,因為中間的元素是不需要動的。

3)a, b, c, d = d, c, b, a 的背后原理是什么?它和上面提到的 1)和 2)有什么區(qū)別呢?

我們還是看一下字節(jié)碼。

0 RESUME                   0

     2 LOAD_NAME                0 (d)
     4 LOAD_NAME                1 (c)
     6 LOAD_NAME                2 (b)
     8 LOAD_NAME                3 (a)
    10 BUILD_TUPLE              4
    12 UNPACK_SEQUENCE          4
    16 STORE_NAME               3 (a)
    18 STORE_NAME               2 (b)
    20 STORE_NAME               1 (c)
    22 STORE_NAME               0 (d)
    24 RETURN_CONST             0 (None)

將等號右邊的變量,按照從左往右的順序,依次壓入棧中,但此時沒有直接將棧里面的元素做交換,而是構(gòu)建一個元組。因為往棧里面壓入了四個元素,所以 BUILD_TUPLE 后面的 oparg 是 4,表示構(gòu)建長度為 4 的元組。

TARGET(BUILD_TUPLE) {
    // stack_pointer 指向運行時棧的棧頂,oparg 表示運行時棧的元素個數(shù)
    // 那么 stack_pointer - oparg 便指向運行時棧的棧底
    PyObject **values = (stack_pointer - oparg);
    PyObject *tup;  // 指向創(chuàng)建的元組
    #line 1489 "Python/bytecodes.c"
    // 運行時棧本質(zhì)上就是個數(shù)組,索引從小到大的方向表示棧底到棧頂?shù)姆较?    // 當執(zhí)行 a, b, c, d = d, c, b, a 時,會將右側(cè)的變量依次入棧
    // 運行時棧里的元素從棧底到棧頂依次是 d、c、b、a
    // 拷貝數(shù)組(運行時棧)里的元素,創(chuàng)建元組,結(jié)果是 (d, c, b, a)
    tup = _PyTuple_FromArraySteal(values, oparg);
    if (tup == NULL) { STACK_SHRINK(oparg); goto error; }
    #line 2038 "Python/generated_cases.c.h"
    // 清空運行時棧
    STACK_SHRINK(oparg);
    // 然后將 tup 入棧
    STACK_GROW(1);
    stack_pointer[-1] = tup;
    DISPATCH();
}

// Object/tupleobject.c
PyObject *
_PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n)
{
    if (n == 0) {
        return tuple_get_empty();
    }
    // 申請長度為 n 的元組
    PyTupleObject *tuple = tuple_alloc(n);
    // ...
    PyObject **dst = tuple->ob_item;
    // 從 0 開始,將數(shù)組里的元組依次拷貝到元組中
    for (Py_ssize_t i = 0; i < n; i++) {
        PyObject *item = src[i];
        dst[i] = item;
    }
    _PyObject_GC_TRACK(tuple);
    return (PyObject *)tuple;
}

此時棧里面只有一個元素,指向一個元組。接下來是 UNPACK_SEQUENCE,負責對序列進行解包,它的指令參數(shù)也是 4,表示要解包的序列的長度為 4,我們來看看它的邏輯。

TARGET(UNPACK_SEQUENCE) {
    PREDICTED(UNPACK_SEQUENCE);
    // 獲取棧頂元素,也就是上一步創(chuàng)建的元組:(d, c, b, a)
    PyObject *seq = stack_pointer[-1];
    #line 1057 "Python/bytecodes.c"
    // ...
    // 將元組里的元素彈出,并依次入棧,此時方向和之前是相反的
    PyObject **top = stack_pointer + oparg - 1;
    int res = unpack_iterable(tstate, seq, oparg, -1, top);
    #line 1462 "Python/generated_cases.c.h"
    Py_DECREF(seq);
    #line 1070 "Python/bytecodes.c"
    if (res == 0) goto pop_1_error;
    #line 1466 "Python/generated_cases.c.h"
    STACK_SHRINK(1);
    STACK_GROW(oparg);
    next_instr += 1;
    DISPATCH();
}

假設(shè)變量 a b c d 的值分別為 1 2 3 4,我們畫圖來描述一下整個過程。

圖片圖片

可以看到當交換的變量多了之后,不會直接在運行時棧里面操作,而是將棧里面的元素挨個彈出、構(gòu)建元組(準確的說應(yīng)該是先構(gòu)建元組,然后再清空運行時棧)。接著再按照指定順序,將元組里面的元素重新壓到棧里面。

當然不管是哪一種做法,Python 在進行變量交換時所做的事情是不變的,核心分為三步。

  • 1)將等號右邊的變量,按照從左往右的順序,依次壓入棧中;
  • 2)對運行時棧里面元素的順序進行調(diào)整;
  • 3)將運行時棧里面的元素挨個彈出,還是按照從左往右的順序,再依次賦值給等號左邊的變量;

只不過當變量不多時,調(diào)整元素位置會直接基于棧進行操作。而當達到四個時,則需要借助元組。

然后多元賦值也是同理,比如 a, b, c = 1, 2, 3,看一下它的字節(jié)碼。

0 RESUME                   0

     2 LOAD_CONST               0 ((1, 2, 3))
     4 UNPACK_SEQUENCE          3
     8 STORE_NAME               0 (a)
    10 STORE_NAME               1 (b)
    12 STORE_NAME               2 (c)
    14 RETURN_CONST             1 (None)

元組直接作為一個常量被加載進來了,然后解包,再依次賦值。運行時棧變化如下:

圖片圖片

沒有任何問題,以上就是多元賦值的原理。

4)a, b, c, d = d, c, b, a 和 a, b, c, d = [d, c, b, a] 有區(qū)別嗎?

答案是沒有區(qū)別,兩者在反編譯之后對應(yīng)的字節(jié)碼指令只有一處不同。

0 RESUME                   0

     2 LOAD_NAME                0 (d)
     4 LOAD_NAME                1 (c)
     6 LOAD_NAME                2 (b)
     8 LOAD_NAME                3 (a)
    10 BUILD_LIST               4
    12 UNPACK_SEQUENCE          4
    16 STORE_NAME               3 (a)
    18 STORE_NAME               2 (b)
    20 STORE_NAME               1 (c)
    22 STORE_NAME               0 (d)
    24 RETURN_CONST             0 (None)

前者是 BUILD_TUPLE,現(xiàn)在變成了 BUILD_LIST,其它部分一模一樣,所以兩者的效果是相同的。當然啦,由于元組的構(gòu)建比列表快一些,因此還是推薦第一種寫法。

5)a = b = c = 123 背后的原理是什么?

如果變量 a、b、c 指向的值相同,比如都是 123,那么便可以通過這種方式進行鏈式賦值。那么它背后是怎么做的呢?

0 RESUME                   0

     2 LOAD_CONST               0 (123)
     4 COPY                     1
     6 STORE_NAME               0 (a)
     8 COPY                     1
    10 STORE_NAME               1 (b)
    12 STORE_NAME               2 (c)
    14 RETURN_CONST             1 (None)

出現(xiàn)了一個新的字節(jié)碼指令 COPY,只要搞清楚它的作用,事情就簡單了。

TARGET(COPY) {
    // 獲取棧底元素,由于當前只有一個元素,所以它也是棧頂元素
    PyObject *bottom = stack_pointer[-(1 + (oparg-1))];
    PyObject *top;
    #line 3364 "Python/bytecodes.c"
    assert(oparg > 0);
    top = Py_NewRef(bottom);
    #line 4636 "Python/generated_cases.c.h"
    // 將元素壓入棧中,也就是將元素拷貝了一份,然后重新入棧
    STACK_GROW(1);
    stack_pointer[-1] = top;
    DISPATCH();
}

所以 COPY 干的事情就是將棧頂元素拷貝一份,再重新壓到棧里面。

圖片圖片

另外不管鏈式賦值語句中有多少個變量,模式都是一樣的,我們以 a = b = c = d = e = 123 為例:

0 RESUME                   0

     2 LOAD_CONST               0 (123)
     4 COPY                     1
     6 STORE_NAME               0 (a)
     8 COPY                     1
    10 STORE_NAME               1 (b)
    12 COPY                     1
    14 STORE_NAME               2 (c)
    16 COPY                     1
    18 STORE_NAME               3 (d)
    20 STORE_NAME               4 (e)
    22 RETURN_CONST             1 (None)

將常量 123 壓入運行時棧,然后拷貝一份,賦值給 a;再拷貝一份,賦值給 b;再拷貝一份,賦值給 c;再拷貝一份,賦值給 d;最后自身賦值給 e。

以上就是鏈式賦值的秘密,其實沒有什么好神奇的,就是將棧頂元素進行拷貝,再依次賦值。

但是這背后有一個坑,就是給變量賦的值不能是可變對象,否則容易造成 BUG。

a = b = c = {}

a["ping"] = "pong"
print(a)  # {'ping': 'pong'}
print(b)  # {'ping': 'pong'}
print(c)  # {'ping': 'pong'}

雖然 Python 一切皆對象,但對象都是通過指針來間接操作的。所以 COPY 是將字典的地址拷貝一份,而字典只有一個,因此最終 a、b、c 會指向同一個字典。

6)a is b 和 a == b 的區(qū)別是什么?

is 用于判斷兩個變量是不是引用同一個對象,也就是保存的對象的地址是否相等;而 == 則是判斷兩個變量引用的對象是否相等,等價于 a.__eq__(b) 。

Python 的變量在 C 看來只是一個指針,因此兩個變量是否指向同一個對象,等價于 C 中的兩個指針存儲的地址是否相等;

而 Python 的 ==,則需要調(diào)用 PyObject_RichCompare,來比較它們指向的對象所維護的值是否相等。

這兩個語句的字節(jié)碼指令集只有一處不同:

# a is b
     0 RESUME                   0
 
     2 LOAD_NAME                0 (a)
     4 LOAD_NAME                1 (b)
     6 IS_OP                    0
     8 POP_TOP
    10 RETURN_CONST             0 (None)

     # a == b
     0 RESUME                   0

     2 LOAD_NAME                0 (a)
     4 LOAD_NAME                1 (b)
     6 COMPARE_OP              40 (==)
    10 POP_TOP
    12 RETURN_CONST             0 (None)

我們看到 a is b 調(diào)用的指令是 IS_OP,而 == 調(diào)用的指令是 COMPARE_OP。

// Python 的 is 在 C 的層面就是比較兩個指針是否相等
TARGET(IS_OP) {
    // 獲取棧頂?shù)膬蓚€元素
    PyObject *right = stack_pointer[-1];
    PyObject *left = stack_pointer[-2];
    PyObject *b;
    #line 2088 "Python/bytecodes.c"
    // 進行比較,即 left == right
    int res = Py_Is(left, right) ^ oparg;
    #line 2902 "Python/generated_cases.c.h"
    Py_DECREF(left);
    Py_DECREF(right);
    #line 2090 "Python/bytecodes.c"
    // 如果相等,結(jié)果為 True,否則為 False
    b = res ? Py_True : Py_False;
    #line 2907 "Python/generated_cases.c.h"
    // 此時棧里面有兩個元素,彈出一個,然后將棧頂元素修改為比較結(jié)果
    // 為了方便,你也可以理解為:將棧里的兩個元素彈出,再將比較結(jié)果入棧
    // 效果上兩者是等價的
    STACK_SHRINK(1);
    stack_pointer[-1] = b;
    DISPATCH();
}


TARGET(COMPARE_OP) {
    PREDICTED(COMPARE_OP);
    // 獲取棧里的兩個元素
    PyObject *right = stack_pointer[-1];
    PyObject *left = stack_pointer[-2];
    PyObject *res;
    // ...
    assert((oparg >> 4) <= Py_GE);
    // 調(diào)用 PyObject_RichCompare 函數(shù)進行比較
    res = PyObject_RichCompare(left, right, oparg>>4);
    #line 2813 "Python/generated_cases.c.h"
    Py_DECREF(left);
    Py_DECREF(right);
    #line 2038 "Python/bytecodes.c"
    if (res == NULL) goto pop_2_error;
    #line 2818 "Python/generated_cases.c.h"
    // 將比較結(jié)果入棧
    STACK_SHRINK(1);
    stack_pointer[-1] = res;
    next_instr += 1;
    DISPATCH();
}

這里我們再看一下 PyObject_RichCompare 函數(shù),看看底層是怎么比較的。

// Include/object.h
#define Py_LT 0
#define Py_LE 1
#define Py_EQ 2
#define Py_NE 3
#define Py_GT 4
#define Py_GE 5

// Objects/object.c
int _Py_SwappedOp[] = {Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE};
static const char * const opstrings[] = {"<", "<=", "==", "!=", ">", ">="};

PyObject *
PyObject_RichCompare(PyObject *v, PyObject *w, int op)
{
    // ...
    // 調(diào)用了 do_richcompare
    PyObject *res = do_richcompare(tstate, v, w, op);
    _Py_LeaveRecursiveCallTstate(tstate);
    return res;
}

static PyObject *
do_richcompare(PyThreadState *tstate, PyObject *v, PyObject *w, int op)
{
    // 類型對象在底層有一個 tp_richcompare 字段,它負責實現(xiàn)比較邏輯
    // 另外在 Python 里面每個操作符都對應(yīng)一個魔法方法
    // 而在底層,所有的比較操作符都由 tp_richcompare 實現(xiàn)
    richcmpfunc f;  // 比較函數(shù)
    PyObject *res;
    int checked_reverse_op = 0;
    // 如果 v 和 w 不是同一種類型,并且 type(w) 是 type(v) 的子類
    // 那么優(yōu)先查找 type(w) 的 tp_richcompare,如果有則調(diào)用
    if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
        PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
        (f = Py_TYPE(w)->tp_richcompare) != NULL) {
        checked_reverse_op = 1;
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    // 否則查找 type(v) 的 tp_richcompare,如果有則調(diào)用
    if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    // 前面兩個條件都不滿足,那么查找 type(w) 的 tp_richcompare
    if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    // 如果以上條件都不滿足,說明沒有實現(xiàn)比較操作
    // 那么檢測操作符是否是 == 或 !=
    // 因為對于這兩個操作符,不管什么類型,都是合法的
    // 此時會比較它們的內(nèi)存地址
    switch (op) {
    case Py_EQ:
        res = (v == w) ? Py_True : Py_False;
        break;
    case Py_NE:
        res = (v != w) ? Py_True : Py_False;
        break;
    default:
        // 如果沒實現(xiàn)比較操作,并且操作符也不是 == 和 !=
        // 那么報錯,這兩個實例之間無法進行比較
        _PyErr_Format(tstate, PyExc_TypeError,
                "'%s' not supported between instances of '%.100s' and '%.100s'",
                opstrings[op],
                Py_TYPE(v)->tp_name,
                Py_TYPE(w)->tp_name);
        return NULL;
    }
    return Py_NewRef(res);
}

雖然在 Python 里面用于比較的魔法方法有多個,比如 __eq__、__le__、__gt__ 等等。但在底層,它們都對應(yīng) tp_richcompare,至于具體是哪一種,則由參數(shù)控制。所以我們實現(xiàn)任意一個用于比較的魔法方法,底層都會實現(xiàn) tp_richcompare。

至于 tp_richcompare 具體支持多少種操作符,則取決于實現(xiàn)了幾個魔法方法,比如我們只實現(xiàn)了 __eq__,但操作符為 Py_ET,那么就會拋出 Py_NotImplemented。

我們實際舉個栗子:

a = 3.14
b = float("3.14")
print(a is b)  # False
print(a == b)  # True

a 和 b 都是 3.14,兩者是相等的,但不是同一個對象。

反過來也是如此,如果 a is b 成立,那么 a == b 也不一定成立??赡苡腥撕闷?,a is b 成立說明 a 和 b 指向的是同一個對象,那么 a == b 表示該對象和自己進行比較,結(jié)果應(yīng)該始終是相等的呀,為啥也不一定成立呢?以下面兩種情況為例:

class Girl:

    def __eq__(self, other):
        return False

g = Girl()
print(g is g)  # True
print(g == g)  # False

__eq__ 返回 False,此時雖然是同一個對象,但是兩者不相等。

import math
import numpy as np

a = float("nan")
b = math.nan
c = np.nan

print(a is a, a == a)  # True False
print(b is b, b == b)  # True False
print(c is c, c == c)  # True False

nan 是一個特殊的浮點數(shù),意思是 not a number(不是一個數(shù)字),用于表示空值。而 nan 和所有數(shù)字的比較結(jié)果均為 False,即使是和它自身比較。

但需要注意的是,在使用 == 進行比較的時候雖然是不相等的,但如果放到容器里面就不一定了。舉個例子:

import numpy as np

lst = [np.nan, np.nan, np.nan]
print(lst[0] == np.nan)  # False
print(lst[1] == np.nan)  # False
print(lst[2] == np.nan)  # False
# lst 里面的三個元素和 np.nan 均不相等

# 但是 np.nan 位于列表中,并且數(shù)量是 3
print(np.nan in lst)  # True
print(lst.count(np.nan))  # 3

出現(xiàn)以上結(jié)果的原因就在于,元素被放到了容器里,而容器的一些 API 在比較元素時會先判定地址是否相同,即:是否指向了同一個對象。如果是,直接認為相等;否則,再去比較對象維護的值是否相等。

可以理解為先進行 is 判斷,如果結(jié)果為 True,直接判定兩者相等;如果 is 操作的結(jié)果不為 True,再進行 == 判斷。

因此 np.nan in lst 的結(jié)果為 True,lst.count(np.nan) 的結(jié)果是 3,因為它們會先比較對象的地址。地址相同,則直接認為對象相等。

在用 pandas 做數(shù)據(jù)處理的時候,nan 是一個非常容易坑的地方。

提到 is 和 ==,那么問題來了,在和 True、False、None 比較時,是用 is 還是用 == 呢?

由于 True、False、None 它們不僅是關(guān)鍵字,而且也被看做是一個常量,最重要的是它們都是單例的,所以我們應(yīng)該用 is 判斷。

另外 is 在底層只需要一個 == 即可完成,這是非常簡單的低級操作,而 Python 的 == 在底層則需要調(diào)用 PyObject_RichCompare 函數(shù)。因此 is 在速度上也更有優(yōu)勢,比函數(shù)調(diào)用要快。

小結(jié)

以上我們就分析了常見的幾個指令,以及變量賦值的底層邏輯,怎么樣,是不是對 Python 有更深的理解了呢。

責任編輯:武曉燕 來源: 古明地覺的編程教室
相關(guān)推薦

2021-12-09 22:36:30

Java 字節(jié)碼頁緩存

2020-04-21 12:09:47

JVM消化字節(jié)碼

2010-01-19 15:42:30

VB.NET賦值語句

2022-05-05 10:00:53

Kafka分區(qū)分配Linux

2010-03-12 14:28:45

Python if語句

2021-05-28 23:04:23

Python利器執(zhí)行

2009-12-31 11:37:05

MPLS網(wǎng)絡(luò)

2024-11-01 16:05:26

2010-01-06 16:16:14

華為交換機vlan配置

2010-09-06 12:50:09

PPP鏈路

2016-12-19 14:35:32

Spark Strea原理剖析數(shù)據(jù)

2019-10-30 08:45:21

JS代碼NodeJS

2011-12-01 14:56:30

Java字節(jié)碼

2022-03-30 10:10:17

字節(jié)碼??臻g

2013-09-17 10:35:17

Python執(zhí)行原理

2010-03-22 12:40:48

Python代碼加密

2009-09-14 10:35:15

Linq內(nèi)部執(zhí)行原理

2010-09-25 10:20:05

JAVA字節(jié)碼

2009-09-07 16:25:14

Linq To SQL

2009-10-21 16:00:26

VB.NET CASE
點贊
收藏

51CTO技術(shù)棧公眾號