函数的参数作为引用时

Python 唯一支持的参数传递模式是共享传参(call by sharing)。多数面
向对象语言都采用这一模式,包括 Ruby、Smalltalk 和 Java(Java 的引
用类型是这样,基本类型按值传参)。
共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是
说,函数内部的形参是实参的别名。
这种方案的结果是,函数可能会修改作为参数传入的可变对象,但是无
法修改那些对象的标识(即不能把一个对象替换成另一个对象)。示例
8-11 中有个简单的函数,它在参数上调用 += 运算符。分别把数字、列
表和元组传给那个函数,实际传入的实参会以不同的方式受到影响。
示例 8-11 函数可能会修改接收到的任何可变对象


❶ 数字 x 没变。
❷ 列表 a 变了。
❸ 元组 t 没变。
与函数参数相关的另一个问题是使用可变值作为默认值,下一节会讨论

8.4.1 不要使用可变类型作为参数的默认值可选参数可以有默认值,这是 Python 函数定义的一个很棒的特性,这样我们的 API 在进化的同时能保证向后兼容。然而,我们应该避免使用可变的对象作为参数的默认值。下面在示例 8-12 中说明这个问题。我们以示例 8-8 中的 Bus 类为基础定义一个新类, HauntedBus,然后修改 init 方法。这一次,passengers 的默认值不是 None,而是 [],这样就不用像之前那样使用 if 判断了。这个“聪明的举动”会让我们陷入麻烦。示例 8-12 一个简单的类,说明可变默认值的危险

❶ 如果没传入 passengers 参数,使用默认绑定的列表对象,一开始
是空列表。
❷ 这个赋值语句把 self.passengers 变成 passengers 的别名,而没
有传入 passengers 参数时,后者又是默认列表的别名。
❸ 在 self.passengers 上调用 .remove() 和 .append() 方法时,修
改的其实是默认列表,它是函数对象的一个属性。
HauntedBus 的诡异行为如示例 8-13 所示。
示例 8-13 备受幽灵乘客折磨的校车

❶ 目前没什么问题,bus1 没有出现异常。
❷ 一开始,bus2 是空的,因此把默认的空列表赋值给self.passengers。
❸ bus3 一开始也是空的,因此还是赋值默认的列表。
❹ 但是默认列表不为空!
❺ 登上 bus3 的 Dave 出现在 bus2 中。
❻ 问题是,bus2.passengers 和 bus3.passengers 指代同一个列表。
❼ 但 bus1.passengers 是不同的列

问题在于,没有指定初始乘客的 HauntedBus 实例会共享同一个乘客列
表。
这种问题很难发现。如示例 8-13 所示,实例化 HauntedBus 时,如果
传入乘客,会按预期运作。但是不为 HauntedBus 指定乘客的话,奇怪
的事就发生了,这是因为 self.passengers 变成了 passengers 参数
默认值的别名。出现这个问题的根源是,默认值在定义函数时计算(通
常在加载模块时),因此默认值变成了函数对象的属性。因此,如果默
认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影
响。
运行示例 8-13 中的代码之后,可以审查 HauntedBus.init
象,看看它的 defaults 属性中的那些幽灵学生:

可变默认值导致的这个问题说明了为什么通常使用 None 作为接收可变值的参数的默认值。在示例 8-8 中,init 方法检查 passengers参数的值是不是 None,如果是就把一个新的空列表赋值给self.passengers。下一节会说明,如果 passengers 不是 None,正确的实现会把 passengers 的副本赋值给 self.passengers。下面详解

8.4.2 防御可变参数如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。例如,如果函数接收一个字典,而且在处理的过程中要修改它,那么这个副作用要不要体现到函数外部?具体情况具体分析。这其实需要函数的编写者和调用方达成共识。在本章最后一个校车示例中,TwilightBus 实例与客户共享乘客列表,这会产生意料之外的结果。在分析实现之前,我们先从客户的角度看看 TwilightBus 类是如何工作的。示例 8-14 从 TwilightBus 下车后,乘客消失了

❶ basketball_team 中有 5 个学生的名字。
❷ 使用这队学生实例化 TwilightBus。
❸ 一个学生从 bus 下车了,接着又有一个学生下车了。
❹ 下车的学生从篮球队中消失了!TwilightBus 违反了设计接口的最佳实践,即“最少惊讶原则”。
学生从校车中下车后,她的名字就从篮球队的名单中消失了,这确实让人惊讶。示例 8-15 是 TwilightBus 的实现,随后解释了出现这个问题的原因。示例 8-15 一个简单的类,说明接受可变参数的风险

❶ 这里谨慎处理,当 passengers 为 None 时,创建一个新的空列表。
❷ 然而,这个赋值语句把 self.passengers 变成 passengers 的别
名,而后者是传给 init 方法的实参(即示例 8-14 中的
basketball_team)的别名。
❸ 在 self.passengers 上调用 .remove() 和 .append() 方法其实会
修改传给构造方法的那个列表。
这里的问题是,校车为传给构造方法的列表创建了别名。正确的做法
是,校车自己维护乘客列表。修正的方法很简单:在 init 中,传
入 passengers 参数时,应该把参数值的副本赋值给
self.passengers,像示例 8-8 中那样做(8.3 节)

