前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >听说你会玩 Python 系列 3

听说你会玩 Python 系列 3

作者头像
用户5753894
发布2020-05-21 09:46:39
5820
发布2020-05-21 09:46:39
举报
文章被收录于专栏:王的机器

本文含 2734 字,12 图表截屏

建议阅读 15 分钟

本文是听说你会玩 Python 系列的第三篇

引言

在 Python 中,当创建变量时,不用像 C 语言那样在前面加入变量类型,如下图所示:

对比发现在 Python 中定义变量时,不需要声明其数据类型,因此 Python 属于动态类型(dynamic typed)语言。读者可能会认为 Python 不够严谨,怎么定义变量都不带变量类型呢?

原因是 Python 中的变量只是一个名字而已,就像下图的 x 存在变量名一样,它的作用仅仅是“指向”引用对象(PyObject)。PyObject 是计算机分配的一块内存,其下有类型大小引用计数等属性。引用计数是说多少个变量名“指向”该对象,当引用计数为零时,意味着没有任何变量名引用,因此可以被回收。

为什么 x 能“轻易地”指向不同变量类型?这要深挖 Python 内部机制是如何运行下面四条语句的。

  • 定义整数 x 并赋值 1031
  • 给 x 赋予一个新值 1032
  • 创建一个新变量 y 并等于 x
  • 将 y 值增加 1

不可修改的整数

定义整数 x 并赋值 1031

  1. 表面上是敲入 x = 1031,实际发生的是:
  2. 创建一个新对象 PyObject
  3. 将该 PyObject 的类型属性设为 int
  4. 将该 PyObject 的值属性设为 1031
  5. 创建一个变量名,叫做 x
  6. 将 x 指向新对象 PyObject
  7. 将 PyObject 里的引用计数加 1

给 x 赋予一个新值1032

表面上是敲入 x =1032,实际发生的是:

  1. 创建一个新对象 PyObject
  2. 将该 PyObject 的类型属性设为 int
  3. 将该 PyObject 的值属性设为 1032
  4. 将 x 指向新对象 PyObject
  5. 将新对象 PyObject 里的引用计数加 1
  6. 将旧对象 PyObject 里的引用计数减 1

旧对象“颜色变灰退出舞台”,代表着它随时会被清理。


创建一个新变量 y 并等于 x

表面上是敲入 y = x 时,实际发生的是:

  1. 将 y 和 x 指向同样的对象 PyObject
  2. 将该对象 PyObject 里的引用计数加 1

注意:在上面过程中没有创建任何新对象 PyObject


将 y 值增加 1

表面上是敲入 y += 1,实际发生的是:

  1. 创建一个新对象 PyObject
  2. 将该 PyObject 的引用计数设为 int
  3. 将该 PyObject 的值属性设为 1033
  4. 将 y 指向新对象 PyObject
  5. 将新对象 PyObject (即 y 指向的对象) 里的引用计数加 1

将旧对象 PyObject (即 x 指向的对象) 里的引用计数减 1


由上图可知,在 Python 中,即便对于一个简单的整数,它不单单包含其值,还包含其类型、大小和引用计数,封装成 PyObject。根据不同的变量值会生成不同的 PyObject,而变量名可以随意指向 PyObject。

让人迷惑是第三步,当 x 和 y 同时指向值为 1032 的 PyObject,但在第四步将 y 加 1,x 却保持不变。虽然迷惑但是合理,要不然改变 y 也改 x 会造成很多麻烦。但为什么改变 y 而不是改变 x 呢?原因在于改变 y 时新建了一个值为1033 的 PyObject,并将 y 指向它,而 x 还是指向原来值为 1032 的 PyObject。

从上面描述可以侧面推出整数是不可修改(immutable)的,因为更改变量值不是在原来的 PyObject 里改,而是新创建一个 PyObject。

