Python对象的空间边界:独善其身与开放包容

今天,跟大家聊聊 Python 中跟身份密切相关的一个话题吧,那就是对象的边界问题。

1、固定边界:自由与孤独

Python 中有一些公民向来我行我素,它们特立独行,与他人之边界划定得清清楚楚。客气的人称它们是定长对象,或者叫不可变对象。

>>> t1 = ('Python', '猫')

>>> t2 = ('Python', '猫')

>>> t1 is t2 # 对象独立False

>>> t1[1] = '蛇' # 不可修改元素

TypeError Traceback (most recent call last)

TypeError: 'tuple' object does not support item assignment

上表就是定长对象的一份名单。可知,它们占据了多数。

定长对象的特性让我不由地想到一种人类,它们严守自己的边界,刻板而严谨,一心只在乎份内之事,默默承担下自己的责任,追求的是内在的自由。虽然也会时常与别人打交道,但是,它们不贪图扩大自己的利益,也不妄想要侵犯别人的领土。独立的个体养成了个人的品牌,它们的不变性成就了外人能有所依赖的确定性。

>>> key1 = 'Python 猫'

>>> key2 = ['someone else']

>>> dict1 = {'Python 猫': '好人'}

>>> dict2 = TypeError Traceback (most recent call last)

TypeError: unhashable type: 'list'

Python 为了维护定长对象的独立性/确定性,在编译机制上做了不少优化,例如 Intern 机制与常量合并机制。其中的好处,我已经多次提及了。

坏处也有,那就是孤独。它们的孤独不在于没有同类,而在于不能(不容易)复制自身。以字符串对象为例,你可以尝试多种多样的手段,然而到头来,却发现唯一通用的方法竟然要先把字符串“碎尸万段”,接着重新组装才行!

在 Python 的世界里,不存在这种烦恼,因为判定两个对象是否相同的标准是确定的,也即是看它们的 id 是否相等。因此,借助 Python 来回答这道题,答案会是:如果用 join() 方法把字符串粉碎成字符再组合,新的字符串不再是原来的字符串了。

2、弹性边界:开放与节制

与定长对象不同,变长对象/可变对象信奉的是另一套哲学。

它们思想开放,采取的是兼容并包的处事观,会因地制宜式伸缩边界。 以列表对象为例,它乐意接纳所有其它的对象,肯花费精力去动态规划,也不惧于拔掉身上所有的“毛”。

>>> l = ['Python', '猫']

>>> l.append('其它猫')

# ['Python','猫','其它猫']

>>> l.pop(1) # ['Python','其它猫']

>>> l.clear() # []

这些大胆的行为,在定长对象那里,都是不可想象的。在变长对象身上,你似乎能感受到一种海纳百川的风范,相比之下,定长对象的铁公鸡形象则立马显得格局忒小了。

变长对象并非没有边界,相反,它们更在乎自身的边界,不惜花费大量的资源来维持动态的稳定。一旦边界确定下来,它们绝不会允许越界行为。跟某些编程语言动不动就数组越界不同,Python 不存在切片越界,因为切片操作始终被控制为边界范围之内,索引超出的部分会自动被舍弃。

变长对象在本质上是一种可伸缩的容器,其主要好处就是支持不断添加或者取出元素。对应到计算机硬件层面,就是不断申请或者释放内存空间。这类操作是代价昂贵的操作,为了减少开销,Python 聪明地设计了一套分配超额空间的机制。

以列表为例,在内存足够的前提下,最初创建列表时不分配超额空间,第一次 append() 扩充列表时,Python 会根据下列公式分配超额空间,即分配大于列表实际元素个数的内存空间,此后,每次扩充操作先看是否有超额空间,有则直接使用,没有则重新计算,再次分配一个超额空间。公式如下:

new_allocated = (newsize >> 3) + (newsize

其中,new_allocated 指的是超额分配的内存大小,newsize 是扩充元素后的实际长度。举例来说,一个长度为 4 的列表,append() 增加一个元素,此时实际长度为 5(即 newsize 为5),但是,Python 不会只给它分配 5 个内存空间,而是计算后给它超额分配 new_allocated == 3 个内存大小,所以最终加起来,该列表的元素实际占用的内存空间就是 8 。

如此一来,当列表再次扩充时,只要最终长度不大于 8 ,就不需要再申请新的内存空间。当扩充后长度等于 9 时,new_allocated 等于 7 ,即额外获得 7 个内存大小,以此类推。

以列表长度为横轴,以超额分配的内存大小为纵轴,我们就得到了如下美妙的图表:

超额分配的空间就是定长对象的软边界 ,这意味着它们在扩张时是有法度的,意味着它们在发展时是有大胆计划与适度节制的。如此看来,与定长对象的“固步自封”相比,变长对象就显得既开明又理智了。

干货分享

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181227B1KJ9100?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券