Python函数的默认参数设计示例详解

作者:Mr-chen 时间:2021-03-23 04:31:58 

在Python教程里,针对默认参数,给了一个“重要警告”的例子:


def f(a, L=[]):
 L.append(a)
 return L

print(f(1))
print(f(2))
print(f(3))

默认值只会执行一次,也没说原因。会打印出结果:

[1]
[1, 2]
[1, 2, 3]

因为学的第一门语言是Ruby,所以感觉有些奇怪。 但肯定的是方法f一定储存了变量L。

准备知识:指针

p指向不可变对象,比如数字。则相当于p指针指向了不同的内存地址。

p指向的是可变对象,比如list。list自身的改变,并不会改变list对象自身所在的内存地址。所以p指向的内存地址不变。


>>> p = 1
>>> id(p)
>>> p = p + 1
>>> id(p)
>>> p = 11
>>> id(p)

>>> p = []
>>> id(p)
>>> p.append(11)
>>> id(p)

根本原因

Python函数的参数默认值,是在编译阶段就绑定了。(写代码时就定义了。)

下面是一段从Python Common Gotchas中摘录的原因解释:

Python's default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

由此可知:

  1. 在运行代码时,运行到函数定义时,默认参数的表达式就被执行了。

  2. 函数调用时,不会再次运行默认参数的表达式。⚠️ 这点和Ruby完全不同。

  3. 由此可知,如果默认参数,指向一个不变对象,例如L = 1。那么在函数调用时,在函数体内对L重新赋值,L其实是一个新的指针, 指向的是一个新的内存地址。而原来默认参数L本身及指向的内存地址,已经储存在最开始编译时的函数定义中。可以用__default__查看。

  4. 如果默认参数指向的是一个可变对象,如list, 那么L.append(a)是对可变对象自身的修改,L指向的内存地址不变。所以每次调用函数,默认参数取出的都是这个内存地址的对象。

第三条,修改上面的例子:


def f(a, L = 1):
 L = a
 print(id(L))
 return L

print("self",id(f.__defaults__[0]))
print(f(1))
print("self",id(f.__defaults__[0]))
print(f(33))
print("self",id(f.__defaults__[0]))

#运行结果:
self 4353170064
1
self 4353170064
33
self 4353170064

默认参数L,在编译阶段就绑定了,储存在__default__内。函数体内的L = a表达式,生成的是新的变量。返回的L是新的变量,和默认参数无关。

第四条,还是上面的例子, 改一下默认参数的类型为可变对象list:


def f(a, L = []):
 L.append(a)
 print(id(L))
 return L
# L = f(1)
print("self",id(f.__defaults__[0]))
print(f(1))
print("self",id(f.__defaults__[0]))
print(f(33))
print("self",id(f.__defaults__[0]))

#返回结果
self 4337586048
[1]
self 4337586048
[1, 33]
self 4337586048

由id号可知,返回的是默认参数自身。

如何避免这个陷阱带来不必要麻烦


def f(a, L = None):
 if L is None:
   L = []
 L.append(a)
 return L

为什么Python要这么设计

 StackOverflow 上争论很多。

Ruby之所以没有这个问题,我想是因为Ruby的def关键字定义了一个封闭作用域,任何数据都必须通过参数传入到方法内,才能用。

和Ruby比,Python参数的作用被大大消弱了。Python的出现晚于Ruby,其创始人肯定参考了Ruby的设计。抛弃了这个设计,选择了类似javascript的函数方式。def定义的函数,执行时是可以接收外部作用域的变量的。

有观点认为:

出于Python编译器的实现方式考虑,函数是一个内部一级对象。而参数默认值是这个对象的属性。在其他任何语言中,对象属性都是在对象创建时做绑定的。因此,函数参数默认值在编译时绑定也就不足为奇了。

本文参考了:http://cenalulu.github.io/python/default-mutable-arguments/#toc1 ,并加入了自己的理解。

来源:https://www.cnblogs.com/chentianwei/p/11963383.html

标签:python,函数,参数
0
投稿

猜你喜欢

  • W3C优质网页小贴士(一)

    2008-04-06 16:35:00
  • oracle命令行删除与创建用户的代码

    2009-03-02 10:54:00
  • Python random库使用方法及异常处理方案

    2023-10-07 13:43:46
  • Python爬取视频(其实是一篇福利)过程解析

    2021-09-21 17:52:11
  • php 无法载入mysql扩展

    2023-09-07 13:07:40
  • Django2.1.7 查询数据返回json格式的实现

    2023-09-28 09:54:20
  • 使用MySQL内建复制功能

    2009-12-15 21:36:00
  • python清除字符串前后空格函数的方法

    2023-10-14 07:12:57
  • 基于php解决json_encode中文UNICODE转码问题

    2023-07-02 20:51:22
  • django filters实现数据过滤的示例代码

    2023-11-20 03:59:22
  • python3+PyQt5实现柱状图

    2023-06-02 22:19:36
  • Python标准库defaultdict模块使用示例

    2022-03-02 04:35:05
  • 100%全屏布局设计

    2009-05-15 12:24:00
  • Python+tkinter制作经典登录界面和点击事件

    2022-12-04 03:32:25
  • JSP组件commons-fileupload实现文件上传

    2023-07-03 03:16:01
  • django如何自定义manage.py管理命令

    2023-04-08 01:10:47
  • python使用Plotly绘图工具绘制散点图、线形图

    2022-06-24 08:11:50
  • Python Flask框架实现Proteus仿真Arduino与网页数据交互

    2022-01-18 08:43:16
  • 高质量Python代码编写的5个优化技巧

    2022-04-20 16:49:16
  • SQL Server2000的安全策略

    2007-08-06 17:14:00
  • asp之家 网络编程 m.aspxhome.com