惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
美团技术团队
NISL@THU
NISL@THU
C
Cisco Blogs
SecWiki News
SecWiki News
N
Netflix TechBlog - Medium
Forbes - Security
Forbes - Security
Cloudbric
Cloudbric
雷峰网
雷峰网
T
Tailwind CSS Blog
博客园 - 司徒正美
The Register - Security
The Register - Security
L
LangChain Blog
S
Security Affairs
Hacker News - Newest:
Hacker News - Newest: "LLM"
B
Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Threat Research - Cisco Blogs
I
InfoQ
S
Schneier on Security
L
Lohrmann on Cybersecurity
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Martin Fowler
Martin Fowler
Schneier on Security
Schneier on Security
F
Fortinet All Blogs
TaoSecurity Blog
TaoSecurity Blog
K
Kaspersky official blog
Google DeepMind News
Google DeepMind News
Cisco Talos Blog
Cisco Talos Blog
PCI Perspectives
PCI Perspectives
Attack and Defense Labs
Attack and Defense Labs
WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
Project Zero
Project Zero
The GitHub Blog
The GitHub Blog
D
Docker
N
News | PayPal Newsroom
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Hacker News: Front Page
云风的 BLOG
云风的 BLOG
Microsoft Security Blog
Microsoft Security Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 聂微东
Webroot Blog
Webroot Blog
MongoDB | Blog
MongoDB | Blog

Shidenggui's blog

读《庞居士研究》有感 杂诗 2 杂诗 卢曼卡片盒的本质 致《斯通纳》 古典风格 跳舞吧 无解的问题,有解的人生 杠精是如何炼成的 存在即被承认 混沌宇宙中涌现的秩序 人生革命 少见的艺术家 爱欲之死 世界围绕着心灵旋转 轻信 日常之外的可能性 第二大脑 新品发布:元思笔记 - 打造自生长的知识网络 推书君 APP 上线了!书荒找书,点评分享,尽在其中 如何通过定价充分挖掘隐藏利润? 高不确定性意味着高风险?认知的局限性才是 聪明的学习胜过大量时间的投入 经济机器是如何运行的? 瑜伽不能减肥?我一个月瘦了12斤 从租售比看,北上深的房价为什么不贵? 记一次业务中的算法应用:动态规划、图、树 Jetbrains 福利,半年全家桶或者一年半单产品(PyCharm, WebStorm等)订阅,非激活码,发放到 account BlogHub 上线了,开源独立博客聚合站,一起来发现更多有趣的灵魂 现在还有必要拥有独立博客吗?谈谈我的独立博客史 专属开源开发者的羊毛福利,作者实际到手现金 5K+ (github namebase airdrop) 24 小时开源公益黑客马拉松,开发一款疫情小程序 如何应对软件复杂度 色彩的奥秘 | 设计杂谈(二) 人生第一次翻译英文文章的心得体会 我是如何一步步打造爆款产品,以及对应方法论 | 翻译 设计杂谈(一):什么是美? 读《Elon Musk》:传奇人生之旅的开端(一) 浮生得闲,饭后偶遇桂花树 推书君网站上线了 LRUCache 的实现 编程与数学(五):Trailing zeroes in factorial 编程与数学(四):Check for integer overflow on multiplication 编程与数学(三):Newton‘s Method 编程与数学(二):Geometric Series 设计模式与 easytrader (一):策略模式 编程与数学(一):Logistic Differential Equation 跑步与数学 Talk is cheap. Show me? 读《Why We Sleep》 读《自控力》 断舍离之公式法 读《师从天才:一个科学王朝的崛起》 读《软件测试的艺术》 读《富足》
复制列表元素时 Python 做了什么
2019-04-05 · via Shidenggui's blog

缘起

而刚刚学习 Python 的人常常会遇到一个问题,如下

# 意图是获取包含 10 个空 list 的列表
>>> li = [[]] * 10

# print 出来感觉没问题
>>> li
[[], [], [], [], [], [], [], [], [], []]

# 尝试往第一个 list 添加一个元素,并打印
>>> li[0].append(1)
>>> li
[[1], [1], [1], [1], [1], [1], [1], [1], [1], [1]]

# 结果发现每个 list 里面都包含了新添加的元素,而预期中应该是
>>> li[0].append(1)
>>> li
[[1], [], [], [], [], [], [], [], [], []]

# 原因是当 Python 执行 [obj] * count 时,新的 list 中的 item 拷贝的是原 obj 的引用,而不会进行复制。
# 如果想要初始化全新的 list 的话,则需要按如下方式初始化
>>> li = [[] for _ in range(10)]
>>> li[0].append(1)
>>> li
[[1], [], [], [], [], [], [], [], [], []]

