一、变量不是盒子
学过java的人都知道,java中的变量可以当成盒子,变量使用前必须声明,才能进行存储空间分配
而在python中,我们最好把它理解为‘便利贴’
示例:
a = [1,2,3]
b = a
b.append(4)
print(a) #[1, 2, 3, 4] b a 贴在了同一个对象上面
print(a is b) #true 是同一个对象,id相同
在Python中,先创建对象,再进行赋值,所以我们说把变量分配给对象,而不是把对象分配给变量
people = People() #一切皆对象 类本身也是对象,全局中只存在一个
if type(people) is People:
print("yes")
结果当然为yes,这是因为在Python中一切为对象,类本身也是对象,在全局中只存在一个这样的对象
二、元组的相对不可变性
元组与多数Python集合(列表,字典等)一样,保存的是对象的引用。如果引用的元素是可变的,即便元组本身不可变,元素依然可变。也就是说,元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。
示例:一开始,t1 t2相等,但是修改t1中的一个可变元素后,二者不相等了
t1 = (1,2,[3,4])
t2 = (1,2,[3,4])
print(t1 == t2) #True t1 t2 是两个不同的对象,但值相等
print(t1 is t2) #False 不同的对象,id自然不同
print(id(t1[-1])) #2919174080328 查看t1[-1]列表的标识
t1[-1].append(99) #就地修改t1[-1]列表
print(t1) #(1, 2, [3, 4, 99])
print(id(t1[-1])) #2919174080328 t1[-1]的标识没变,只是值变了
print(t1 == t2) #False 现在,t1 t2值不再相等
三、函数的参数作为引用时
Python唯一支持的参数传递模式为共享传参,多数面向对象语言都采用这一模式。共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参是实参的别名。
这种机制的结果是,函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另外一个对象)。
下面的例子中,分别把数字、列表、元组传递给函数,实际传入的实参会以不同的方式受到影响:
def add(a,b):
a +=b
return a
a = 1
b = 2
print(add(a,b)) #3
print(a,b)#1,2
a = [1,2]
b = [3,4]
#参数类型为list时,a的值收到了影响
print(add(a,b)) #[1, 2, 3, 4]
print(a,b)#[1, 2, 3, 4] [3, 4]
a = (1,2)
b = (3,4)
print(add(a,b)) #(1, 2, 3, 4)
print(a,b)#(1, 2) (3, 4)
对+=或*=之类所做的增量赋值来说,如果左边变量绑定的是不可变对象,会创建新对象;如果是可变对象,会就地修改。
所以,不要使用可变类型作为参数的默认值
举个例子:
#举个例子
class Company:
def __init__(self,name,staffs=[]):
self.name = name
self.staffs = staffs
def add(self,staff_name):
self.staffs.append(staff_name)
def remove(self,staff_name):
self.staffs.remove(staff_name)
if __name__ == '__main__':
com1 = Company('com1',['hobby1','hobby2'])
com1.add('hobby3')
com1.remove('hobby1')
print(com1.staffs)#['hobby2', 'hobby3']
com2 = Company('com2')
com2.add('hobby4')
print(com2.staffs)#['hobby4']
print(Company.__init__.__defaults__) #(['hobby4'],)
com3 = Company('com3')
com3.add('hobby5')
print(com2.staffs)#['hobby4', 'hobby5']
print(com3.staffs)#['hobby4', 'hobby5']
print(com2.staffs is com3.staffs) #true
#因为我们传递进来的是一个列表,即可变对象,我们没有给com2 com3的staffs赋值,所以com2 com3共用了同一个staffs,该staffs为空列表
#尽量不传递list因为其可修改
本例中,如果没传入staffs参数,则使用默认绑定的列表对象,一开始是空列表,
self.staffs = staffs
这个赋值语句把self.staffs变成了staffs的别名,而没有传入staffs参数时,后者又是默认列表的别名。
在self.staffs上调用remove()和append()方法时,修改的其实是默认列表,它是函数对象的一个属性。
四、del 和垃圾回收
del语句指删除名称,而不是对象。del命令可能会导致对象被当做垃圾回收,但是仅当删除的变量保存的是对象的最后1个引用,或者无法得到对象时。重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。
在Cpython中,垃圾回收使用的主要算法是引用计数,实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象就被销毁:Cpython会在对象上调用__del__方法(如果定义了),然后释放分配给对象的内存。
a = 1
b = a #此时1上面有两个变量指向了它
del a #将引用计数器减一,只有当减到0时,Python回收机制回收
#和java不同,java中的del只是进行回收,而Python会对计数器减一
a = object()
b = a
del a #注意delete(引用计数器)与垃圾回收的区别
print(b)
#print(a)#NameError: name 'a' is not defined
我们可以在自己的类中定义__del__魔法函数,当del的时候,就可以执行相应的处理:
class A:
#实际上我们可以自己定义魔法函数来进行delete,当del对象的时候,就可以使用这里面的逻辑
def __del__(self):
pass