26. Python中函数参数的按对象传递

《Python编程的术与道:Python语言入门》视频课程
《Python编程的术与道:Python语言入门》视频课程链接:https://edu.csdn.net/course/detail/27845

函数参数的按对象传递

如何将函数调用中的参数传递给函数的参数(即函数参数的自变量的评估策略),因编程语言而异。

最常见的评估策略是“按值调用”(“Call by Value” )和“按引用调用”(“Call by Reference”):

按值调用(Call by Value )

最常见的策略是按值调用评估,有时也称为按值传递。例如,用于C和C ++。按值传递中,对参数表达式进行求值,并将求值结果绑定到函数中的相应变量。因此,如果表达式是一个变量,则其值将赋值(复制)到相应的参数。这样可以确保在函数返回时,调用者作用域内的变量将保持不变。

按引用调用(Call by Reference)

在按引用调用评估(也称为按引用传递)中,函数获取对参数的隐式引用,而不是其值的副本(copy)。函数可以修改自变量,即可以更改调用者作用域内的变量值。

通过按引用传递,不需要复制参数,可以节省计算时间和内存空间。
另一方面,这具有以下缺点:在函数调用中会意外地更改变量。因此,为不更改这些值,必须特别注意保护这些值。

许多编程语言都支持按引用传递,例如C或C++,但是Perl将其用作默认调用。

Python中的参数调用(传递)

Python使用一种机制,称为"Call-by-Object"(“按对象调用”),有时也称为"Call by Object Reference"(“按对象引用调用”)、或"Call by Sharing"(“按共享调用”)、或赋值传递 (pass by assignment)、或按对象传递

  1. 如果将不可变(immutable)的参数(例如整数、字符串或元组)传递给函数,则传递的行为类似于按值调用。对象引用传递给函数参数,但它们无法在函数中更改,因为它们根本无法更改。

  2. 如果传递可变(mutable)参数,则有所不同。它们也通过对象引用传递,但是可以在函数中对其进行更改。如果我们将一个列表传递给函数,则必须考虑两种情况:

  • 列表元素可以就地更改,即,在调用者的作用域内,列表也将更改。
  • 如果将一个新列表赋值给该名称,则旧列表将不受影响,即,调用者作用域内的列表将保持不变。

首先,让我们看一下整数变量。只要不更改参数,函数内部的参数仍是对arguments变量的引用。一旦为其赋值了新值,Python就会创建一个单独的局部变量。调用者的变量不会以这种方式被更改:

def ref_demo(x):
    print("x=",x," id=",id(x))
    x=35
    print("x=",x," id=",id(x))
x = 9
id(x)
140730833478288
ref_demo(x)
x= 9  id= 140730833478288
x= 35  id= 140730833479120
id(x)
140730833478288
x
9

在上面的示例中,使用了id() 函数,该函数将对象作为参数。 id(obj)返回对象“ obj”的"identity"(“标识”)。此标识,即函数的返回值,是一个整数,对于该对象在其生存期内是唯一且恒定的。

如果调用函数ref_demo():可以看到在主作用域中,x具有一个标识。在ref_demo()函数的第一个print语句中,使用了主作用域中的x,因为可以看到有相同的标识。在将值35赋值给x之后,x获得一个新的标识,即与全局x分开的存储位置。因此,当回到主作用域时,x仍具有原始值9。

这意味着Python最初的行为类似于按引用调用,但是一旦更改了此类变量的值,即,一旦为其赋值了新对象,Python就会切换为按值调用。这意味着将创建局部变量x并将全局变量x的值复制到其中。

那么对于上面的例子,是不是就没有办法改变 x 的值了呢?答案当然是否定的,我们只需稍作改变,让函数返回一个新变量,赋值给 x。这样,x 就指向了一个新的值为 35 的对象,x 的值也因此变为 35。

def ref_demo(x):
    print("x=",x," id=",id(x))
    x=35
    print("x=",x," id=",id(x))
    return x
x = 9
id(x)
140730833478288
x=ref_demo(x)
x= 9  id= 140730833478288
x= 35  id= 140730833479120
print(x)
id(x)
35





140730833479120

函数的副作用

如果该函数除了产生返回值之外,还以其他方式修改了调用者的环境,则该函数具有副作用。 例如,一个函数可能会修改全局或静态变量、修改其参数之一、抛出异常、将数据显示或写入文件等。

在某些情况下,这些副作用是预期的,即它们是所需功能的一部分。 但是在其他情况下,则不需要它们,它们是隐藏的副作用。

在本节中,我们关注的副作用是:对于作为参数传递给函数的变量,副作用会更改一个或多个全局变量。

假设,将一个列表传递给函数。 我们希望该函数不更改此列表。

首先,让我们看一个没有副作用的函数。 将一个新列表分配给func1()中的参数列表后,将为列表创建一个新的存储位置,并且列表成为一个局部变量。

def no_side_effects(cities):
    print(cities)
    cities = cities + ["Birmingham", "Bradford"]
    print(cities)

locations = ["London", "Leeds", "Glasgow", "Sheffield"]
no_side_effects(locations)
print(locations)
['London', 'Leeds', 'Glasgow', 'Sheffield']
['London', 'Leeds', 'Glasgow', 'Sheffield', 'Birmingham', 'Bradford']
['London', 'Leeds', 'Glasgow', 'Sheffield']

如果使用增强的赋值运算符+=来添加列表,则情况将发生大变化。

def side_effects(cities):
    print(cities)
    cities += ["Birmingham", "Bradford"]
    print(cities)

locations = ["London", "Leeds", "Glasgow", "Sheffield"]
side_effects(locations) 
print(locations)
['London', 'Leeds', 'Glasgow', 'Sheffield']
['London', 'Leeds', 'Glasgow', 'Sheffield', 'Birmingham', 'Bradford']
['London', 'Leeds', 'Glasgow', 'Sheffield', 'Birmingham', 'Bradford']

我们可以看到BirminghamBradford也包括在全局列表locations中,因为+=用作一个就地操作(in-place operation)执行。

可以通过将列表的副本传递给该函数来防止这种副作用。 因为列表中没有嵌套结构,浅拷贝就足够了。

def side_effects(cities):
    print(cities)
    cities += ["Paris", "Marseille"]
    print(cities)
locations = ["Lyon", "Toulouse", "Nice", "Nantes", "Strasbourg"]
side_effects(locations[:])
['Lyon', 'Toulouse', 'Nice', 'Nantes', 'Strasbourg']
['Lyon', 'Toulouse', 'Nice', 'Nantes', 'Strasbourg', 'Paris', 'Marseille']
print(locations)
['Lyon', 'Toulouse', 'Nice', 'Nantes', 'Strasbourg']

我们可以看到,全局列表locations没有受到函数执行的影响。

总结:

Python 中参数的传递既不是按值传递,也不是按引用传递,而是按对象传递(或称按对象引用传递)。

这里的按对象传递,不是指向一个具体的内存地址,而是指向一个具体的对象。如果对象不可变,简单的赋值只能改变其中一个变量的值,其余变量则不受影响。如果对象是可变的,当其改变时,指向这个对象的变量也会改变。

  • 如果想通过函数来改变某个变量的值,通常有两种方法:

    1. 直接将可变数据类型(比如列表,字典,集合)当作参数传入,直接在其上修改

    2. 创建一个新变量,来保存修改后的值,然后将其返回给原变量。 这种方法对于不可变对象也是可以的。

  • 如果想通过函数而不改变一个可变对象的变量的值,可将可变对象的拷贝传入函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bai666ai

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

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

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

打赏作者

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

抵扣说明:

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

余额充值