从内存管理的角度看,Python语言的变量和参数传递情况图解

从内存管理的角度看,Python语言的变量和参数传递情况图解

概述

从内存管理的角度来看,Python中的变量和参数传递有一些特点:

☆ 变量是对象的引用:在Python中,变量实际上是对象的引用,而不是对象本身。当给一个变量赋值时,实际上是将变量指向了一个对象。这意味着变量可以指向不同类型的对象,并且可以在程序中随时改变指向的对象。

☆ 引用计数:Python使用引用计数来管理内存。每个对象都有一个引用计数,表示有多少个变量引用了该对象。当引用计数为0时,对象将被自动回收。当一个变量不再引用一个对象时,引用计数会减少。当引用计数为0时,对象的内存将被释放。

☆ 对象的可变性:Python中的对象分为可变对象和不可变对象。可变对象(如列表、字典)的值可以被修改,而不可变对象(如整数、字符串、元组)的值不能被修改。这意味着如果修改了一个可变对象,那么所有引用这个对象的变量都会受到影响。

☆ 参数传递方式:在Python中,函数的参数传递是按值调用(call by value) 来传递的(其中的 值 始终是对象的 引用 而不是对象的值)——实际上,按对象引用调用(call by object reference)调用这种说法更好。对于不可变对象(如整数、字符串、元组),由于它们的值不能被改变,所以函数内部对这些对象的修改实际上是创建了一个新的对象。因此,函数内部的修改不会影响到函数外部的实际参数。对于可变对象(如列表、字典),由于它们的值可以被改变,所以函数内部对这些对象的修改会直接改变原始对象的值。因此,函数内部的修改会影响到函数外部的实际参数。

在Python中,id()函数用于获取对象的唯一标识符(即内存地址)。每个对象(Python语言中,所有的 数据 都被称之为 对象)在内存中都有一个唯一的标识符,可以通过id()函数来获取。

例如id(8.53),它会返回8.53的唯一标识符(内存地址)。这个标识符是一个整数值,用于唯一标识该对象在内存中的位置,类似如2200635092656,注意,这个值在不同的运行时环境中可能会有所不同,因为它取决于内存管理和对象分配的具体实现。Python的内存管理和对象分配模型是自动的,由Python的内存管理器负责。

先看下面情况

解释:在Python中,id()函数返回对象的内存地址,这是每个对象的唯一标识符。

首先,当调用id(21.37)时,Python创建了一个浮点数对象21.37并返回了它的内存地址,即2491682696880。如下图所示:

然后,创建了一个变量a并将其赋值为21.37。此时,Python再次创建了一个新的浮点数对象21.37(因为浮点数是不可变的),并将a指向这个新对象。因此,当调用id(a)时,它返回的是这个新对象的内存地址,即2491682692176。如下图所示:

接下来,创建了一个新的变量b并将其赋值为a。在这种情况下,Python并没有创建新的对象,而是让b指向了a所指向的那个对象。因此,a和b实际上是指向同一个对象,所以id(b)返回的也是同一个内存地址2491682692176。如下所示图:

最后,当打印b时,它显示的是b所指向的那个对象的值,即21.37。

提示:Python中变量赋值语句 variable = expression的机制:

1.计算表达式(expression)的值:Python首先会计算等号右边的表达式的值。

2.创建新对象:Python会为这个值创建一个新的对象。

3.变量(variable)绑定:Python会将变量名绑定到新创建的对象,或者说变量指向这个对象。

这是Python变量赋值的基本机制。需要注意的是,对于某些特定类型的对象——如小整数(-5到256之间的整数),和特定短字符串,如比较短的只包含ASCII字符,不包含空格,数字或者特殊字符的字面量字符串。当Python解释器启动时,它会预先创建并缓存一定范围内的整数和一些常用的短字符串对象。当你在代码中使用这些值时,Python并不会创建新的对象,而是直接引用已经存在的对象。这就是所谓的"interning"机制。这种机制可以提高Python的运行效率,因为对于这些常用的小整数和短字符串,Python可以避免反复创建和销毁对象,从而节省内存和CPU资源。

再看如下情况:

解释:在Python中,小整数对象(通常是-5到256之间的整数)是预先创建并重复使用的。这是为了优化内存使用和性能,因为这些小整数在大多数Python程序中都会频繁使用。所以当创建一个变量a并赋值为2,然后创建一个变量b并赋值为2,它们实际上都是指向同一个内存对象。

