对象引用、变量
1.变量不是盒子,在python中变量不过是一种标注,类似于Java中的引用类型的变量。
a=[1,2,3] b=a b.append(4) print(a) print(b) # [1, 2, 3, 4] # [1, 2, 3, 4]
如上所示,可以清晰的看出,变量是一种标识,a b 指向同一块区域,所以修改b ,a也会随着改变。
每个变量都有标识、类型和值,对象一旦创建,它的标识一定不会改变,可以把标识理解为对象在内存中的地址。
is比较两个对象的标识;
id()返回对象标识在内存中的地址。
因此,在理解赋值语句时,要先看右边,对象在右边创建和获取,之后左边的变量才会绑定到对象上。
2==和is的区别
- ==:比较两个对象的值(对象中保存的数据)
- is:比较对象的标识。
charles={'name': 'Charles L. Dodgson', 'born': 1832} lews=charles #变量赋值,其实相当于变量标识是相同,因为id返回值相同 print(lews is charles) #True print(id(lews),id(charles)) #2695836549352 2695836549352 lews['balance'] = 950 #添加一个键值对,之后lews和alex的键和值完全相同 alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} print(alex==charles)# True 比较两个对象,结果相等,这是因为 dict 类的 __eq__ 方法就是这 样实现的。 print(alex is charles) #False 这两个对象不相同,即为不同的标识 print(id(alex),id(charles)) #2221654098232 2221654098152
3.元组的相对不可变性:简单来讲,元组是不可变的,但在元组中存储了可变的对象时,元组相对可变。
- 容器序列:存放的是他们所包含的任意类型的对象引用,而
- 扁平序列:存放的是值而不是引用,换句话说扁平序列其实是一段连续的内存空间,但其中只能存放字符、字节和数值类型
- 可变序列:list、bytearray、array.array、collections.deque和memoryview.
- 不可变序列:tuple、str和bytes。
t1 = (1, 2, [30, 40]) #元组中存放相对可变序列 t1[-1].append(50) #元组相对可变 print(t1) #(1, 2, [30, 40, 50])
4浅度复制和深度复制
4.1浅度复制:对于复制列表来说可以使用l2=list(l1)(将l1复制一份给l2)或可以通过l2=l1[:],来实现,但这种复制方式为浅复制,即只复制了容器的最外层,副本中保存的是原容器中元素的引用。
l1 = [3, [66, 55, 44], (7, 8, 9)] l2=list(l1) #浅拷贝 ,注意这种方式与l2=l1是不同的, l1.append(100) #给l1添加一个值,l2不会受到影响 print(l1) #[3, [66, 55, 44], (7, 8, 9), 100] print(l2) #[3, [66, 55, 44], (7, 8, 9)] l1[1].remove(55) #移除l1中的55,l2会受到影响,因为l1和l2指向同一个引用 print("l1",l1) #l1 [3, [66, 44], (7, 8, 9)] print("l2",l2) #l2 [3, [66, 44], (7, 8, 9)] l2[1]+=[33,22] l2[2]+=(10,11) print(l1) #[3, [66, 44, 33, 22], (7, 8, 9), 100] print(l2) #[3, [66, 44, 33, 22], (7, 8, 9, 10, 11)] #对元组来说,+= 运算符创建一个新元组,然后重新绑定给变量 l1[2]。 # 这等同于 l1[2] = l1[2] + (10, 11)。现在,l1 和 l2 中最 后位置上的元组不是同一个对象。
如上图所示:l1经过浅度复制后为l2,l1[1]和l2[1]指向同一块区域,同样l1[2]和l2[2]指向同一块区域即(tuple)。
上述程序结束后各个引用的指向(流畅的Python)
4.2深度复制:副本不共享引用,各自独立
import copy class Bus: def __init__(self,passengers=None): if passengers is None: self.passengers=[] else: self.passengers=list(passengers) def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name) bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) bus2=copy.copy(bus1) #浅度复制 bus3=copy.deepcopy(bus1) #深度复制 bus1.drop('Bill') #bus1中移除Bill后,bus2中相应也移除了,bus3中还在 print(bus1.passengers) #['Alice', 'Claire', 'David'] print(bus2.passengers) #['Alice', 'Claire', 'David'] # #浅拷贝,bus2和bus1指向相同的列表对象 print(bus3.passengers) #['Alice', 'Bill', 'Claire', 'David']
5.函数的参数
python唯一支持参数传递模式是共享传参(call by sharing)。共享传参指函数的各个形式参数获得实名参数中的副本,也就是说,函数中内部是形参是实参的别名(可以看成上面提到标记),简单说,就是传入函数的形参改变可能会影响外部实参,这可能是我们不希望看到的。
def f(a,b): b.append(5) a+=b return a a=[1,2] b=[3,4] f(a,b) print(a) #[1, 2, 3, 4, 5] print(b) #[3, 4, 5] #a b改变了
5.1参数默认值的问题,
在python中可选参数可以有默认值,但要避免使用可变的对象作为参数的默认值。
import copy class Bus: def __init__(self,passengers=[]): #使用可变列表作为参数默认值 self.passengers=passengers #把self.passengers当做passengers的别名,当 passengers没有参数时又是默认列表别名 #当在self.passengers上调用.append或.remove方法时,修改的其实时默认列表,是函数对象的一个属性。 def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name) bus1=Bus(['Alice', 'Bill']) bus1.pick('Chariles') print(bus1.passengers) #['Alice', 'Bill', 'Chariles'] bus2=Bus() bus2.pick('Helen') print(bus2.passengers) #['Helen'] bus3=Bus() print(bus3.passengers) #['Helen'],这就是使用可变的列表作为默认参数的弊端,bus3中初始化没有传入任何值,却受到上一个实例化参数的影响 bus3.pick('Hel') #这时bus2和bus3还会互相影响,bus1却正常,不会受影响 print(bus1.passengers) #['Alice', 'Bill', 'Chariles'] print(bus2.passengers) #['Helen', 'Hel'] print(bus3.passengers) #['Helen', 'Hel']
问题原因:没有指定实例初始化乘客的Bus会共享一个乘客列表,默认值在函数定义时计算(通常认为是在程序加载时),因此默认值就成为了函数对象的属性,即如果默认值是可变对象。而且修改了其值,那么后续函数调用将受到影响。
解决办法,None判断:
import copy class Bus: def __init__(self,passengers=None): if passengers is None: self.passengers=[] #加入一个判断当传入参数为None时,创建一个新的空列表 else: self.passengers=list(passengers) #进行浅度复制,避免对外部传入参数的影响 def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name) bus1=Bus(['Alice', 'Bill']) bus1.pick('Chariles') print(bus1.passengers) #['Alice', 'Bill', 'Chariles'] bus2=Bus() bus2.pick('Helen') print(bus2.passengers) #['Helen'] bus3=Bus() print(bus3.passengers) #[] 没有受到上述问题影响