今天在编写python粒子群寻优时,在定义计算适应度函数中,我将适应度先初始化为每个粒子位置X(ndarray)的第一个参数,然后通过索引使一些粒子的适应度改为-1000。
def cal_fitness(X):
fitness = X[:, 0]
...
...
fitness[np.where(max_value > 1)] = -1000
return fitness
但是发现结果总是不收敛,而且跳来跳去,仔细调试了一会才发现问题出在传参上,还是基础掌握的不牢固。
在fitness = X[:, 0]
这一句中,python实际上并未分配新的内存空间给变量fitness
,而是将fitness
作为引用指向X[:, 0]
所在内存地址,于是在通过索引修改fitness
的值时,粒子的位置X
也受到修改,因此造成结果不对。
所以在用numpy中的深拷贝.copy()
代替直接赋值=
后,即fitness = (X[:, 0]).copy()
,问题得到解决。
一般来说:
- 如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值。
- 如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象。
通过搜索:
在python中,不可变对象是共享的,创建可变对象永远是分配新地址
(参考https://www.jb51.net/article/127667.htm)
有以下例子供思考:
b = 2
print(id(b)) # 94852102146912
a = 1
b = 1
print(id(a)) # 94852102146880
print(id(b)) # 94852102146880
我们将a, b都赋值为1,可以看到a, b的地址相同,这时如果我们将b+1:
b = b + 1
c = 2
print(id(b)) # 94852102146912
print(id(c)) # 94852102146912
此时b与c的地址相同,即不可变对象的值是共享的。程序只会在第一次给2分配内存地址,之后再创建值为2的变量时,都会指向内存中同一个2,而不是重新分配一个内存地址写入2。
但是对于可变对象,即使两个变量的值相同,其内存也会不同:
a = [1, 2, 3]
b = [1, 2, 3]
c = [1, 2] + [3]
print(id(a)) # 140199584204360
print(id(b)) # 140199550355080
print(id(c)) # 140199301368200
即创建可变对象永远是分配新地址,此时观察以下例子:
a = [1, 2, 3]
print(id(a)) # 140199300247048
a.pop()
c = a # [1, 2]
print(id(c)) # 140199300247048
c = c + [3] # [1, 2, 3]
print(id(c)) # 140199300252360
我们可以观察到,对于c = c + [3]
,程序重新分配了内存空间,符合创建可变对象永远是分配新地址。而对列表a使用内置方法pop后,即使列表内容修改,地址也不会改变,不像第一个例子将b修改为2后b的地址也发生改变。同样对于直接修改列表中的值也一样,也就是我一开始碰到的问题。
参考(https://blog.csdn.net/ewfwewef/article/details/109686193)得:
- 对于int, float, bool, str, tuple等不可变对象,变量值的改变本质上是该引用变量指向的改变,而原来那块内存的值是不变的。
- 可变对象的改变为原内存处值的改变,对于一个列表a,如果将其直接赋值给b,则a与b同时引用一个列表,一方改变,两者同变。
但是注意列表的这个改变应该限于内置方法以及通过索引修改列表内单元的值(?),就是因为创建可变对象永远是分配新地址,如下面例子:
a = [1, 2, 3]
print(id(a)) # 140199301367880
b = a
print(id(b)) # 140199301367880
b = b + [4]
print(id(b)) # 140199300247368
print(a) # [1, 2, 3]
总结:我们没有必要去讨论函数传参是传值还是传址,我们只需要注意传给函数可变对象(比如字典或者列表)的引用,就会有破坏对象的原始值的风险,就像C++中将数组地址传给函数一样。其次我们需要记住:在python中,不可变对象是共享的,创建可变对象永远是分配新地址!
所以最开始的那个问题,我们甚至可以通过fitness = X[:, 0] * 1
来代替深拷贝.deep()
,因为此时fitness
已经指向新的地址。
望指正!