来看看你对Python变量理解到位了没有
变量是编程的基础概念,Python 的变量也看似很简单,但是如果理解不当,生搬硬套,可能会遇到一些麻烦。
下面用 10 个代码示例展示 Python 的 变量 本质。
以下内容有对应的 视频 手把手详细讲解,更适合零基础小白。
1. 只是个名字
当 a
出现在赋值语句的左侧时,它仅仅代表一个 名字:
a = 1024
a = 'davycloud'
a = ['点赞', '关注', '收藏']
赋值完成后,这个名字和右侧的对象就 绑定 在一起了。在这个过程中,
a
是否已经绑定了其它对象完全不用考虑,也就是说,不管前面a
绑定了什么,或者什么也没有绑定,它都是个名字。- 只要名字是合法的,它可以绑定到任意的对象,完全不用考虑对象的类型
2. 对象的引用
绑定了对象的名字,也就是我们常说的 变量,当它出现在赋值语句的右侧时,它代表了 对象的引用:
a = []
b = a
当把 a
赋给 b
的时候,a
代表的是列表对象的引用,也就是说:
- 列表对象本体不受影响
- 并不会复制出一个新的列表
a
和b
在这之后都是这同一个列表的引用
因为列表是可变对象,所以可以通过 a
和 b
任意一个变量改变对象,两者同时都会反映出对象的变化:
a.append(1) # a = [1], b = [1]
b.append(2) # a = [1, 2], b = [1, 2]
可变对象下面会再次讨论
3. 先右 后左
把多个赋值语句连在一起时,处理的顺序是从右往左:
a = b = []
这里 b
首先是充当名字,绑定到一个列表对象;然后又充当对象的引用,赋给另一个名字 a
,也就是说,上面的语句等价于:
# 写法1
b = []
a = b
它和下面的赋值方式有着截然不同的后果:
# 写法2
b = []
a = []
在这里,两个变量分别绑定了两个不同的列表,它们之间互相并无关联。
但是这里有个有趣的地方,如果我们把 []
换成一个整数 1
或者字符串,那么 写法 1 和 写法 2 两种赋值方式在结果上就并无不同。
4. 变化取决于对象
继续上面的例子,两个名字绑定到同一个列表,操作其中一个,另一个就受到影响:
b = []
a = b
a.append(1)
这是因为列表是一个 可变对象。而如果把列表换成数字或者字符串:
b = 'davy'
a = b
a += 'cloud'
对 a
的自增操作并不会影响到 b
。先给出自增操作的等价形式:
a = a + 'cloud'
可见,这仍然是一次赋值而已。利用前面的结论:
- 右边的
a
代表对象的引用,即'davy'
,它和'cloud'
加起来生成一个新的对象 - 左边的
a
是一个名字,它再次和新对象,即'davycloud'
绑定在一起
也就是说,这中间总共产生了 3 个字符串对象。
我们仔细观察不难看出,当我们想要 改变对象的时候,必须通过的是对象提供的接口(对于列表来说,就是 append
方法,或者下标操作,对于其它对象,可以是改变它的属性),而 不可能通过重新赋值改变对象 。
赋值只是名字的绑定,再次强调。
这里我们还能得到另外一个结论:
不可变对象被多次引用/绑定不会产生副作用。比如说:
# a, b 绑定到同一个对象
a = b = 'davy'
# a, b 分别绑定到一个对象
a = 'davy'
b = 'davy'
在上面的示例中,两种绑定的语法含义是不同的,但是,因为字符串是不可变的,也就是说,即使 a
和 b
绑定到同一个字符串,它们也不会互相影响。既然这样,那么又何必在内存中重复创建两个一模一样的的 davy
字符串出来呢。不如直接复用好了,可以节省一点内存:
>>> a = 'davy'
>>> b = 'davy'
>>> a is b
True
再次但是,这种优化并不是全局的,也就是说,并不是只要是相同的字符串就一定是唯一的对象:
>>> a += 'cloud'
>>> b += 'cloud'
>>> a is b
False
>>> a
'davycloud'
>>> b
'davycloud'
>>> a == b
True
所以呢,大家知道有这种情况就好,对于不可变对象都要使用 ==
去比较,而不要用 is
,因为它可能会产生时而正确时而错误的诡异结果。
5. 瞬间交换的秘密
当一个赋值语句中右侧出现了多个对象,或者多个对象的引用,它们会自动打包成一个元组。
在赋值语句的左边,需要有相同数量的名字供解包:
a = [1024]
b = 'davycloud'
a, b = b, a
这个例子中,综合利用前 3 个规则:
- 先右后左
- 变量是对象的引用
- 左侧是名字
不难得出一个结论,这里两个名字交换了对象的引用,对象本体并没有移动。
6. 都是名字的错
a
没有赋值,直接地运行结果:
>>> print(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
这里看似很好解释,变量 a
没有定义嘛!加一行赋值语句不就行了。
但是:
- 这里报的错误是
NameError
: name 'a' is not defined,名字未定义错误,并不是变量未定义 - 仔细想想,这个
a
到底是什么呢?数字?字符串?函数?类?模块?
7. 我们都是变量
接上一个例子:
a = 1
def a():
pass
class a():
pass
import sys as a
不仅是赋值,定义函数,定义类,导入模块或模块中的对象,都是在绑定名字。
定义函数是把一个名字绑定到一个函数对象,定义类是把名字绑定到一个类对象,导入一个模块就是把一个名字绑定到一个模块对象。
Python 中一切皆对象,所以,它们都是变量。
8. 传参也不过是命名
既然说到了函数,那就继续来看函数的传参:
def func(x):
return x
这个函数毫无用处,但是正好用来解释参数的传入和传出。
a = func(1024)
b = func(a)
函数中的参数 x
其实也是一个名字,只是它的 作用域 是限定在 func
函数的内部。
给函数传参就如同是赋值,给这个内部名字绑定一个对象,而出参就好似出现在赋值右侧的变量,就是传出来一个对象引用:
# 伪代码
func(1024):
x = 1024
a = x
关于变量的作用域这里点到为止,有机会再详细讨论。
注意,上面的规则对所有的对象类型都是一样的,无论是可变对象还是不可变对象。
根据前面的分析,很容易得出结论:
- 如果参数/返回是不可变对象,那么它是不会产生副作用的
- 如果参数/返回是可变对象,那么函数内部对它的操作都会影响其它绑定到这个对象上的变量
因此,对于可变对象的传参需要格外谨慎。特别地,函数的默认参数不要使用可变对象。
因为默认参数的绑定是在函数定义阶段发生的:
def get_people(people=[]):
return people
在调用 get_people
时,除非给 people
指定一个参数,否则它绑定的总是在函数定义时刻产生的那个列表。而我们的本意可能是,如果默认没有参数,就生成一个空列表。
一个常规的做法是,在函数内部新建对象:
def get_people(people=None):
if people is None:
people = []
return people
9. 删不掉的对象
Python 提供了 del
关键字可以用来 删除 变量,然而实际上这个操作的后果只是把变量名字和对象解绑,然后删掉这个名字。删掉的名字如果再访问,会触发 NameError
,就好像它从来没存在过一样。
而对象呢,它们只是减少了一个引用:
a = [1, 2, 3]
b = a
del a # 完全不会影响到 b
一个对象有多个变量引用到的情况下不会被清理很好理解,其实即使当前没有任何名字绑定到这个对象,这个对象也不会立即删除掉:
>>> a = []
>>> id(a)
2292904305736
>>> del a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b = []
>>> id(b)
2292904305736
总而言之,del
的含义就是 解绑,别指望它删除对象。
对象的引用计数和销毁是 Python 内部维护的,一般情况下我们无需关心。有兴趣的可以查阅 Python 的垃圾回收相关内容。
10. 浅拷贝?零拷贝!
终极例子:
a = [[]] * 3
a[0].append(1)
print(a) # [[1], [1], [1]]
这里迷惑性比较强,因为用到了列表的 *
操作。当对一个列表乘法操作时,一般的理解是对其中的元素进行 复制(copy)。
看到 copy 很容易又会提起所谓的 浅拷贝 和 深拷贝,这里显然不是深拷贝,那么想当然的很容易理解为是浅拷贝,错!
这里不过又是一次隐形的赋值,让我们把它展开:
x = []
y = [x]
a = [x, x, x] # a = y * 3 的等价写法
a[0].append(1)
print(a)
最重要的就是第 3 行代码:
y * 3
是要把y
中的元素复制3
份- 现在这个元素就是
x
, - 那么就让
x
重复出现3
次吧 - 实际的效果就是
[x, x, x]
,
x
是对象的引用,所以我们只是把对象的引用复制了 3
份,对象本体完全没有触及。
那么如果要真正复制这个列表应该怎么做呢?利用到系统提供的浅拷贝函数,或者是利用 切片:
# 列表内置的 copy 方法
a = [x.copy() for i in range(3)]
# 利用切片
a = [x[:] for i in range(3)]
# 利用 copy 标准库
from copy import copy
a = [copy(x) for i in range(3)]
前面两种方法是列表对象自带的接口,而 copy
模块则更加通用。
小结
- 变量指的是名字绑定了对象
- 绑定时,变量就是名字
- 使用时,变量代表对象的引用
- 变量改变的只有绑定关系
- 想要改变/复制对象,需要看对象有没有提供方法
除了以上内容,关于变量还有个重要的概念就是理解它的 作用域,这部分内容将另撰文讲解。
如果本文对你有帮助,请 点赞、分享、关注,谢谢!
来看看你对Python变量理解到位了没有的更多相关文章
- Python变量理解
变量进阶(理解) 01. 变量的引用 变量 和 数据 都是保存在 内存 中的 在 Python 中 函数 的 参数传递 以及 返回值 都是靠 引用 传递的 1.1 引用的概念 在 Python 中 变 ...
- 深入理解Python变量与常量
深入理解Python变量与常量 变量是计算机内存中的一块区域,变量可以存储规定范围内的值,而且值可以改变.基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中.常量是一块只读的内 ...
- python 深入理解 赋值、引用、拷贝、作用域
在 python 中赋值语句总是建立对象的引用值,而不是复制对象.因此,python 变量更像是指针,而不是数据存储区域, 这点和大多数 OO 语言类似吧,比如 C++.java 等 ~ 1.先来看个 ...
- python3 变量理解 解释器理解 常量理解 用户交互理解 逻辑运算
先来条NLP再说...... 九,每人都已经具备使自己快乐的资源 每一个人都有过成功快乐的体验,也即是说有使自己快乐的能力. 人类只用了大脑能力极少部分,提升大脑的运用,很多新的突破便会出现. 运用大 ...
- 【python系列】--Python变量和数据类型
python数据类型 一.整数 Python可以处理任意大小的整数,当然包括负整数,在Python程序中,整数的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等. 计算机由于使用 ...
- #5 Python变量与输入输出
前言 学习一门编程语言,最基本的无非不过学习其变量规则.条件语句.循环语句和函数,接下来的几节将开始记录这些基本的语法,本节主要记录变量规则! 一.Python输入输出 在说Python变量之前,先补 ...
- python变量和简单的数据类型
1.运行hello_world.py时发生的情况 运行hello_world.py时,Python都做了些什么呢?实际上,即便是运行简单的程序,Python所做的工作也相当多: #!/usr/bin/ ...
- Python变量和数据类型(入门2)
转载请标明出处: http://www.cnblogs.com/why168888/p/6400809.html 本文出自:[Edwin博客园] Python变量和数据类型 一.整数 int = 20 ...
- python入门课程 第3章 Python变量和数据类型
第3章 Python变量和数据类型3-1 Python中数据类型计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值.但是,计算机能处理的远不止数值,还可以处理文本.图形 ...
随机推荐
- react入门(六):状态提升&context上下文小白速懂
一.状态提升 使用 react 经常会遇到几个组件需要共用状态数据的情况.这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理. 原理:父组件基于属性把自己的一个fn函数传递给子组 ...
- 【一起学源码-微服务】Hystrix 源码二:Hystrix核心流程:Hystix非降级逻辑流程梳理
说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一讲我们讲了配置了feign.hystrix.enabl ...
- Tarjan求割点和桥
by szTom 前置知识 邻接表存储及遍历图 tarjan求强连通分量 割点 割点的定义 在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多, ...
- Typescript 实战 --- (7)类型兼容性
ts 允许类型兼容的变量相互赋值,这个特性增加了语言的灵活性 当一个 类型Y 可以被赋值给另一个 类型X 时,就可以说类型X兼容类型Y.其中,X被称为“目标类型”,Y被称为“源类型” X兼容Y : ...
- Selenium的简单使用
selenium的使用对于新手来说十分友好,因为他避开了如今网络中的异步加载抓取的困扰,使得我们大部分的时间可以用于提取信息和存储中,下面就简单的列一些使用的代码,希望给同样初学的你有一定的参考价值. ...
- 关于neo4j初入门(1)
图形数据库也称为图形数据库管理系统或GDBMS. Neo4j的官方网站:http://www.neo4j.org Neo4j的优点 它很容易表示连接的数据 检索/遍历/导航更多的连接数据是非常容易和快 ...
- 一次jvm调优过程
jvm调优实战 前端时间把公司的一个分布式定时调度的系统弄上了容器云,部署在kubernetes,在容器运行的动不动就出现问题,特别容易jvm溢出,导致程序不可用,终端无法进入,日志一直在刷错误,ku ...
- 去除Linux中的^M
(1)安装tofrodos sudo apt-get install tofrodos (2)做一些优化 ln -s /usr/bin/todos /usr/bin/unix2dos ln -s /u ...
- SIR模型预测新冠病毒肺炎发病数据
大家还好吗? 背景就不用多说了吧?本来我是初四上班的,现在延长到2月10日了.这是我工作以来时间最长的一个假期了.可惜哪也去不了.待在家里,没啥事,就用python模拟预测一下新冠病毒肺炎的数据吧.要 ...
- 压力测试---Jemeter的使用
一.线程组配置 线程组相当于有多个用户,同时去执行相同的一批次任务.每个线程之间都是隔离的,互不影响的.一个线程的执行过程中,操作的变量,不会影响其他线程的变量值. Delay Thread crea ...