ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python 对象变动(Mutation)全面解析:可变与不可变类型、别名陷阱与函数默认参数

Python 对象变动(Mutation)全面解析:可变与不可变类型、别名陷阱与函数默认参数 教程文档教育【免费下载链接】interpy-zh《Python进阶》Intermediate Python - Chinese Version项目地址https://gitcode.com/gh_mirrors/in/interpy-zh点击查看免费下载在 Python 中可变mutable与不可变immutable对象的行为差异是无数新手甚至部分老手踩坑的根源。本文基于《Python进阶》Intermediate Python 中文译本对象变动章节结合仓库中对应的可运行示例代码系统讲解赋值即别名这一核心机制、可变对象作为函数默认参数时造成的状态泄漏问题以及业界标准的None哨兵写法。读完本文你将能够准确预判foo bar之后数据何时会被意外修改并写出在任何场景下行为都符合预期的函数签名。一、可变与不可变Python 类型的两大阵营简单地说可变mutable意味着可以被改动不可变immutable意味着常量constant。一个对象是可变还是不可变决定了你能否在不改变对象身份identity的前提下修改其内部内容不可变类型int、float、str、tuple、frozenset等。任何修改操作实际上都会创建一个新对象原对象保持不变。可变类型list、dict、set、bytearray以及用户自定义的普通类实例等。你可以通过索引赋值、append()、update()等操作就地修改其内容对象身份始终不变。这种区分并非抽象概念它会直接反映在运行时行为上——最典型的表现就是下面这个看似违背直觉的例子。二、别名陷阱bar foo之后发生了什么foo [hi] print(foo) # Output: [hi] bar foo bar [bye] print(foo) # Output: [hi, bye]刚刚发生了什么我们预期的不是那样我们期望看到的是这样的foo [hi] print(foo) # Output: [hi] bar foo bar [bye] print(foo) # Output: [hi] print(bar) # Output: [hi, bye]这不是一个 bug这是对象可变性mutability在作怪。每当你将一个变量赋值为另一个可变类型的变量时对这个数据的任意改动会同时反映到这两个变量上去。新变量只不过是老变量的一个**别名alias**而已。要理解这一点需要区分两个概念变量只是名字Python 中的变量不直接存值而是绑定到对象上。bar foo做的事情是把bar也绑定到foo当前指向的同一个列表对象两个名字指向同一块内存中的同一个对象。对可变对象是就地操作bar [bye]等价于调用bar.extend([bye])它直接修改了那个共享列表对象的内容。因为foo和bar指向同一个对象所以通过foo观察到的内容同样发生了变化。这个行为只针对可变数据类型。如果foo是不可变对象例如整数或字符串bar foo后对bar的重新赋值只会让bar绑定到新对象foo永远不会被影响。这正是为什么理解哪些类型可变如此重要。仓库佐证可直接运行的示例脚本本仓库在 code/2.7/9_mutation.py 中提供了这一陷阱的 Python 2.7 版本可运行示例其核心片段如下foo [hi] print foo bar foo bar [bye] print foo脚本同样会打印出[hi, bye]而非[hi]。在 Python 3 环境下运行时只需将print foo改为print(foo)其余逻辑完全一致。该脚本遵循仓库 code/2.7/README.md 中章节_对应测试内容的命名约定9_即对应《数据类型》章节序号方便读者按章节对照阅读。三、可变默认参数Python 中最经典的函数陷阱别名机制只是开始它和函数默认参数结合后会引出一个让几乎所有 Python 程序员都中过招的著名陷阱。考虑下面的函数def add_to(num, target[]): target.append(num) return target add_to(1) # Output: [1] add_to(2) # Output: [1, 2] add_to(3) # Output: [1, 2, 3]你可能预期它表现得不是这样子。你可能希望当你调用add_to时有一个新的列表被创建就像这样def add_to(num, target[]): target.append(num) return target add_to(1) # Output: [1] add_to(2) # Output: [2] add_to(3) # Output: [3]啊哈这次又没有达到预期是列表的可变性在作怪。为什么默认参数会记住上次的状态关键事实是在 Python 中当函数被定义时默认参数只会运算一次而不是每次被调用时都会重新运算。具体来说def add_to(num, target[])中的[]是在def语句执行即函数定义时被创建的一个列表对象它被保存在函数对象内部。此后每次调用add_to且未显式传入target时Python 都会把同一个列表对象绑定给参数target。于是第一次调用target指向那个共享列表追加1列表变为[1]第二次调用target仍然指向同一个列表追加2列表变为[1, 2]第三次调用继续追加3列表变为[1, 2, 3]。由于列表是可变对象append就地修改了共享列表的内容于是每次调用的结果都叠加到了上一次之上。这与上一节的别名机制是同一原理的延伸默认参数对象在函数定义时被创建一次之后所有调用共享它。你应该永远不要定义可变类型的默认参数除非你知道你正在做什么。四、标准解法None哨兵惯用法正确的写法是默认参数使用不可变的None在函数体内部按需创建新的可变对象def add_to(element, targetNone): if target is None: target [] target.append(element) return target现在每当你在调用这个函数不传入target参数的时候一个新的列表会被创建。举个例子add_to(42) # Output: [42] add_to(42) # Output: [42] add_to(42) # Output: [42]每次都得到独立的[42]函数行为终于符合直觉了。这套写法paramNoneif param is None: param 默认可变对象被称为None哨兵sentinel惯用法是 Python 社区公认的标准实践。它之所以可靠是因为None是不可变对象作为默认值不存在共享可变状态的问题而真正需要的新列表在每次调用时通过函数体内的代码重新创建。补充说明不可变默认参数为什么安全值得注意的是默认参数只求值一次的机制本身并非缺陷它只是与可变对象结合时才产生问题。如果默认参数是不可变对象如target()由于任何修改都会产生新对象而不是就地改动共享对象函数行为就是符合直觉的。这也再次印证了第一节的结论理解类型的可变性是理解 Python 一切行为模型的地基。五、同一原理在类与容器中的体现共享可变对象导致意外改动这一主题在本仓库的其他章节中反复出现构成了贯穿全书的隐线可变类变量类章节 展示了在类中定义superpowers []这样的可变类变量时所有实例会共享同一个列表foo.add_superpower(fly)之后bar.superpowers也会变成[fly]。这与默认参数陷阱同源可变对象被多个名字共享就地修改必然互相影响。文中给出的建议与本文一致——只有当你知道自己在做什么时才可以使用它们。不可变容器容器章节 明确说明namedtuple是不可变的试图给其实例属性赋值会抛出AttributeError: cant set attribute正因其不可变作为默认参数或共享数据时天然安全。参数传递*args与**kwargs章节 讨论了函数参数的各种传递方式理解这些机制与本文的默认参数陷阱相辅相成共同构成如何安全地把数据交给函数的完整认知。这些章节的源码佐证同样位于 code/2.7/ 目录下例如12_collection.py中通过namedtuple_test()特意注释掉perry.age 21这行非法赋值正是对不可变容器不可修改的直接验证。六、实战自查清单读完本文你可以用下面这份清单快速自查代码是否存在可变性陷阱赋值前先问类型bar foo后若foo是list/dict/set等可变类型bar的任何就地修改都会影响foo。需要独立副本时请显式拷贝如bar foo[:]或bar foo.copy()。函数默认参数拒绝可变类型def f(x, items[])是反模式应改为def f(x, itemsNone)并在函数体内用if items is None: items []创建新对象。类变量慎用容器需要每个实例独立的列表/字典时请在__init__中通过self.xxx []创建实例变量而不是在类体中定义共享的可变类变量。善用不可变类型当数据只需读取、不允许修改时优先使用tuple、frozenset、namedtuple从类型层面杜绝意外变动。对象变动mutation不是 Python 的 bug而是其对象模型的必然结果。掌握变量即名字、可变对象共享即隐患、默认参数只求值一次这三条定律你就能在阅读本仓库其余章节如装饰器、生成器、上下文管理器时始终保持对共享状态的警觉写出真正可预测、可维护的 Python 代码。赞分享教程文档教育【免费下载链接】interpy-zh《Python进阶》Intermediate Python - Chinese Version项目地址https://gitcode.com/gh_mirrors/in/interpy-zh点击查看免费下载相关推荐最完整Python数据类型wtfpython理解可变与不可变对象最完整Python数据类型wtfpython理解可变与不可变对象 引言为什么Python数据类型会让你困惑 你是否曾经遇到过这样的Python代码行为让文档教程5分钟搭建个人微信公众号聚合中心告别碎片化阅读的终极指南5分钟搭建个人微信公众号聚合中心告别碎片化阅读的终极指南 你是否厌倦了在无数个微信公众号之间来回切换只为寻找有价值的内容每天花费大量时间在碎片化的信息海洋后端前端Sway 变量详解不可变默认、可变声明与类型注解机制Sway 变量详解不可变默认、可变声明与类型注解机制 导读 本文基于 Sway 官方文档 docs/book/src/basics/variables.md编程语言编译器区块链上一篇Gatsby与Netlify CMS集成gh_mirrors/v41/v4可视化内容管理方案下一篇FAST 自适应配色系统中的 neutralFillStealthFocus隐形填充焦点态 Design Token 深入解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表