zoukankan      html  css  js  c++  java
  • 对象引用、变量

    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
  • 相关阅读:
    GIL全局解释器锁、死锁、递归锁以及event事件与信号量、线程queue
    进程通信、消费者模型和线程
    COM inerop 创建自定义互操作集 问题
    工业自动化软件产业发展的探索与实践
    为什么都是a
    Dictionary用“集合初始值设定项”进行初始化
    非“自动实现的属性”也可以用“对象初始值设定项”进行初始化
    通过接口来解开依赖
    什么叫委派
    私有可写属性
  • 原文地址:https://www.cnblogs.com/2019zjp/p/11876963.html
Copyright © 2011-2022 走看看