人总有不会的,遇到一些问题深究下去必定有所收获
这个问题是在我写python爬虫项目的时候的疑问,可能是我太菜了(以前没学透彻),也可能是上学期学Java的时候按值传递的特点给搞混了,因为当时在用多线程的生产者消费者问题处理资源队列,参考别人代码的时候突然蒙了一下,但后来查了查资料发现原来是下面的原因,值得记录一下坑点,顺便当复习,对语言有个更深入的理解也挺好的
前置的一些知识
-
在python里面一切皆为对象,而这个对象分成两种类型,第一种是可变的,另外一种是不可变的。
-
按值传递:会在堆中建立一个新的副本,以后操作只对副本操作,对原来主函数里面的值不影响。
按引用传递:会在堆中建立一个地址的引用,也就是参数的地址,一旦改变这个值就会把主函数里面的变量也会改变。
做一些验证
这里我以参考的代码里面的一部分进行验证,这里用类去类比一下函数,一样的效果,通过id参数打印一下地址
class Consumer(object):
def __init__(self,page_queue,*args,**kwargs):
super(Consumer, self).__init__(*args,**kwargs)
self.page_queue = page_queue
print(id(self.page_queue))
def main():
page_queue = 1
print(id(page_queue))
c = Consumer(page_queue)
if __name__ == '__main__':
main()
输出结果是这样的
140722209422880
140722209422880
可以发现两处的地址是一样的,可以脑补一下图应该是这样的,好像是引用传值,到底是不是这样的呢?
再来一段代码验证
class Consumer(object):
def __init__(self,page_queue,*args,**kwargs):
super(Consumer, self).__init__(*args,**kwargs)
self.page_queue = page_queue
print(id(self.page_queue))
self.page_queue += 1
print(id(self.page_queue))
print(id(page_queue),page_queue)
def main():
page_queue = 1
print(id(page_queue))
c = Consumer(page_queue)
if __name__ == '__main__':
main()
结果是这个样子的
140722209422880
140722209422880
140722209422912
140722209422880 1
可以发现,以本来引用的常规思路去看的话,这样的操作应该会对同一个地址的东西修改了才对,你会发现,他重新开辟了一个新的空间去容纳新的值,原来传进去的参数没有存在任何影响,脑补一下这个图,现在变成了这样,跟平常的引用是不是有点不一样。
再来看这样一段代码,以队列为例,然后对传进的队列做修改,再观察一下地址内容的改变,查看其是否为空
# -*- coding: utf-8 -*-
# Author:0verWatch
from queue import Queue
class Consumer(object):
def __init__(self,page_queue,*args,**kwargs):
super(Consumer, self).__init__(*args,**kwargs)
self.page_queue = page_queue
self.page_queue.put(1) #增加一个值
print(id(self.page_queue))
print(id(page_queue),page_queue.empty())
def main():
page_queue = Queue(100)
print(id(page_queue))
print(page_queue.empty())
c = Consumer(page_queue)
if __name__ == '__main__':
main()
输出的结果是这个样子的
1519902231520
True
1519902231520
1519902231520 False
可以发现值变化了,地址却没发生变化,明显的引用传参的例子
自己的小结
这里就可以对照一下上面为什么说python对象有两种类型,一种是可变的,另外一种是不可变的,因为在python这个语言中,对于不可变对象的传参例如(tuple,数字,字符)他们一旦发生改变,就会重新在堆里面分配你一块空间,去给变化的值,这也在宏观上给人一种按值传递的错觉,但是这样的机制也优化了python的运行,对于可变的对象的传参例如(list,dict,还有上面提及到的queue类)相当于通过按引用来传递对象。
写代码的时候才发现自己有多菜2333333,大佬们请忽略