Advance Python 06 :对象引用、可变性和垃圾回收

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 指向一块内存空间
上一篇:第18周


下一篇:[设计模式、C++、go]结构型模式:适配器模式