• 参数传递


    之前在看北理工嵩天等老师的python3的课程,在第五周中老师讲到了函数的调用传递。老师讲了这样一个例子

    #处理多个银行账户的余额信息
    def addInterest(balances, rate):  
        for i in range(len(balances)):  
            balances[i] = balances[i] * (1+rate)  
    def test():  
        amounts = [1000, 105, 3500, 739]  
        rate = 0.05  
        addInterest(amounts, rate)  
        print(amounts)  
    
    test()  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这个例子中可以看到为了处理多个银行账户设置了amounts这个列表,老师的原话是这样的:

    “在test函数中一开始设置了amounts为4个值的列表,然后将amounts作为第一个参数传递给函数addInterest,并调用执行该函数,最后打印输出amounts的结果,运行结果如下:

    [1050.0,110.25,3675.0,775.95] 
    
    • 1

    然后礼欣老师得出结论,以下为原话

    “在这个例子中可以看到,amounts变量的值好像是被修改了,但是函数是不能改变变量本身即amounts的” 

    这里的背景色是

    接下来是分析下函数的执行过程,过程如下图 
    addinterest()函数.png-267.9kB

    分析原话如下

    “接下来分析下整个执行过程,查看amounts输出结果是如何发生变化的。首先函数test的前两条语句建立了两个变量amounts和rate,然后控制传递给addinterest,这里amounts是一个包含4个整数类型值的列表对象,以实参的形式传递给函数addinterest形参balances,下一步执行函数addinterest,从0到length-1范围执行循环,并更新balances的值。”

    重点来了:原话如下

    “图中旧值 [1000, 105, 3500, 739] 
    并没有改变,只是Python又创建了一组新值[1050.0,110.25,3675.0,775.95] 
    ,并且使列表对象指向该组新值,而旧值会在Python的垃圾数据回收的时候被清除掉③,从图中我们可以清楚的看出,为什么包含列表参数的程序addinterest修改了列表的值?但程序addinterest结束时存储在amounts中的是新balances的值,实际上变量amounts从来没有被改变过。”

    “它(amounts,作者注)仍然指向的是调用addinterest函数之前的同一个列表,只是当控制返回到调用函数中时,列表呈现了被修改的状态”②

    最后是得出结论,原话如下:

    “通过上述过程我们可以了解到:Python的参数是通过值来传递的。但是如果变量是可变对象,比如是列表或者是图形对象,返回到调用程序后,该对象会呈现出被修改的状态。”

    ^_^ 
    注:课程原始视频部分结束。

    看了老师的这段讲解之后产生了很多疑问:在前面(①处)讲的amounts是不能被修改的,但是在(②处)又说列表呈现了被修改的状态,这不是自相矛盾吗?在(③)处讲列表创建了新值并且使列表指向了新值,这里不就是说amounts发生了改变吗?怎么能说没变呢?最后结论也是列表呈现出了被修改的状态。这个结论云山雾绕,看得人似懂非懂。

    那在Python3中参数变量是列表,在调用函数完返回后到底是被修改了还是没被修改呢?

    为了弄清这个问题,我做了一个实验,id()可以查看变量在内存中的地址,这个值相当于一个对象的“身份证”。

    # 处理多个银行账户的余额信息
    def addInterest(balances, rates):
    print()
    print("第二处", id(balances))
        for i in range(len(balances)):
            balances[i]= balances[i]*(1+rates)
            print()
            print("第三处",id(balances))
    def test():
        amounts = [1000,105,3500,739]
        print()
        print("第一处",id(amounts))
        rate = 0.05
        addInterest(amounts, rate)
        print()
        print(amounts)
        print()
        print("第四处",id(amounts))
    test()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出结果:

    第一处 41203656
    
    第二处 41203656
    
    第三处 41203656
    
    第三处 41203656
    
    第三处 41203656
    
    第三处 41203656
    
    [1050.0, 110.25, 3675.0, 775.95]
    
    第四处 41203656
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这个实验中可以清楚的看到,amounts这个对象的身份证号码在整个程序运行过程中从未变过,而非视频中老师讲的创建了新的列表对象。所以amounts作为一个列表对象在程序运行过程中是被直接修改了,是的就是直接被修改了,而非指向新balances的值。为什么可以得出这一结论?我们可以看下第一、三处的id,在未进入函数之前id是41203656(第一处),进入函数之后对象id仍然未变,函数运行完返回之后对象id仍然未变!

    所以结论应该这样写会比较清楚:

    改变参数值值的函数: 
    实参传给形参时,python的参数是通过值来传递的; 
    如果变量是可变对象(如列表或者图形对象),该对象会在函数中会被直接修改,返回到调用程序后也是被修改后的状态。

    那是不是Python3中函数都是像这种传递方式呢?我们对课程视频中的另一个例子做一个简单的修改。

     # 计算单个银行账户余额
    def addinterest(balance, rate):
        print("第二处", id(balance))
        newBalance = balance * (1 + rate)
        print()
        print("第三处", id(balance))
        print()
        print("第四处", id(newBalance))
        return newBalance
    
    
    def main():
        amount = 1000
        print("第一处", id(amount))
        print()
        rate = 0.05
        amount = addinterest(amount, rate)
        print()
        print("第五处", id(amount))
        print()
        print(amount)
        print("第六处", id(amount))
    
    
    main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    运行结果如下:

    第一处 33533648
    
    第二处 33533648
    
    第三处 33533648
    
    第四处 33563344
    
    第五处 33563344
    
    1050.0
    第六处 33563344
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    不是说好的直接修改的吗?怎么身份证又变了?其实这里的对象amount是个常数,即为不可变对象,当在函数中要对对象进行处理时,由于对象不可变,只能新建一个新对象,然后return出新的对象了。

    这个也就是目前网络上大部分博客的结论: 
    1、不可变对象作为函数参数,Python通过值传递; 
    2、 可变对象作为函数参数,Python通过引用传递。 
    注:Python中,数值类型(int和float)、字符串str、元组tuple都是不可变类型。而列表list、字典dict、集合set是可变类型。 
    (但是也有博客把这两个结论搞反了)

    但是也有博客提出了一个类似这样的例子

    def change(val):
        val = val + [10]
    nums = [0, 1]
    change(nums)
    print(nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出结果为

    [0, 1]
    
    • 1

    其实这里是写的不严谨,不能直接用加号添加列表元素 
    可以改为这样

    def change(val):
        newval = [10]
        val= val + newval
    
    nums = [0, 1]
    change(nums)
    print(nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    但是输出结果还是

    [0, 1]
    
    • 1

    难道上面的结论不对吗? 
    其实这里要补充另外一种情况:对于可变对象作为函数参数,且参数不指向其他对象时,相当于引用传递;否则,若参数指向其他对象,则对参数变量的操作并不影响原变量的对象值

    函数里的参数变量val指向了与nums不同的内存变量,所以函数里的参数变量val不影响原变量nums的值 
    3.png-23.8kB
    **这也是因为python的特性” 变量无类型,对象有类型 “。 
    变量是对内存空间的引用,当参数变量和原变量指向不同的内存空间时,操作互不影响。**

    用下面这个看下

    def change(val):
        newval = [10]
        print("第二处",id(val))
        val = val + newval
        print("第三处",id(val))
    nums = [0, 1]
    print("第一处",id(nums))
    change(nums)
    print("第四处",id(nums))
    print(nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果如下:

    第一处 39695944
    第二处 39695944
    第三处 39710024
    第四处 39695944
    [0, 1]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看到第一处的nums和第二处的val的内存地址完全一样,然后执行到第三处时,由于函数内VAL重新指向了别的内存变量,所以内存地址不同。但是最后结果要输出变量nums,即第一处第二处内存地址的值,所以和第三处的val就没关系了。其实这里的val是没有返回值的。

    想要直接在列表中添加元素可以写成这样:

    def change(val):
        val.append(10)
    nums = [0, 1]
    change(nums)
    print(nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出结果是

    [0, 1, 10]
    
    • 1

    关于变量无类型,对象有类型可以这样理解:只有放在内存空间中的对象(也就是数据)才有类型,而变量是没有类型的。

    如果还是不明白可以做这样一种比喻:变量就好比钓鱼者,湖水就好像内存,里面有各种各样的鱼,它们就是对象。钓鱼者(变量)的任务就是用某种方式把自己和鱼(对象)通过鱼线连接起来。那么,鱼(对象)是有类型的,有鲢鱼、鲫鱼、带鱼。钓鱼者(变量)没有类型,他钓到不同类型的鱼(对象)。

    用钓鱼的比喻解释下上面的例子

    def change(val):
        newval = [10]
        val= val + newval
    
    nums = [0, 1]
    change(nums)
    print(nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1、钓鱼人已经钓了一桶鱼用nums桶装着,nums桶可以装很多鱼。

    2、现在提着这个nums桶继续在湖里钓鱼,这时候nums桶暂时叫做装鱼桶val,突然钓鱼人钓了一条大鱼,发现装鱼桶val装不下,于是钓鱼人又在渔具店买了另一个大的装鱼桶VAL,把大鱼和之前的鱼一块装了。

    钓鱼活动结束。 
    3、最后要看看那个叫nums的桶有哪些鱼,这时候当然只能看之前的情况。

    即这个结论:对于可变对象作为函数参数,且参数不指向其他对象时,相当于引用传递;否则,若参数指向其他对象,则对参数变量的操作并不影响原变量的对象值。

    同样的针对其他两个结论,也可以用这个比喻解释:

    def change( val):
        newval = val + 10
        return  newval
    num = 1
    num = change(num)
    print(num)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1、钓鱼人手上的东西num是个小蚯蚓。
    2、钓鱼人拿着num去湖边钓鱼,小蚯蚓被大鱼吃了,钓鱼人钓到了一条大鱼,钓鱼人拿着鱼回家。
    钓鱼活动结束。
    3、问钓鱼人手上现在拿着什么东西num?当然是一条大鱼。
    
    • 1
    • 2
    • 3
    • 4
    def change(val):
        val.append(10)
    nums = [0, 1]
    change(nums)
    print(nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1、钓鱼人提着一个叫nums的桶,桶里装着2条鱼 2、钓鱼人来到湖边钓鱼,此时桶暂时叫装鱼桶val,钓鱼人钓到了一条鱼放进装鱼桶val。 
    钓鱼活动结束。 
    3、看看钓鱼人桶里的有几条鱼。

    总结来说:

    **对于不可变对象作为函数参数,相当于C系语言的值传递; 
    对于可变对象作为函数参数,且参数不指向其他对象时,相当于C系语言的引用传递。 
    对于可变对象作为函数参数,参数指向其他对象,对参数变量的操作不影响原变量的值。**

    链接:

    https://blog.csdn.net/wps20001990/article/details/79394502

    萍水相逢逢萍水,浮萍之水水浮萍!
  • 相关阅读:
    SQL 通配符
    正则表达式
    与运算(&)、或运算(|)、异或运算(^)、右移运算符(>>>)本质介绍
    博客园博客目录自动生成(页面目录)
    Linux查看并杀死被占用的端口
    Eclipse的环境配置
    L-Rui
    Web页面弹出窗口代码大全
    linux-用户
    linux-网络
  • 原文地址:https://www.cnblogs.com/AIBigTruth/p/9651383.html
Copyright © 2020-2023  润新知