关于python传参引发的一些思考

人总有不会的,遇到一些问题深究下去必定有所收获

这个问题是在我写python爬虫项目的时候的疑问,可能是我太菜了(以前没学透彻),也可能是上学期学Java的时候按值传递的特点给搞混了,因为当时在用多线程的生产者消费者问题处理资源队列,参考别人代码的时候突然蒙了一下,但后来查了查资料发现原来是下面的原因,值得记录一下坑点,顺便当复习,对语言有个更深入的理解也挺好的

前置的一些知识

  1. 在python里面一切皆为对象,而这个对象分成两种类型,第一种是可变的,另外一种是不可变的。

  2. 按值传递:会在堆中建立一个新的副本,以后操作只对副本操作,对原来主函数里面的值不影响。

    按引用传递:会在堆中建立一个地址的引用,也就是参数的地址,一旦改变这个值就会把主函数里面的变量也会改变。

做一些验证

这里我以参考的代码里面的一部分进行验证,这里用类去类比一下函数,一样的效果,通过id参数打印一下地址

1
2
3
4
5
6
7
8
9
10
11
12
13
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()

输出结果是这样的

1
2
140722209422880
140722209422880

可以发现两处的地址是一样的,可以脑补一下图应该是这样的,好像是引用传值,到底是不是这样的呢?

AO7U9f.png

再来一段代码验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()

结果是这个样子的

1
2
3
4
140722209422880
140722209422880
140722209422912
140722209422880 1

可以发现,以本来引用的常规思路去看的话,这样的操作应该会对同一个地址的东西修改了才对,你会发现,他重新开辟了一个新的空间去容纳新的值,原来传进去的参数没有存在任何影响,脑补一下这个图,现在变成了这样,跟平常的引用是不是有点不一样。

AOHK5q.png

再来看这样一段代码,以队列为例,然后对传进的队列做修改,再观察一下地址内容的改变,查看其是否为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- 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()

输出的结果是这个样子的

1
2
3
4
1519902231520
True
1519902231520
1519902231520 False

可以发现值变化了,地址却没发生变化,明显的引用传参的例子

自己的小结

这里就可以对照一下上面为什么说python对象有两种类型,一种是可变的,另外一种是不可变的,因为在python这个语言中,对于不可变对象的传参例如(tuple,数字,字符)他们一旦发生改变,就会重新在堆里面分配你一块空间,去给变化的值,这也在宏观上给人一种按值传递的错觉,但是这样的机制也优化了python的运行,对于可变的对象的传参例如(list,dict,还有上面提及到的queue类)相当于通过按引用来传递对象。

写代码的时候才发现自己有多菜2333333,大佬们请忽略


听说,打赏我的人最后都成了大佬。



文章目录
  1. 1. 前置的一些知识
  2. 2. 做一些验证
  3. 3. 自己的小结