Python进阶核心知识点(6)- 列表使用的坑,这里总结好了请查收!

列表是python中最简单最常用的容器,没有之一。很多初学者最新接触的Python数据结构就是列表。这也是列表的特性决定的:列表就是一个筐,什么东西都可以往里面装!关键是里面的元素还可以是不同的类型。也正是因为简单,大多数情况下我们都用列表来完成我们的任务。但是,列表有一些关键的地方需要我们注意的坑,不然会出现一些莫名其妙的错误。下面就是小编平常使用中总结的一些,供大家参考。

入坑之前,我们先明白python的列表是一个可变对象。至于什么是可变对象,用下面的代码来说明:

a = [1,2,3]
b= a
print(f'before: id(a) = {id(a)},id(b) = {id(b)}')
a.append(4)
print(f'after: a = {a}, b={b}')
print(f'after: id(a) = {id(a)},id(b) = {id(b)}')

>>>执行结果:
before: id(a) = 1528755230464,id(b) = 1528755230464
after: a = [1, 2, 3, 4], b=[1, 2, 3, 4]
after: id(a) = 1528755230464,id(b) = 1528755230464

这段代码大家都很容易看懂:

  • 先是创建了一个列表对象 :[1,2,3],并将变量a指向这个对象,之后将变量b也指向这个对象。注意,b=a,只是将a指向的对象赋给了b,此时系统并没有为b在单独创建一个对象。我们用id方法可以看到变量a和b指向同一个对象,即id(a)和id(b)完全相同。这里的指向可以理解为一个便利贴,便利贴上写着a,然后贴到了对象[1,2,3]上面。既然是便利签,那么可以随时拿下来贴到其他对象上面
  • 在变量a的末尾增加一个元素后,实际上是改变的变量a指向的对象,即列表[1,2,3]。我们所说的列表是可变对象,意思就是说这个对象的是可以改变的。增加后值变成了[1,2,3,4]但是地址并没有改变
  • 我们只对a进行了append操作,但是实际上是操作a指向的对象。这个对象的值改变后,所有指向他的变量的值也都变了。所以虽然只对a进行append操作,变量b的值也跟着变了

理解了上面这一点,下面这个就容易了:

2. 坑1: 列表等容器作为可变对象,当其作为参数传递给函数时,是通过引用进行传递。

也就是说函数接受到的是这个变量指向的对象。因而在函数内部对这个对象进行操作时,会改变这个对象的值!从而影响到函数外部对这个对象的引用!这一点需要注意,看下面代码:

list_outer = [1,2,3,4]
print('in the function outside, the list id is {}'.format(id(list_outer)))
def list_append_100(a):
    print('in the function inside, the list id is {}'.format(id(a)))
    a.append(100)
    
list_append_100(list_outer)
print('after call the function, the list_outer is:', list_outer)

>>>执行结果:
in the function outside, the list id is 1528771905856
in the function inside, the list id is 1528771905856
after call the function, the list_outer is: [1, 2, 3, 4, 100]

从中可以很明显的看到函数接受的列表和外部的列表的id相同,即是同一个对象。当在内部对这个对象进行更改操作后,会对外部造成影响。需要注意!如果我们调用函数时不想对外面的对象造成影响,则可以使用深拷贝,拷贝一个对象在内部使用。

3. 坑2:类初始化时,这个点比较隐蔽,但是道理相同

假如现在我们要管理一些机器,每个机器都有各自的id和一些参数需要管控。我们先创建一个机器类:

class machine():
    def __init__(self, id, parameter=[]):
        self.id = id
        self.parameter = parameter
        
    def add_parameter(self,p):
        self.parameter.append(p)
        
    def remove_parameter(self,p):
        self.parameter.remove(p)
        
photo_A = machine("ASML_01", ['LENS_TEMP', 'AIR_TEMP'])
print(f'photo_A id is {photo_A.id}, photo_A parameter is {photo_A.parameter}')