➊ 创建 passengers 列表的副本;如果不是列表,就把它转换成列表。在内部像这样处理乘客列表,就不会影响初始化校车时传入的参数了。此外,这种处理方式还更灵活:现在,传给 passengers 参数的值可以是元组或任何其他可迭代对象,例如 set 对象,甚至数据库查询结果,因为 list 构造方法接受任何可迭代对象。自己创建并管理列表可以确保支持所需的 .remove() 和 .append() 操作,这样 .pick() 和.drop() 方法才能正常运作。

注意:
除非这个方法确实想修改通过参数传入的对象,否则在类中直接把参数赋值给实例变量之前一定要三思,因为这样会为参数对象创建别名。如果不确定,那就创建副本。这样客户会少些麻

函数的参数作为引用时相关推荐

  1. SQL SERVER 自定义函数参数数量对调用时参数数量的影响

    parameter_name 用户定义函数的参数.CREATE FUNCTION 语句中可以声明一个或多个参数.函数最多可以有 1,024 个参数.函数执行时每个已声明参数的值必须由用户指定,除非该参 ...

  2. 深入浅出之函数的参数传递方式

    1 按值传递 按值传递也称传值,其传递形式是形参为普通变量,实参为表达式或变量,实参向形参赋值. 按值传递的特点是参数传递后,实参和形参不再有任何联系.需要注意的是,此时实参是表达式,故形参不可能给实 ...

  3. 深入解析Python中函数的参数与作用域

    传递参数 函数传递参数时的一些简要的关键点: 参数的传递是通过自动将对象赋值给本地变量名来实现的.所有的参数实际上都是通过指针进行传递的,作为参数被传递的对象从来不自动拷贝. 对于numbers,St ...

  4. 小议C++中函数的参数的传递

    c++中为每一个函数都维护了一个运行栈(活动记录),这个栈存储了与该函数相关的一系列信息,包括函数中声明的变量,传递给函数的实际参数,以及该函数的返回地址等,使用gdb调试器可以清楚的看到这一切.当调 ...

  5. C++ 引用与引用作为函数的参数

    对一个数据建立一个"引用",他的作用是为一个变量起一个别名.这是C++对C语言的一个重要补充. 如何建立一个引用 int a = 5;int &b = a;cout< ...

  6. 引用与引用作为函数的参数

    int InitStack(SqStack &S)与int InitStack(SqStack *S)有区别: 1.&S是引用,S和传入的形参变量共用同一个存储空间,相当于别名. 2. ...

  7. Go 学习笔记(15)— 函数(01)[函数定义、函数特点、多值返回、实参形参、变长参数,函数作为参数调用]

    1. 函数定义 Go 语言最少有个 main() 函数.函数声明告诉了编译器函数的名称,返回类型和参数. func funcName(parameter_list)(result_list) {fun ...

  8. python函数用法详解2(变量的作用域(全局变量、局部变量)、共享全局变量、函数返回值、函数的参数(位置参数、关键字参数、默认参数、不定长参数)、拆包、交换变量值、引用、可变和不可变类型)

    1. 变量作⽤域         变量作⽤域指的是变量⽣效的范围,主要分为两类:局部变量和全局变量. 局部变量         定义在函数体内部的变量,即只在函数体内部⽣效. def testA(): ...

  9. python怎么理解函数的参数_理解Python中函数的参数

    定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了.对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解 ...

最新文章

  1. 【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )
  2. boost::hana::flip用法的测试程序
  3. Java学习--设计模式之创建型模式
  4. 启用或禁用对 Exchange Server 中的邮箱的 POP3 或 IMAP4 访问
  5. MySQL 5.5 手册下载
  6. windows下pyhton_vitrualenv虚拟环境pycharm如何创建django项目
  7. C#.NET中数组、ArrayList和List三者的区别
  8. Makefile中创建一个以当前时间为文件夹名的文件
  9. 基于matlab的汉明码信道编码,信道编码仿真.doc
  10. 【学习笔记】C++ 编程规范——101条规则、准则与最佳实践
  11. FPGA开发中常见报错或警告汇总
  12. Linux Ubuntu 初学命令
  13. 无法重命名文件夹,错误0x80004005 未指定的错误
  14. 科研人必备英语改写神器——PARAPHRASER多语言改写
  15. IDEA:Lambda expression are not supported at language level ‘5‘
  16. 二项式分布和多项式分布
  17. 命令行把java项目打成jar包
  18. Visual Studio 2008 下载地址 V9各种版本官方下载网址
  19. Unity 你以为SetParent()是个很简单的API???!!
  20. google翻译网页不错

热门文章

  1. Anaconda3安装教程(详细)
  2. 周鸿祎:一个好团队不要超过3个人[联络易]
  3. onekey ghost下载_onekey ghost y6.3下载地址
  4. 2019年7月训练记录(更新ing)
  5. Ubuntu安装腾讯APP
  6. 2022-2028全球皮肤科冷冻外科装置市场现状及未来发展趋势
  7. WP采集插件免费wordpress采集挂机插件
  8. 企业数仓DQC数据质量管理实践篇
  9. html5网页录音和语音识别
  10. JAVA 的while循环和字符串的使用