Python核心技术与实战
景霄
Facebook资深工程师
立即订阅
13891 人已学习
课程目录
已完结 46 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 从工程的角度深入理解Python
免费
基础篇 (14讲)
01 | 如何逐步突破,成为Python高手?
02 | Jupyter Notebook为什么是现代Python的必学技术?
03 | 列表和元组,到底用哪一个?
04 | 字典、集合,你真的了解吗?
05 | 深入浅出字符串
06 | Python “黑箱”:输入与输出
07 | 修炼基本功:条件与循环
08 | 异常处理:如何提高程序的稳定性?
09 | 不可或缺的自定义函数
10 | 简约不简单的匿名函数
11 | 面向对象(上):从生活中的类比说起
12 | 面向对象(下):如何实现一个搜索引擎?
13 | 搭建积木:Python 模块化
14 | 答疑(一):列表和元组的内部实现是怎样的?
进阶篇 (11讲)
15 | Python对象的比较、拷贝
16 | 值传递,引用传递or其他,Python里参数是如何传递的?
17 | 强大的装饰器
18 | metaclass,是潘多拉魔盒还是阿拉丁神灯?
19 | 深入理解迭代器和生成器
20 | 揭秘 Python 协程
21 | Python并发编程之Futures
22 | 并发编程之Asyncio
23 | 你真的懂Python GIL(全局解释器锁)吗?
24 | 带你解析 Python 垃圾回收机制
25 | 答疑(二):GIL与多线程是什么关系呢?
规范篇 (7讲)
26 | 活都来不及干了,还有空注意代码风格?!
27 | 学会合理分解代码,提高代码可读性
28 | 如何合理利用assert?
29 | 巧用上下文管理器和With语句精简代码
30 | 真的有必要写单元测试吗?
31 | pdb & cProfile:调试和性能分析的法宝
32 | 答疑(三):如何选择合适的异常处理方式?
量化交易实战篇 (8讲)
33 | 带你初探量化世界
免费
34 | RESTful & Socket: 搭建交易执行层核心
35 | RESTful & Socket: 行情数据对接和抓取
36 | Pandas & Numpy: 策略与回测系统
免费
37 | Kafka & ZMQ:自动化交易流水线
38 | MySQL:日志和数据存储系统
39 | Django:搭建监控平台
40 | 总结:Python中的数据结构与算法全景
技术见闻与分享 (4讲)
41 | 硅谷一线互联网公司的工作体验
42 | 细数技术研发的注意事项
加餐 | 带你上手SWIG:一份清晰好用的SWIG编程实践指南
43 | Q&A:聊一聊职业发展和选择
结束语 (1讲)
结束语 | 技术之外的几点成长建议
Python核心技术与实战
登录|注册

16 | 值传递,引用传递or其他,Python里参数是如何传递的?

景霄 2019-06-14
你好,我是景霄。
在前面的第一大章节中,我们一起学习了 Python 的函数基础及其应用。我们大致明白了,所谓的传参,就是把一些参数从一个函数传递到另一个函数,从而使其执行相应的任务。但是你有没有想过,参数传递的底层是如何工作的,原理又是怎样的呢?
实际工作中,很多人会遇到这样的场景:写完了代码,一测试,发现结果和自己期望的不一样,于是开始一层层地 debug。花了很多时间,可到最后才发现,是传参过程中数据结构的改变,导致了程序的“出错”。
比如,我将一个列表作为参数传入另一个函数,期望列表在函数运行结束后不变,但是往往“事与愿违”,由于某些操作,它的值改变了,那就很有可能带来后续程序一系列的错误。
因此,了解 Python 中参数的传递机制,具有十分重要的意义,这往往能让我们写代码时少犯错误,提高效率。今天我们就一起来学习一下,Python 中参数是如何传递的。

什么是值传递和引用传递