photo_A.add_parameter('CO2_PRES')
print('after add, the photo_A parameter is {}'.format(photo_A.parameter))
>>>执行结果:
photo_A id is ASML_01, photo_A parameter is ['LENS_TEMP', 'AIR_TEMP']
after add, the photo_A parameter is ['LENS_TEMP', 'AIR_TEMP', 'CO2_PRES']

上面的代码中我们定义了一个机器的类作为模板,他有两个属性,id和参数列表。
同时在类的内部定义了两个方法用于添加和删除参数。通过初始化一个光刻机并调用对象的add方法,OK,一切顺利。
有些时候某些设备的参数一开始不明确,初始化的时候如果我们不传参数列表就容易出问题。如下:

# 在创建两台设备,没有参数
Etch_A = machine('LAM-XR001')
Track_A = machine('TEL_AF02')
print(Etch_A.id, Etch_A.parameter)
print(Track_A.id, Track_A.parameter)
>>>执行结果:
LAM-XR001 []
TEL_AF02 []

可以看到,因为创建的时候没有传入参数列表,对象直接继承父类的参数列表,一个空列表。这是一个非常危险的动作。此时,如果对其中一个对象的参数进行操作会同时影响另一个对象!如下:

Etch_A.add_parameter('MaxFlow')
print('after add, Etch_A parameter is: {}'.format(Etch_A.parameter))
print('after add, Track_A parameter is: {}'.format(Track_A.parameter))

print(f'id Etch_A.parameter is {id(Etch_A.parameter)} ')
print(f'id Track_A.parameter is {id(Track_A.parameter)} ')
>>>执行结果:
after add, Etch_A parameter is: ['MaxFlow']
after add, Track_A parameter is: ['MaxFlow']
id Etch_A.parameter is 2710193692416 
id Track_A.parameter is 2710193692416 

可以看到我们虽然只对Etch_A这台设备进行操作,但同时影响了另外一台设备,这个是需要避免的。究其原因是因为这两个设备的参数列表指向了同一个对象,我们可以看到他们的id是相同的。在实际操作中,我们可以通过在初始的时候传入一个空的列表来避免。对不同的设备,即使都是传入一个空的列表,初始化的时候也会为每个对象单独开辟一个地址来存放。这样设备之间的操作就没有影响了。如下:

# 在创建两台设备,没有参数,但是传入一个空的列表作为参数
Etch_B = machine('LAM-XR002', [])
Track_B = machine('TEL_BF02',[])
print(Etch_B.id, Etch_B.parameter,id(Etch_B.parameter))
print(Track_B.id, Track_B.parameter,id(Track_B.parameter))
print('----' *10)
Track_B.add_parameter('PR_weight')
print('after add, Track_B parameter is: {}'.format(Track_B.parameter))
print('after add, Etch_B parameter is: {}'.format(Etch_B.parameter))
>>>执行结果:
LAM-XR002 [] 2710193691200
TEL_BF02 [] 2710193692224
----------------------------------------
after add, Track_B parameter is: ['PR_weight']
after add, Etch_B parameter is: []

可以看到,在初始化的时候,虽然两台设备都是传入一个空的列表作为参数,但是这两个空列表的id不一样,所以是两个对象。对其中一个对象进行操作,不会影响另外一个!

4. 坑3:列表的append, extend等方法没有返回值!他们直接在原列表上修改

其实这一点根本原因也是因为列表是可变对象,可以对他的值就行直接修改,而不必创建一个新的对象。也因为此我们千万不能有下面这种写法:

a = [1,2,3]
b = a.append(4)

原意是想把列表a增加一个元素4后赋值给b, 这样写不可能得到正确的结果,因为append方法没有返回值。也就是说
b = a.append(4)后,b是个None!也就是没有返回值的函数的默认返回值。
与此类似还有extend, remove等方法。使用时需要注意!

你可能感兴趣的:(Python进阶核心知识点(6)- 列表使用的坑,这里总结好了请查收!)