对于大整数(通常是大于256的整数),Python会为每个新的整数创建一个新的对象。所以当创建一个变量a并赋值为1500,然后创建一个变量b并赋值为1500,它们实际上是指向两个不同的内存对象。这就是为什么id(a)和id(b)返回不同的值。参见下图:

Python语言的变量和赋值情况

Python语言中,所有的 数据 都被称之为 对象。

每个整数、小数、字符串,后面要学的 字典、元组、列表 等, 都是对象。

编程语言为了方便操作数据对象,也需要给对象起一个名字,称之为 变量名 , 有时也简称 变量。

Python中的变量,就是 对象的名称。

Python中,名称 用于指代对象。 名称是通过名称绑定操作来引入的。

【见官方文档 命名与绑定

https://docs.python.org/zh-cn/3/reference/executionmodel.html#naming-and-binding

提示:在Python的官方文档中更多地使用了“名称”(name)这个术语来。但是在实际的编码和交流中,人们也常常使用“变量”(variable)这个术语来描述Python中的名称。】

Python 是一门动态类型的语言,所以我们无须预先声明变量类型,直接对变量赋值即可。

可以使用赋值语句来创建变量并使其引用一个数据。当一个变量表示存储器中的某个值时,也称这个变量引用(reference)了这个值。

赋值语句的基本格式是:

variable = expression

等号(=)称为赋值运算符(assignment operator)。在这个基本格式中,variable 是变量名,expression 是一个值,或者能够产生一个值的代码。赋值语句执行结束后,位于等号左边的变量将引用位于等号右边的值。

提示:在给变量赋值之前是不能使用变量的。如果在变量被赋值之前就对它进行某种操作,如打印它,将导致一个错误。

【注意: Python 语言中的变量与大多数其他编程语言中的变量的工作方式不同。在大多数编程语言中,变量是保存值的内存位置。在这些编程语言中,当给变量赋值时,该值保存在变量的内存位置。而在 Python 语言中,变量是一个内存位置,它保存另一个内存位置的地址。将值赋给 Python 变量时,该值将存储在与该变量分离的内存位置。变量保存的是保存值的内存位置的地址。这就是为什么在 Python 中,不说变量“保存”一个值,而说变量“引用”一个变量。或者说,Python 中变量是代表存储在计算机存储器中的某个值的名称。】

变量之所以称为“变量”,是因为在程序执行过程中它们可以引用不同的值。下面就看看Python 中变量的赋值和再赋值情况。

创建了一个名为 dollars 的变量,并将 2.75 赋值给它

Dollars = 2.75

给变量 dollars 赋以一个新值99.95,当给变量 dollars 赋以一个新值99.95时,虽然旧值 2.75 依然保存在计算机的存储器中,但是因为没有变量引用它,所以这个值已不能再被使用。参见下图:

注意,当存储器中的值不再被变量引用时,Python 解释器将通过所谓的垃圾收集机制来进行处理,自动地将它们移出存储器。

【在python中,变量保存的是对象(值)的引用,id()函数可以获取变量在内存中的地址。在Python中,值可以放在内存的某个位置(地址),变量用于引用它们,给变量赋一个新值,原值不会被新值覆盖,变量只是引用了新值。顺便说明,Python的垃圾回收机制会自动清理不再被用到的值,所以不用担心计算机内存中充满被“丢弃”的无效的值。】

一定要牢记:在 Python 语言中,变量仅仅是引用存储器中某个数据的名字,并且可以引用任何类型的数据项。

x = 99 语句创建了名为 x 的变量,并赋给它 int 型的值 99。

将一个字符串 'Take me to your leader'赋值给变量 x时,变量 x 不再引用一个 int 型数据,而是引用字符串 'Take me to your leader'。

下面再看看这种情况:

a = 10

b = a

在上述的例子中,我们声明了一个变量 a,其值为 10,然后将 b 也指向 a,这是在内存中的布局是这样的,变量 a 和 b 会指向同一个对象 10,而不是给 b 重新生成一个新的对象。

当执行完 a = a + 10 后,因为整数是不可变对象,所以并不会将 10 变成 20,而是生成一个新的对象 20 ,然后 a 会指向这个新的对象。b 还是指向旧对象 10。

Python 允许你同时为多个变量赋值。例如:

a = b = c = 10

以面示例,创建一个整型对象,值为 10,从后向前赋值,三个变量被赋予相同的数值。

您也可以为多个对象指定多个变量。例如:

a, b, c = 10, 20, " hello"

以面示例,两个整型对象 10 和 20 的分配给变量 a 和 b,字符串对象 “hello” 分配给变量 c。

在Python中的参数传递情况

要讲清在Python中函数的参数传递,需要从Python中函数是什么讲起。