如果你接触过其他的编程语言,比如 C/C++,很容易想到,常见的参数传递有 2 种:值传递引用传递。所谓值传递,通常就是拷贝参数的值,然后传递给函数里的新变量。这样,原变量和新变量之间互相独立,互不影响。
比如,我们来看下面的一段 C++ 代码:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Python核心技术与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(57)

  • Jingxiao 置顶
    关于思考题:
    第一题:
    l2和l3是指向同一个对象,因为两者之间用等号赋值了,l1并不是,l1所指向的[1, 2, 3]是另外一块内存空间,大家可以通过id()这个函数验证

    第二题:
    输出的是{'a': 10, 'b': 20},字典是可变的,传入函数后,函数里的d和外部的d实际上都指向同一个对象
    d[idx] = value语句改变了字典对应key所指向的值
    2019-06-17
    1
    17
  • 不瘦到140不改名
    针对@小恶魔的问题,回复一下

    python里面一切皆对象, 比如a=1。在java里面是int a = 1,相当于先声明了一个int类型的变量a,然后给这个变量赋值为1。但在python中,是先在内存中申请一份空间,存的值为1,然后再给这块空间贴上一个标签,叫变量a,因此python中变量实际上是一个便利贴,可以贴在任何地方。并且还可以通过值来推断出变量的类型,这一步是由解释器来完成的。所以python虽然不需要显式声明变量,但它其实是强类型语言。
    def func(d):
        d['a'] = 10
        d['b'] = 20
        d = {'a': 1, 'b': 2}


    d = {}
    func(d)
    print(d) # {'a': 10, 'b': 20}

    至于这里为什么会是这个结果,当我们将d传递给func的时候,其实func里面的d和外面的d指向的是同一片内存。相当于一开始d={},存放{}这份空间只有d这一个便利贴,但是func(d)的时候,这份空间又多了一个便利贴。尽管都叫d,但一个是全局变量d,一个是函数的参数d
    当d['a'] = 10和d['b']=20的时候,由于字典是可变类型,所以外面的d也被修改了,此时外面的d和函数里面的d都指向了{'a': 10, 'b': 20}, 但是当d = {'a': 1, 'b': 2}的时候,这是属于赋值。因此python会在内存中再开辟一份空间,空间存放{'a': 1, 'b': 2},然后让函数里面的局部变量d指向它,相当于将原本位于{'a':10,'b':20}上的便利贴撕下来,贴在了另一块空间。但这只是函数里面的d,对外面的d是没有影响的,所以外面的d依旧是{'a': 10, 'b': 20}。
    2019-06-14
    7
    33
  • somenzz
    第一个比较简单,列表是可变对象,每创建一个列表,都会重新分配内存,因此 l1 和 l2 并不是同一个对象,由于 l3 = l2 表明 l3 指向 l2 的对象。

    第二个 输出的结果应该是 {'a': 10, 'b': 20} ,d = {'a': 1, 'b': 2} 属于重新指向新的对象,并不改变原有的字典对象。
    2019-06-14
    8
  • yshan
    首先更正下,需要先定义d={}。
    然后,局部变量与全局变量的区别,函数内定义的d为全局变量,在没有关键字声明的情形下不能改变全局变量,由于字典可变,遵循可变则可变的原则,输出为{'a': 10, 'b': 20}。
    最后,看实验:
    def func(d):
        print(id(d))
        d['a'] = 10
        d['b'] = 20
        print(id(d))
        d = {'a':1, 'b':2}
        print(id(d))
        print(d)

    d = {}
    print(id(d))
    func(d)
    print(d)
    print(id(d))

    执行结果:
    3072243980
    3072243980
    3072243980
    3072244108
    {'a': 1, 'b': 2}
    {'a': 10, 'b': 20}
    3072243980

    2019-06-16
    1
    6
  • 程序员人生
    第一题,用id()打印出来后可以证明,l1和l2不是同一个对象,l2和l3是同一个对象。由于列表是可变的,所以l1和l2指向不同的内存区域。
    第二题,做了一下修改,如下:
    def func(d):
        d['a'] = 10
        d['b'] = 20
        d={'a':1,'b':2}

    d={}
    func(d)
    print(d)

    执行结果:
    {'a': 10, 'b': 20}

    d = {'a': 1, 'b': 2}应该是指向了新的对象


    2019-06-14
    3
  • Wing·三金
    # C++

    - 按值传递:拷贝参数的值构建新的变量传递到函数
    - 按引用传递:把参数的引用(i.e. 地址)传递到函数

    # Python

    - 按赋值传递/按对象的引用传递
    - 凡是对对象本身进行的操作,都会影响传递的原对象;凡是生成了新对象的操作,都不会影响传递的原对象
    - 正如【一个人可以死两次,第一次是肉体死去,第二次是当没人记得它的时候】,python 中如果有多个变量指向同一个对象,那么当删除一个变量时并不会真正删除其所指定的对象;只有当所有指定该对象的变量都被删除时,python 才会回收该对象所占用的资源
    - 一般原则:对于不可变的数据类型,operator 等操作会返回新的对象,不会影响原对象;对于可变的数据类型,任何对【对象本身】的操作都会影响所有指向该对象的变量
    - 补充上一条:e.g. 对于 list 而言,l += [1] 和 l = l + [1] 不同!前者是在 l 本身的末尾添加新元素,后者是在 l 的基础上添加新的元素并返回新的对象
    - 在工程上,偏爱类似于上一条后者的作法——即通过【创建新的对象+将其返回】的作法,来减少出错的概率

    # 思考题

    1. l1 与 l2 不同,l3 与 l2 同;
    2. 严格来说,如果没有上下文,这是一段错误的代码,因为没有预先定义 d 变量;不妨假设在第 6 行之前补充语句 d = {},则输出结果为 {'a': 10, 'b': 20},因为 func 中前两行才是改变了对象的操作。而第 3 行只是将函数中的局部变量 d 指向了新的字典 {'a': 1, 'b': 2},但全局变量 d 仍然指向着刚刚被修改过的字典对象。
    2019-06-16
    2
  • KaitoShy
    1. l2和l3是一个,l1不是。可以通过id(l2),id(l1),id(l3)验证。
    2. d不是没有初始化么。输出错误吧。如果在使用函数func(),将d初始化为d={},输出{'a':10, 'b':20}.原因:前两个改变了对象的值。后面是创建了新对象赋值给了本地对象。
    2019-06-14
    2
  • 张丽娜
    def my_func2(b):
        print('a的值是{}'.format(a))
        print('b的值是{}'.format(a))
        b = 2
        print('b的值是{}'.format(b))
        return b


    a = 1
    a = my_func2(a) #这句话so 重要,重新用返回值对于a进行了赋值,看起来debug来逐步分析很重要啊
    print('a的值是{}'.format(a))
    2019-06-14
    1
  • 小恶魔
    看了这么多关于问题2回复都是结果。我想知道python中对参数赋值不会影响外部的值,这是设定语法,还有什么深层次的原因或设计考虑么,谢谢老师。
    2019-06-14
    1
    1
  • SCAR
    第一题:l2和l3指向同一个对象,l2和l1不指向同一个对象。这个题的关键要点是要了解list对象是没有“内存驻留”机制的,这点和整数对象对小于256的数采用的“内存驻留”是截然不同的,所以l1和l2不是指向同一对象。而l3=l2,这就是让l3指向l2指向的对象,很显然l3和l2指向的是同一个对象。
    第二题:题目里的d = {'a': 1, 'b': 2}应该是顶格的吧,估计是老师手误或是编辑器出问题了,不然没意义。如果是这样,print(d),输出应该是{'a': 10, 'b': 20}。
    2019-06-14
    1
  • Paul Shan
    思考题
    l1 指向一个对象
    l2和l3指向同一个对象

    a :10,b:2
    2019-11-16
  • mercy
    对象的id能否理解为指针

    作者回复: 不一样的概念呢

    2019-11-12
  • Arthur
    可变类型,按引用传递;不可变类型,按值传递。
    2019-11-12
  • 1cho糖糖
    遇到了下面一个问题

    ```
    def demo1(array):
        array += [4, 5]
        return array


    def demo2(array):
        array = array + [4, 5]
        return array


    a = [1, 2, 3]
    b = [1, 2, 3]

    c = demo1(a)
    print('a list is {}\nc list is {}\na is c :{}'.format(a, c, a is c)) # True
    # 输出结果
    a list is [1, 2, 3, 4, 5]
    c list is [1, 2, 3, 4, 5]
    a is c :True

    d = demo2(b)
    print('b list is {}\nd list is {}\nb is d :{}'.format(b, d, b is d)) # False
    # 输出结果
    b list is [1, 2, 3]
    d list is [1, 2, 3, 4, 5]
    b is d :False

    # 函数内部为什么 array += [4, 5] 与 array = array + [4, 5] 对传入的列表影响结果不同
    ```
    2019-11-03
  • 自由民
    总结:Python中参数传递既不是传值也不是传引用,而是赋值传递,或传对象的引用。不是指向一个具体的内存地址,而是指向具体的对象。
    如果对象是不变的,改变对象会新建一个对象,并将其中一个变量指向该对象,其它变量不变。如果对象是可变的,改变一个变量时,其它所有指向该对象的变量都会受影响。要想在函数中改变对象,可以传入可变数据类型(列表,字典,集合),直接改变;也可以创建一个新对象,修改以后返回。建议用后者,表达清晰明了,不易出错。
    思考题1:
    l2与l3指向同一对象,与l1不同。
    # 思考题1
     l1 = [1,2,3,4]
     l2 = [1,2,3,4]
     l3 = l2
     print(id(l1), id(l2), id(l3))
    思考题2
    {"a":10, "b":20}
    课程的练习代码: https://github.com/zwdnet/PythonPractice
    2019-10-03
  • zjlyyq
    思考题一:
    print(l1 is l2) #False
    print(l3 is l2) #True
    l1和l2指向的是不同对象,l3和l2指向同一对象。l2 = [1,2,3],Python是直接新建了一个对象,然后将l2指向它,不是先在内存中查找是否存在相同值得对象是否已经创建过
    2019-09-10
  • 建强
    思考题1:
    l1、l2指向不同的对象,l2和l3指向同一对象。

    思考题2:
    d最后的输出是:{'a': 10, 'b': 20}
    2019-09-09
  • R
    请问老师,return返回b,b是一个引用,还是一个对象呢。
    2019-08-27
  • 木易
    为什么第一题中的l1和l2不指向同一个对象,但是a=1, b=1中的a和b却指向同一个对象?
    2019-08-06
  • 江南皮革厂研发中心保安队长
    老师,func4的原因我个人觉得跟作用域的关系比较大,不知道理解对不对
    2019-07-30
收起评论
57
返回
顶部