对象引用变量

Posted 2019zjp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对象引用变量相关的知识,希望对你有一定的参考价值。

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

 

以上是关于对象引用变量的主要内容,如果未能解决你的问题,请参考以下文章

Thymeleaf引用片段传入参数

Fragment 的 GetTag 返回空对象引用

变量的内存分析图

中继:预计会收到一个对象,其中传播了`...MyComponent_user`,但未找到片段引用`

SQL记录-PLSQL包

在一些片段之间填充对象变量的最佳方法