数据结构与算法学习笔记1-变量、地址与赋值

    技术2024-08-10  75

    python中可变对象和不可变对象

    不可变对象: 对象所指向的内存中的值不能被改变,当改变这个变量的时候,原来指向的内存中的值不变,变量不再指向原来的值,而是开辟一块新的内存,变量指向新的内存。 数值类型int 、float、 字符串str 、元祖tuple、boole 都是不可变对象。 可变对象: 对象指向的内存中的值会改变,当更改这个变量的时候,还是指向原来内存地址中的值,并且在原来的内存地址值进行原地修改,并没有开辟新的内存。 基本只有列表list、集合set、字典dict是可变对象。

    python中的赋值

    python赋值的实质:把要赋值对象的“地址”赋给变量名,而不是直接把复制内容“直接装进”变量名中(变量和列表都是一样的)。

    python赋值的流程:1.先为等式右边(待赋值对象)开辟内存空间(即使右边是相同的内容,两次的地址也不一样) 2.把内存的地址(列表则是首地址)赋给变量名。 注意:这里等式右边必须是实实在在的“常量”(直接打出的常数,字符串,列表等),而不是两个变量(等号左右都是变量称为浅复制,不是赋值)

    python中“=” 赋值的本质: 把右侧的地址(或首地址)赋给左侧。如果右侧没有地址(常量或者直接打出的列表等),则为其开辟一个新的内存空间,再把地址赋给左侧。

    python的“浅复制”:用“=”给两个“同类对象”赋值就是“浅复制”,浅复制只是把右边变量的地址给了左边变量,二者指向的仍然是同一处内存空间。即实际上完全是一个东西(不独立),在不改变地址的情况下,一个变另外一个也会跟着变。

    变量和列表的浅复制: 对于变量来说,是不是浅复制完全没有太大区别,因为不存在改变变量值而不改变其地址的情况(变量值变则地址必然变,即使是a+=1这种)。 对于列表则完全不同,append(item)等方法可以在不改变首地址的情况下更改列表内容(在不变的首地址后改变或增加新的内存空间),这时新老列表会一起改变,但不到复制的目的,因此称为浅复制。

    """对变量的赋值""" old_variable = 1 new_variable = old_variable print(id(old_variable), id(new_variable)) # 二者地址相同,完全为一个变量 new_variable = 10 # 此时,系统会给10开辟一个新地址,然后赋给new_variable,此时地址改变 print(old_variable, new_variable) # 二者地址已经不同,互相独立 print(id(old_variable), id(new_variable)) ------------------------------ # 输出结果 140708878979904 140708878979904 1 10 140708878979904 140708878980192 """对列表的赋值""" old_list = [1, 2, 3] new_list = old_list # 二者地址相同,完全为一个列表 print(id(old_list), id(new_list)) new_list.append(4) # 添加元素不改变序列首地址,二者地址依然相同,同时改变 print(old_list, new_list) new_list = [4, 5, 6, 7] # 重新赋值后,new_list指向存放新列表的新内存空间,地址改变 print(id(old_list), id(new_list)) print(old_list, new_list) # 此时二者已经不同,相互独立 ------------------ # 输出结果 2327661077064 2327661077064 [1, 2, 3, 4] [1, 2, 3, 4] 2327661077064 2327661077128 [1, 2, 3, 4] [4, 5, 6, 7]

    那么对于列表和字典如何做到真正的复制(深复制)呢? 一种方便且常用的方法如下,运用了list(),dict()函数后,会自动开辟新的地址,生成新的列表或字典:

    >>> list1 = [0,1,2,3] >>> list2 = list1 >>> (id(list1),id(list2)) (2812678434312, 2812678434312) # 地址相同 >>> list3 = list(list1) # 深复制 >>> (id(list1),id(list3)) (2812678434312, 2812678501128) # 地址不同,成功复制 >>> dict1 = {'1':1,'2':2} >>> dict2 = dict1 # 浅复制 >>> dict3 = dict(dict1) # 深复制 >>> (id(dict1),id(dict2),id(dict3)) (2812678444808, 2812678444808, 2812678307056) >>> (dict1,dict2,dict3) ({'1': 1, '2': 2}, {'1': 1, '2': 2}, {'1': 1, '2': 2})

    机械层面的本质: 只要两个对象的地址(或者首地址)相同,不管对象名相同与否,二者就是一个东西,一个变了另一个也会变。

    对象名的本质: 对象名(如变量名或者列表名或其他类的实例名)实际上就是对存储对象的内存空间的“地址”或“首地址”的“映射(指代)”。 这种映射不是固定的,同一个地址可以映射成不同的对象名(但本质都是同一个东西),同一个对象名也可以在不同时刻映射为不同的地址。 这实际上体现了计算机语言“物理(机械)层面”(地址)和“程序层面”(对象名)的处理单位不同

    函数与对象: 众所周知,函数在不“返回”变量时,是不能改变函数外的中的变量的(不可变对象)。而对于列表来说(可变对象),即使不返回列表,也可以改变列表的内容(因为可以在不改变地址的情况下改变列表内容)

    函数设置缺省参数时的坑

    函数定义中,入口参数的设定缺省值语句,仅仅在第一次调用函数时执行一次,以后再次调用也不会再执行,而只是直接使用第一次设置之后所创建的结果。 这意味着什么呢?如果缺省值设置为不可变对象(整数,元组等),那不会有任何影响。但如果设置为列表或者字典这种可变对象时,就会有很大问题。 因为设定缺省值的语句只会执行一次,所以无论函数调用多少次,之后的函数都使用的是最开始创建的那个列表(地址完全相同,不重新创建新的)。如果在函数内部使用了append等可以不改变首地址就可以改变列表内容的方法后,则之后每次调用函数时,这个缺省列表都会改变、都会保留上次函数调用后的结果,这显然不是我们想要的。

    """以下来自山阴少年的博客""" def add(x, lst=[]): print(id(lst)) if x not in lst: lst.append(x) return lst def main(): list1 = add(1) print(list1) list2 = add(2) print(list2) list3 = add(3, [11, 12, 13, 14]) print(list3) list4 = add(4) print(list4) main()

    结果为

    4469603648 [1] 4469603648 [1, 2] 4469670472 [11, 12, 13, 14, 3] 4469603648 [1, 2, 4]

    这与我们设置的初衷显然不符,原因如上所述。 想要杜绝这种问题,最好就不要在函数(包括类定义)中把缺省参数设置为列表或者字典。 更多见 Python之在函数中使用列表作为默认参数。

    Processed: 0.014, SQL: 10