一探究竟

最近在看 《Python源码剖析》,让我们看看 [obj] * count 在 Python 底层是如何实现的。

字节码

Python 所有的代码都会编译成字节码之后执行,而类似 [obj] * count 的表达式,编译成字节码之后为

>>> import dis
>>> def test(): return [obj] * count
>>> dis.dis(test)

# 构建 [obj]
  1           0 LOAD_GLOBAL              0 (obj)
              2 BUILD_LIST               1

# 读取 count
              4 LOAD_GLOBAL              1 (count)

# 实现 [obj] * count
              6 BINARY_MULTIPLY

# 函数返回值
              8 RETURN_VALUE

具体指令实现

而 BINARY_MULTIPLY 指令在源码中的实现为

        TARGET(BINARY_MULTIPLY) {
             # 获取 count
            PyObject *right = POP();
             # 获取 [obj]
            PyObject *left = TOP();

            # 重点来了,在这里获取相乘的结果
            PyObject *res = PyNumber_Multiply(left, right);

            Py_DECREF(left);
            Py_DECREF(right);
            SET_TOP(res);
            if (res == NULL)
                goto error;
            DISPATCH();
        }

PyNumber_Multiply 实现

指令的核心便是 PyNumber_Multiply,Python 源码中对该函数的注解

/* This is the equivalent of the Python expression: o1 * o2. */
PyAPI_FUNC(PyObject *) PyNumber_Multiply(PyObject *o1, PyObject *o2);

原来 Python 中所有 o1 * o2 的操作最后都是通过该函数实现,而 [obj] * count 也不例外。

PyNumber_Multiply 的实现如下:

PyObject *
PyNumber_Multiply(PyObject *v, PyObject *w)
{
        // ......
        // v 为 [obj],w 为 count
       // v->ob_type 就是 [obj]  的类型 list,  而 tp_as_sequence 是其实现的一组与 sequence 相关的函数集合。
        PySequenceMethods *mv = v->ob_type->tp_as_sequence;
        PySequenceMethods *mw = w->ob_type->tp_as_sequence;
        Py_DECREF(result);
        
        if  (mv && mv->sq_repeat) {
            // 因为 [obj] 定义了 tp_as_sequence->sq_repeat 所以最后会进入这里
            return sequence_repeat(mv->sq_repeat, v, w);
        }
        else if (mw && mw->sq_repeat) {
            return sequence_repeat(mw->sq_repeat, w, v);
        }

    return result;
}

sequence_repeat 仅仅是简单进行了一些类型检查,最后直接 call repeatfunc,也即外界传入的 sq_repeat

static PyObject *
sequence_repeat(ssizeargfunc repeatfunc, PyObject *seq, PyObject *n)
{
    // 略过
    return (*repeatfunc)(seq, count);
}

sq_repeat 到底是什么?

该函数在 list 类型的实现为

// list_as_sequence 实现了一组与 list 相关的操作
static PySequenceMethods list_as_sequence = {
    // 比如 list 的长度
    (lenfunc)list_length,                       /* sq_length */
     // 这就是最终调用的函数
    (ssizeargfunc)list_repeat,                  /* sq_repeat */

   ...
};

list_repeat 实现源码

经过寻找,我们最后终于找到了最终实现 [obj] * count 的代码。

下面是 list_repeat 简化后的代码:

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    ...
    PyObject *elem;
    # np 指向新分配的 count 大小的 list 对象
    np = (PyListObject *) PyList_New(size);

    items = np->ob_item;

    # 如果 [obj] 的对象大小为 1 的话,我们这里就是如此
    if (Py_SIZE(a) == 1) {

        # 获取 obj,即这里的 elem
        elem = a->ob_item[0];

        for (i = 0; i < n; i++) {
            # 因为 elem 是指针,所以这里新的 list 的 items 里面包含的是原有的 obj 的引用,而不是复制
            items[i] = elem;
            Py_INCREF(elem);
        }
        return (PyObject *) np;
    }
    ...
}

所以 [obj] * count 最后获得是包含 countobj 引用的新 list

结尾

最后我们可以看到实际上最后实现该功能的函数是

obj->ob_type->tp_as_sequence->sq_repeat = list_repeat

Python 为什么经过那么多层的抽象呢?因为 C 本身并不支持 class,而 Python 本身却是万物皆对象,而基于 C 实现的 Python 自然就需要通过大量的抽象来模拟实现面向对象的功能。

结尾安利下 《Python源码剖析》,虽然书有点老了,而且基于 Python 2.5 ,但是 Python 整体的大框架并没有改变很多,还是非常值得一看。