:[‘坤坤’, ‘唱’, ‘跳’, 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}
-
-
小结
-
所有迭代对象都可以进行解包操作
-
“*和**表达式”是处理变长对象的利器
-
以下内容仅作为帮助理解解包过程
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; }
-
总结
- python 中对list 和 tunp对象有原生支持
- 可迭代对象均可以通过unpack_iterable函数进行解析。
- 解包操作是c语言实现效率很高。
再来一段,关于a,b=b,a的辟谣
很多博客上写这种交换操作也是解包
-
让我们尝试一下获取它的字节码
1
2
3
4
5
6
7a, 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
7TARGET(ROT_TWO) { PyObject *top = TOP(); PyObject *second = SECOND(); SET_TOP(second); SET_SECOND(top); FAST_DISPATCH(); }
这里就不做展开, 有兴趣的小伙伴可以自行查阅源码哦