在Python中,函数是一种组织代码的方式,它将一段程序代码封装起来,以便于重复使用。函数可以接收输入参数,并返回一个或多个结果。这种封装可以使代码更加清晰、易读,也有助于代码的重用。Python提供了许多内置函数,如print()、len()等,可以直接使用。编程人员还可以自定义函数。自定义图示如下:

Python中的函数定义使用def关键字,后跟函数名、括号()和冒号。括号中可以放置函数的参数,参数之间用逗号分隔。函数的主体部分在冒号后的缩进块中定义。Python中定义函数语法如下:

def 函数名(参数列表):

    函数体

函数体由缩进的代码块组成。Python的函数体包括:

函数体包括以下几个部分:

文档字符串(docstring):文档字符串是可选的,用于描述函数的作用、参数、返回值等信息。它位于函数定义的第一行或第二行,使用三引号('''或""")括起来。文档字符串可以通过help()函数或在交互式环境中使用__doc__属性来访问。

语句(statements):函数体中可以包含任意数量的语句,用于执行具体的任务。这些语句可以是赋值语句、条件语句、循环语句等,根据函数的需求编写。

返回表达式(return expression):函数可以通过return语句返回一个值。返回值是可选的,如果没有return语句或者return后面没有表达式,函数将返回None。当函数执行到return语句时,函数将立即结束,并将返回值传递给调用函数的地方。

下面介绍python参数传递

python官方文档的说法【https://docs.python.org/zh-cn/3/tutorial/controlflow.html#defining-functions 】实参是使用 按值调用(call by value) 来传递的(其中的 值 始终是对象的 引用 而不是对象的值)——实际上,按对象引用调用(call by object reference)调用这种说法更好。具体来说:

如果传入的参数是不可变类型(如数字、字符串、元组),那么在函数体内修改参数的值,并不会影响到原来的变量。因为不可变类型的变量实际上是值的引用,当试图改变变量的值时,相当于是在创建新的对象。例如:

def change_number(num):
    num = 100

x = 10
change_number(x)
print(x)  # 输出:10

在上面的例子中,尽管在函数内部num的值被改变了,但是原变量x的值并没有改变。参见下图:

如果传入的参数是可变类型(如列表、字典),那么在函数体内修改参数的值,会影响到原来的变量。因为可变类型的变量存储的是一个地址,当试图改变变量的值时,实际上是在改变这个地址所指向的内容。例如:

def change_list(lst):
    lst.append(100)

x = [1, 2, 3]
change_list(x)
print(x)  # 输出:[1, 2, 3, 100]

在上面的例子中,函数内部对参数lst的修改影响到了原变量x的值。参见下图:

python的参数传递机制官方说法:按对象引用调用(call by object reference)。调用者和被调用者之间共享这个对象,而对于不可变对象,由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值来实现的。

 下面是官网文档节选【 https://docs.python.org/zh-cn/3/tutorial/controlflow.html#defining-functions

在调用函数时会将实际参数(实参)引入到被调用函数的局部符号表中;因此,实参是使用 按值调用(call by value) 来传递的(其中的 值 始终是对象的引用 而不是对象的值)[1] 。

[备注1]实际上,按对象引用调用(call by object reference) 这种说法更好,因为,传递的是可变对象时,调用者能发现被调者做出的任何更改(插入列表的元素)。

又:如何编写带有输出参数的函数(按照引用调用)?【见https://docs.python.org/zh-cn/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

请记住,Python 中的实参是通过赋值(assignment)传递的。由于赋值只是创建了对象的引用,所以调用方(caller)和被调用方(callee)的参数名都不存在别名,本质上也就不存在按引用调用(call-by-reference)的方式。通过以下几种方式,可以得到所需的效果。(下面是例子,略)

解析一下,这意味着:如果你尝试在函数内部重新赋值一个新的对象给参数,这个改变不会影响到函数外部的实际参数。因为在Python中,变量名和数据是分开的,变量名只是指向数据的引用,而不是数据本身。然而,你可以通过一些方法达到类似引用传递的效果。例如,你可以传递一个可变对象(如列表或字典),然后在函数内部修改这个对象。由于对象的引用被传递,所以这些修改会反映在函数外部。这就是所谓的“共享传递”(pass by sharing)。

 

附录

Python变量的认识理解 https://blog.csdn.net/cnds123/article/details/116768499 
python函数 https://blog.csdn.net/cnds123/article/details/108179769

多种(C++、Java、JavaScript、Python)编程语言参数传递方式介绍 https://blog.csdn.net/cnds123/article/details/132981086

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值