Python参数内存分析

505 阅读5分钟

这是我参与更文挑战的第 14 天,活动详情查看: 更文挑战

1.参数简介

在上一张节,我们学习函数,涉及到相关的形参和实参

那我们本期就来详细学习一下参数传递的过程是怎么样的?

1.1 什么是参数

参数分为形参和实参。

形参名叫形式参数,相当于X,用于占位用

实参名叫实际参数,传入具体的值给函数进行处理的参数

  • 形参
  1. 定义函数时在圆括号里定义的参数(相当于X,占位)
  2. 只能在函数里使用
  • 实参
  1. 调用函数时传入的具体参数
  2. 通过函数后面的括号传递给函数,让函数处理的值
  • 形参与实参的关系
  1. 形参用于函数定义,实参用于函数调用
  2. 形参在函数起到占位;实参在函数调用后传入给函数处理
  3. 实参涉及到参数传递,形参主要有5种类型
1.3 参数分类
序号名字作用
1必备参数调用函数时以正确的顺序传入函数
2关键字参数调用函数时通过名字传入参数值
3默认参数指定形参默认值
4不定长参数允许形参前加入*号,调用函数可以接收多个值,当成元组传入函数
5逆向参数收集调用函数时拆分列表/元组/字典等对象传入给函数参数

本次,先大概了解参数类型,我们下期详细学习。

2.参数传递内存分析

函数的参数传递本质上是:从实参到形参的赋值操作。

Python中,一切都是对象。所有的赋值操作都是“引用赋值”。所以,Python中参数传递都是“引用传递”,不是“值传递”。

具体分两类:

  1. 对可变对象,进行写操作,直接用于源对象本身
  2. 对不可变对象,进行写操作,会产生一个新的对象空间,并用于新的值填充这块空间。

好啦,我们再来复习一下,可变对象和不可变对象。

  • 可变对象

    字典、列表、集合、自定义的对象等

  • 不可变对象

    数字、字符串、元组、函数等

2.1 传递可变对象

传递参数是可变对象,实际传递的还是对象的引用。在

函数体中不创建新的对象拷贝,而是可以直接修改传递的对象。

接下来我们具体来看看传递可变对象的过程

# 定义一个列表
a = [1, 2]
# 定义一个函数,列表后面添加元素
def listappend(b):
    print("b:",b)
    print("b:",id(b))  # b 和 a 是同一个对象
    b.append(30)

# 调用listappend()函数,传入实参变量a
listappend(a)
print("a:",a)
print("a:",id(a))

  1. 定义实参[1,2] 和 listappend函数对象

在栈内存中分别创建变量a 和 listappend栈区

在listappend栈区创建变量b

传递可变对象1

  1. 调用函数listappend()函数

实参对象[1,2]传入给函数listappend()形参b。

实参变量a 和 b 地址是一样

由于传入的实参a是列表数据类型,属于可变对象,可变的对象特点是地址不变,值可以改变。

因此,仍然在变量a对象[1,2]里末尾进行添加对象30的引用

最后调用print(),把结果打印在电脑屏幕上

传递可变对象2

2.2 传递不可变对象

传递参数是不可变对象(如:num,str,tuple等),实际传递的还是对象的引用。

在赋值操作时,由于不可变对象无法修改,系统系统会创建新创建一个对象。

同样,我们还是来看个栗子

# 定义a,Sum函数
a = 10
def Sum(b):
    print("b:",b)
    print("b:", id(b))  # a 和 b 传递进来的是对象10的地址

    b = b + 100  # 由于对象10是不可变对象,因此会创建新的地址空间

    print("b:",b)  # b变量已经重新指向新对象
    print("b:", id(b))

# 调用函数
Sum(a)
print("a:",a)
print("a:", id(a))
  1. 定义实参为10,函数Sum()

在栈内存中分别创建变量a 和 Sum栈区

在Sum栈区创建变量b

传递不可变对象1

  1. 调用函数Sum()

把变量a 的对象10传入给Sum()形参b

由于对象10是不可变对象,因此改变的话,要重新要重新创建新地址

变量b重新指向新的地址(计算结果)

传递不可变对象2

2.3 传递不可变对象含可变对象

具上一章节深浅拷贝的学习,了解深浅拷贝原理。

传递参数是不可变对象,在写操作是,会创建一个新对象的拷贝。这个拷贝的过程就是浅拷贝(引用拷贝),而不是深拷贝。

举个列子

# 定义一个不可变对象
a = (1,[3, 4])
def test(b):
    print("b:", id(b))
    print("b[1]:", id(b[1]))
    b[1][1] = 100

    print("b:",b)
    print("b:", id(b))
    print("b[1]:", id(b[1]))

#调用函数
test(a)
print("a:",a)
print("a:", id(a))
  1. 定义变量a和test()函数

在栈区创建变a 指向堆区对象(1,[2,3])

在栈区创建test栈区,并且在test栈区创建变量b

传递不可变对象含可变对象1

  1. 调用函数

变量a对象传入给函数test变量b

变量a和变量指向同一个地址(1,[2,3])

由于对象a[1]是可变对象,因此无需新创建空间

a[1][1]的位置上更概引用,指向对象100位置

传递不可变对象含可变对象2

总结

经过上面的介绍,我们对形参和实参有了认识,并且对参数传递进行深刻的学习。

接下来,我们后续会继续学习,前面了解到参数类型及用法。

好啦,以上是本期内容,欢迎大佬们评论区指正,下次见~