python-解包完全攻略

:[‘坤坤’, ‘唱’, ‘跳’, rap’, … ‘篮球’, ‘练习生’ ] 你觉得这样的列表怎么提取姓名身份和爱好比较好呢?

:你这个数据有点过时啊,哎,算了。。。 试试解包吧。

[TOC]

Python解包–可迭代对象分解为单独变量

​ 代码均为python3

​ 第一段阅读时间为2min(主要内容)

  • 固定元素个数列表等可迭代对象解包

    1
    2
    3
    4
    5
    6
    7
    8
    # 直接对应元素位置进行操作即可
    a, b = (1, 2)
    a, b = [1, 2]
    # 如果元素数量不匹配会抛出如下异常
    >>> a, b = [1, 2, 3]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: too many values to unpack (expected 2)
    
  • 如果想丢弃固定位置的变量python没有提供特殊的语法去处理,一般使用不会使用到的变量做占位

    1
    a, _, b = [1, 2, 3]
    
  • 任意长度的可迭代对象分解元素

    这种时候我们就要使用python的“*表达式” *式语法对于变长序列尤为有用 让我们来解决上面的问题吧

    1
    2
    3
    4
    5
    6
    7
    8
    # *式语法简直量身定做
    >>> name, *love, occupation = ['坤坤', '唱', '跳', 'rap', '篮球', '练习生' ]
    >>> name
    '坤坤'
    >>> occupation
    '练习生'
    >>> love
    ['唱', '跳', 'rap', '篮球']
    
  • 其他例子

    • 所有实现__next__方法的对象都支持解包操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      # tips:所有实现__next__方法的对象都支持解包操作
      >>> a, b, c, d, e = 'hello'
      >>> b
      'e'
      # 字典对象也可以使用解包方法 只会保存key值
      >>> a = {"a": 1, "b": 2}
      >>> c, d = a
      >>> c
      'a'
      >>> d
      'b'
      
    • Python3.5以上支持多个解包,而且可以用在表达式中。

      举个例子

      1
      2
      3
      4
      5
      # python3.5以下
      a = {"a" = 1}
      b = {"b" = 2}
      c = copy.deepcoy(a)
      c.update(c)
      
      1
      2
      # 更新之后我们就可以直接这样操作
      c = {**a, **b}
      
  • 小结

    1. 所有迭代对象都可以进行解包操作

    2. “*和**表达式”是处理变长对象的利器


以下内容仅作为帮助理解解包过程

