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
View Code
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])
View Code
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 中最 后位置上的元组不是同一个对象。
View Code
如上图所示: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']
View Code
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改变了
View Code
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']
View Code
问题原因:没有指定实例初始化乘客的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) #[] 没有受到上述问题影响
View Code