判断变量 x 是否可修改,用 id(x) 函数,该函数打印出变量 x 的地址。

  • 如果 x 可修改,那么更新其值前后的地址一样
  • 如果 x 不可修改,那么更新其值前后的地址不一样

创建 x 并打印出地址

代码语言:txt
复制
x = 1031
id(x) 
代码语言:txt
复制
2479057898512

更新 x 的值,地址变了,因此 x 不可修改

代码语言:txt
复制
x = 1032
id(x) 
代码语言:txt
复制
2479067931376

将 x 赋予 y,两个指向相同对象,地址相同

代码语言:txt
复制
y = x
print( id(x) )
print( id(y) )
代码语言:txt
复制
2479067931376
2479067931376

更新 y 的值,y 的地址变了,因此 y 不可修改

代码语言:txt
复制
y += 1
print( id(x) )
print( id(y) )
代码语言:txt
复制
2479067931376
2479067931440

结论:整型变量是不可修改的。

再回到上面动态类型的例子,当变量 x 定义为整数 1、字符串 'one' 和布尔值 True 时,实际上变量名 x 轮流指向三个 PyObject,因此它们的内存地址也不一样。

配着上面的解释再回顾一下引言里的图,现在都明白了吧。

可修改的列表

Python 中的整数变量是不可修改的,而列表是可修改的。虽然还没介绍列表,可把它当成一个存储元素的容器,创建一个存储 1, 10.31 和'Python' 的列表,起名为 l,它在内存中的示意图如下:

和上面整数变量一样,表面上是敲入l = 1, 10.31, 'Python',实际发生的是:

  1. 创建一个新对象 PyObject(列表的)
    1. 将该 PyObject的类型属性设为 list
    2. 将该 PyObject的值属性指向三个地址
  2. 创建三个新对象 PyObjects(列表元素的)
    1. 将它们的类型属性设为 int, float, str
    2. 将它们的值属性设为 1, 10.31, 'Python'
  3. 将三个地址分别指向 PyObjects
  4. 创建一个变量名,叫做 l
  5. 将 l 指向新对象 PyObject
  6. 将 PyObject 里的引用计数加 1

根据上述流程,当更改列表中的元素,只是新创建其元素的 PyObject,而没有新创建列表本身的 PyObject。因此列表是可修改的,可用 id() 函数来验证更改列表前后的地址是一样的。

创建 l 并打印出地址

代码语言:txt
复制
l = [1, 2, 3]id(l) 
代码语言:txt
复制
2233189737736

更新 l 第一个元素值,地址没变,因此 l 可修改

代码语言:txt
复制
l[0] = 10000
id(l) 
代码语言:txt
复制
2233189737736

不可修改的元组

和列表不同,元组是不可修改的。创建一个元组 t,注意里面还包含一个列表 1, 2。

代码语言:txt
复制
t = (1, [1, 2], 'Python')

它在内存中的示意图如下(注意第二个列表元素又指向两个整型 PyObject):

由于元组不可修改,直接给元组元素赋值会报错。

代码语言:txt
复制
t[1] = [1, 2, 3]
代码语言:txt
复制
TypeError: 'tuple' object does not support item assignment

但只要元组中的元素可修改,比如列表,那么可以更改它,注意这跟赋值其元素不同。

代码语言:txt
复制
t[1].append(3)
t
代码语言:txt
复制
(1, [1, 2, 3], 'Python')

这也好理解,由于列表可修改,因此在1, 2 后面加个 3 不会改变列表的内存地址 0x210640,因此元组的内存地址也没有改变。但如果将整个列表重新赋值,那么要新创建一个列表赋给元组第二个元素,列表的地址肯定改变了,那么元组的内存地址也改变了,这样就违背了元组不可修改的特性,所以会报错。

总结

记住整数和元组不可修改、列表可修改一点也不难。

知道用 id() 函数来验证一个变量是否可修改也不难。

难的是要知道为什么,知其然还要知其所以然!

Stay Tuned!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 王的机器 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档