python解包进阶 – 源码解析

  • 解包是如何操作?

    1
    2
    3
    4
    5
    6
    7
    8
    >>> a, b = [1, 2]
    # 以下为此解包操作的字节码
    0 LOAD_CONST               1 (1)
    2 LOAD_CONST               2 (2)
    4 BUILD_LIST               2
    6 UNPACK_SEQUENCE          2
    8 STORE_FAST               0 (a)
    10 STORE_FAST              1 (b)
    
  • 核心操作很明显是UNPACK_SEQUENCE所以我们称这个过程为解包

  • 大致功能为 把栈顶元素打包成单独的计数值,然后再把这些值从右到左放入堆栈中

    在源码中的实现为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 源码版本为3.7.4
    // ceval.c
    TARGET(UNPACK_SEQUENCE) {
    PyObject *seq = POP(), *item, **items;
    if (PyTuple_CheckExact(seq) &&
        PyTuple_GET_SIZE(seq) == oparg) {
        items = ((PyTupleObject *)seq)->ob_item;
        while (oparg--) {
            item = items[oparg];
            Py_INCREF(item);
            PUSH(item);
        }
    } else if (PyList_CheckExact(seq) &&
                PyList_GET_SIZE(seq) == oparg) {
        items = ((PyListObject *)seq)->ob_item;
        while (oparg--) {
            item = items[oparg];
            Py_INCREF(item);
            PUSH(item);
        }
    } else if (unpack_iterable(seq, oparg, -1,
                                stack_pointer + oparg)) {
        STACKADJ(oparg);
    } else {
        /* unpack_iterable() raised an exception */
        Py_DECREF(seq);
        goto error;
    }
    Py_DECREF(seq);
    DISPATCH();
    }
    
    • 可以看出python对元祖和列表类型的内置对象有直接的操作,其他对象是使用 unpack_iterable函数进行解包操作。
  • *的解包方法为UNPACK_EX

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 源码版本为3.7.4
    // ceval.c
    TARGET(UNPACK_EX) {
      int totalargs = 1 + (oparg & 0xFF) + (oparg >> 8);
      PyObject *seq = POP();
      
      if (unpack_iterable(seq, oparg & 0xFF, oparg >> 8,
                          stack_pointer + totalargs)) {
        stack_pointer += totalargs;
      } else {
        Py_DECREF(seq);
        goto error;
      }
      Py_DECREF(seq);
      DISPATCH();
    }
    
    • 其实同样是使用unpack_iterable进行解包操作
  • unpack_iterable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    // 源码版本为3.7.4
    // ceval.c
      
    /* Iterate v argcnt times and store the results on the stack (via decreasing
       sp).  Return 1 for success, 0 if error.
      
       If argcntafter == -1, do a simple unpack. If it is >= 0, do an unpack
       with a variable target.
    */
    unpack_iterable(PyObject *v, int argcnt, int argcntafter, PyObject **sp)
    {
        int i = 0, j = 0;
        Py_ssize_t ll = 0;
        PyObject *it;  /* iter(v) */
        PyObject *w;
        PyObject *l = NULL; /* variable list */
      
        assert(v != NULL);
      		
        // 获取python的迭代器
      	// 相当于iter(o)
        // __iter__
        it = PyObject_GetIter(v);
        // 如果无法迭代抛出异常(PyExc_TypeError)
        if (it == NULL) {
            if (PyErr_ExceptionMatches(PyExc_TypeError) &&
                v->ob_type->tp_iter == NULL && !PySequence_Check(v))
            {
                PyErr_Format(PyExc_TypeError,
                             "cannot unpack non-iterable %.200s object",
                             v->ob_type->tp_name);
            }
            return 0;
        }
      		
      	// argcnt为迭代次数
        for (; i < argcnt; i++) {
           // __next__
           // 没有此方法也会抛出异常
            w = PyIter_Next(it);
            if (w == NULL) {
                /* Iterator done, via error or exhaustion. */
                if (!PyErr_Occurred()) {
                    if (argcntafter == -1) {
                        PyErr_Format(PyExc_ValueError,
                            "not enough values to unpack (expected %d, got %d)",
                            argcnt, i);
                    }
                    else {
                        PyErr_Format(PyExc_ValueError,
                            "not enough values to unpack "
                            "(expected at least %d, got %d)",
                            argcnt + argcntafter, i);
                    }
                }
                goto Error;
            }
            *--sp = w;
        }
      		
      	// argcntafter 为-1 执行简单的解包
        if (argcntafter == -1) {
            /* We better have exhausted the iterator now. */
            w = PyIter_Next(it);
            if (w == NULL) {
                if (PyErr_Occurred())
                    goto Error;
                Py_DECREF(it);
                return 1;
            }
            Py_DECREF(w);
            // 看到这熟悉的异常了么,忘记的话看看上面的代码👆
            PyErr_Format(PyExc_ValueError,
                "too many values to unpack (expected %d)",
                argcnt);
            goto Error;
        }
      		
        // 等效python list()
      	// a, *b, c = [1, 2, 3, 4]
        // 还记得坤坤的例子么
        l = PySequence_List(it);
        if (l == NULL)
            goto Error;
        *--sp = l;
        i++;
      
        ll = PyList_GET_SIZE(l);
        if (ll < argcntafter) {
            PyErr_Format(PyExc_ValueError,
                "not enough values to unpack (expected at least %d, got %zd)",
                argcnt + argcntafter, argcnt + ll);
            goto Error;
        }
      
        /* Pop the "after-variable" args off the list. */
        for (j = argcntafter; j > 0; j--, i++) {
            *--sp = PyList_GET_ITEM(l, ll - j);
        }
        /* Resize the list. */
        Py_SIZE(l) = ll - argcntafter;
        Py_DECREF(it);
        return 1;
      
    Error:
        for (; i > 0; i--, sp++)
            Py_DECREF(*sp);
        Py_XDECREF(it);
        return 0;
    }
    
  • 总结

    1. python 中对list 和 tunp对象有原生支持
    2. 可迭代对象均可以通过unpack_iterable函数进行解析。
    3. 解包操作是c语言实现效率很高。

再来一段,关于a,b=b,a的辟谣

很多博客上写这种交换操作也是解包

  • 让我们尝试一下获取它的字节码

    1
    2
    3
    4
    5
    6
    7
    a, b = b, a
      
    0 LOAD_FAST                1 (b)
    2 LOAD_FAST                0 (a)
    4 ROT_TWO
    6 STORE_FAST               0 (a)
    8 STORE_FAST               1 (b)
    

    其实仅仅是执行了ROT_TWO操作即为将栈顶两个值交换

    1
    2
    3
    4
    5
    6
    7
    TARGET(ROT_TWO) {
    PyObject *top = TOP();
    PyObject *second = SECOND();
    SET_TOP(second);
    SET_SECOND(top);
    FAST_DISPATCH();
    }
    

    这里就不做展开, 有兴趣的小伙伴可以自行查阅源码哦