Introduce
-
python 的变量是什么
-
is 和 == 区别
-
del 语句和垃圾回收
-
一个经典的参数错误
文章目录
一、python 的变量是什么
1.1、变量、内存的理解
变量:用来标识(identify)一块内存区域。为了方便表示内存,我们操作变量实质上是在操作变量指向的那块内存单元。编译器负责分配。我们可以使用Python内建函数id()来获取变量的地址
变量名:是一个标识符(dientify),用来代之一块内存空间,使用这个变量名,我们可以很方便的操作这块内存区域。
内存: 内存是我们电脑硬件,用来存放数据,形象的理解就是内存有一个一个的小格子组成,每个格子的大小是一个字节,每个格子可以存放一个字节大小的数据。我们如何才能知道,数据存放在哪些格子中,那就得靠地址,地址类似于楼房的门牌号,是内存的标识符。
1.2、id()
id(object)函数是返回对象object在其生命周期内位于内存中的地址,id函数的参数类型是一个对象。
1.3、python 的变量是什么
概述
- java 中变量相当于申请一个盒子,盒子有类型大小之说
- python 中变量,类似一个指针,指针的值是固定的,类似便利贴,可以贴到任何对象上
我们通过解析下面一段代码来了解Python的变量是什么
示例代码:
# a 贴到 1 上面
a = 1
# a 再贴到 'abc' 上
a = 'abc'
# 注意顺序: 先生成对象,然后贴便利贴
la = [1, 2, 3]
lb = la
# is, 本质 id(a) 与 id(b) 比较
print(lb is la) # True, id 相同
print(id(la), id(lb))
la.append(100)
print(lb) # lb 和 la 指向相同对象,lb 会发生变化
代码解析:
a = 1
过程 :
- 先到内存中申请一块int空间
- 把 a 贴在这块内存空间上
便利贴的大小是固定的, 所以a可以帖在任何对象上
a = 'abc'
两个指针指向同一个对象(两个便利贴贴在同一个物体上)
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4]
print(a is b) # True
可以看到,修改b的同时(修改了b对应的对象),a也变化了。a,b贴在同一个对象上面。
python赋值方式
“把 变量 分配给 对象”,而不是“把 对象 分配给变量 ”。对 <引用式变量> 来说,说把变量分配给对象更合理,反过来说就有问题。毕竟,对象在赋值之前就创建了。
为了理解 Python 中的赋值语句,应该始终先读右边。对象在 右边创建或获取,在此之后左边的变量才会绑定到对象上,这就像 为对象贴上标注.
二、is 和 == 区别
2.1、概述
is : 比较 id()是否相同
(占用的内存地址是否相同)
== : 比较 变量值 是否相同
(内存地址可以不一样,内容一样就可以了,默认会调用对象的 eq()方法)
# is, 本质 id(a) 与 id(b) 比较
# = 右边为对象时,表示生成新对象
a = [1, 2, 3, 4]
b = [1, 2, 3, 4]
print(a is b) # False, 说明 id 不同
print(id(a), id(b))
print(a == b) # True, 值相同,内部 __eq__ 魔法函数
# 内部优化 小整数、小字符串 全局唯一 intern机制(下文会介绍)
a1 = 1
a2 = 1
print(a1 is a2) # True,为啥id一样呢???且看下文intern机制
s1 = 'abc'
s2 = 'abc'
print(s1 is s2) # True
class People:
pass
# People 全局唯一
person = People()
print(type(person) is People) # True
2.2、魔法函数__eq__
a = [1,2,3,4]
b = [1,2,3,4]
print(a==b)
print(a is b)
True
False
这个其实和魔法函数有关,list里面有一个魔法函数__eq__,当使用==时,会调用list里的__eq__魔法函数,从而判断值是否相等。
所以判断一个实例是否属于一个类时,要用is进行判断(但是用isinstance更好)
示例代码:
class People:
pass
person = People()
if type(person) is People: # 类也是一个对象,People也是全局唯一的
print ("yes")
yes
2.3、intern机制
intern机制
在python内部有一个intern机制,会把一定范围内的小整数建立一个全局唯一对象(常量池),如上面a=1,当执行b=1时,会指向同一个对象。这个对小串的字符串来说,也是一样的。
关键词
- 内部优化
- 小整数
- 小字符串
- 全局唯一
示例代码:
a = 1
b = 1
print(a is b)
print(id(a),id(b))
True
140724036245152 140724036245152
可以看到这两个对象是一致的,在python内部有一个intern机制,会把一定范围内的小整数建立一个全局唯一对象(常量池),如上面a=1,当执行b=1时,会指向同一个对象。这个对小串的字符串来说,也是一样的。
2.4、意外
而看一下另外一段代码:
>>> a = 257
>>> b = 257
>>> a is b
False
这是什么原因呢?
注意,Python仅仅对比较小的整数对象进行缓存(范围为范围[-5, 256])缓存起来,而并非是所有整数对象。需要注意的是,这仅仅是在命令行中执行,而在Pycharm或者保存为文件执行,结果是不一样的,这是因为解释器做了一部分优化。
2.5、总结
1、is 比较两个对象的 id 值是否相等,是否指向同一个内存地址;
2、== 比较的是两个对象的内容是否相等,值是否相等;
3、小整数对象[-5,256]在全局解释器范围内被放入缓存供重复使用;
4、is 运算符比 == 效率高,在变量和None进行比较时,应该使用 is。(因为它不能重载,所以 Python 不用寻找并调用 特殊方法,而是直接比较两个整数 ID。)
三、del 语句和垃圾回收
python中垃圾回收的算法是采用引用计数。
# python 中垃圾回收算法为 引用计数
a = 1 # 1这个对象就会有一个计数器,a=1时会在引用计数器上+1
b = a # 引用计数器加一
del a # 引用计数器减,而不是直接回收对象,这与C++不同,Python中,当计数器=0时,才会将1对象回收,防止一直占用内存
- del 触发 del 逻辑
- 对象引用计数为 0 时,会被垃圾回收
注意:
python的del和c++中不同,c++是直接回收对象,而python del直到计数器=0时,才会对对象进行回收
Tips:
- 在cpython2.0中就不是计数器。
- 当然,自己定义一个对象时,可以在__del__中写自己的垃圾回收逻辑
四、一个经典的参数错误
a, b 都是整型时
def add(a, b):
a += b
return a
if __name__ == '__main__':
a, b = 1, 2
c = add(a, b)
print('a: %s, b: %s, c: %s' % (a, b, c))
# 结果为 a: 1, b: 2, c: 3
# a 未发生变化
a, b 都是列表时
def add(a, b):
a += b
return a
if __name__ == '__main__':
a, b = [1, 2], [3, 4]
c = add(a, b)
print('a: %s, b: %s, c: %s' % (a, b, c))
# 结果为 a: [1, 2, 3, 4], b: [3, 4], c: [1, 2, 3, 4]
# a 发生变化!!!
a, b 都是元组时
def add(a, b):
a += b
return a
if __name__ == '__main__':
a, b = (1, 2), (3, 4)
c = add(a, b)
print('a: %s, b: %s, c: %s' % (a, b, c))
# 结果为 a: (1, 2), b: (3, 4), c: (1, 2, 3, 4)
# a 未发生变化
默认类型为可变类型时
class Company:
def __init__(self, name, staff_list=[]):
self.name = name
self.staff_list = staff_list
def add(self, staff):
self.staff_list.append(staff)
def remove(self, staff):
self.staff_list.remove(staff)
if __name__ == '__main__':
com1 = Company('com1', ['staff1', 'staff11'])
com1.add('staff111')
com1.remove('staff11')
print(com1.staff_list)
com2 = Company('com2')
com2.add('staff2')
com3 = Company('com3')
com3.add('staff3')
print(com2.staff_list) # ['staff2', 'staff3']
print(com3.staff_list) # ['staff2', 'staff3']
- 默认值为可变类型 [],com2.staff_list 和 com3.staff_list 指向一块内存空间