add by zhj: Python设计者为何将默认参数设计成这样呢?参见Python函数参数默认值的陷阱和原理深究

原文:https://github.com/acmerfight/insight_python/blob/master/Default_Parameter.md

文章的主题

不要使用可变对象作为函数的默认参数例如 list,dict,因为def是一个可执行语句,只有def执行的时候才会计算默认默认参数的值,所以使用默认参数会造成函数执行的时候一直在使用同一个对象,引起bug。

基本原理

在 Python 源码中,我们使用def来定义函数或者方法。在其他语言中,类似的东西往往只是一一个语法声明关键字,但def却是一个可执行的指令。Python代码执行的时候先会使用 compile 将其编译成 PyCodeObject.

PyCodeObject 本质上依然是一种静态源代码,只不过以字节码方式存储,因为它面向虚拟机。因此 Code 关注的是如何执行这些字节码,比如栈空间大小,各种常量变量符号列表,以及字节码与源码行号的对应关系等等。

PyFunctionObject 是运行期产生的。它提供一个动态环境,让 PyCodeObject 与运行环境关联起来。同时为函数调用提供一系列的上下文属性,诸如所在模块、全局名字空间、参数默认值等等。这是def语句执行的时候干的活。

PyFunctionObject 让函数面向逻辑,而不仅仅是虚拟机。PyFunctionObject 和 PyCodeObject 组合起来才是一个完整的函数。

下文翻译了一篇文章,有一些很好的例子。但是由于水平有限,有些不会翻译或者有些翻译有误,敬请谅解。如果有任何问题请发邮件到 acmerfight圈gmail.com,感激不尽

主要参考资料 书籍:《深入Python编程》 大牛:shell 和 Topsky

原文链接

Python对于函数中默认参数的处理往往会给新手造成困扰(但是通常只有一次)。

当你使用“可变”的对象作为函数中作为默认参数时会往往引起问题。因为在这种情况下参数可以在不创建新对象的情况下进行修改,例如 list dict。

>>> def function(data=[]):
... data.append(1)
... return data
...
>>> function()
[1]
>>> function()
[1, 1]
>>> function()
[1, 1, 1]

像你所看到的那样,list变得越来越长。如果你仔细地查看这个list。你会发现list一直是同一个对象。

>>> id(function())
12516768
>>> id(function())
12516768
>>> id(function())
12516768

原因很简单: 在每次函数调用的时候,函数一直再使用同一个list对象。这么使用引起的变化,非常“sticky”。

为什么会发生这种情况?

当且仅当默认参数所在的“def”语句执行的时候,默认参数才会进行计算。请看文档描述

https://docs.python.org/2/reference/compound_stmts.html#function-definitions

其中有下面一段

"Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call. This is especially important to understand when a default parameter is a mutable object, such as a list or a dictionary: if the function modifies the object (e.g. by appending an item to a list), the default value is in effect modified. This is generally not what was intended. A way around this is to use None as the default, and explicitly test for it in the body of the function,e.g.:

def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin

"

"def"是Python中的可执行语句,默认参数在"def"的语句环境里被计算。如果你执行了"def"语句多次,每次它都将会创建一个新的函数对象。接下来我们将看到例子。

用什么来代替?

像其他人所提到的那样,用一个占位符来替代可以修改的默认值。None

def myfunc(value=None):
if value is None:
value = []
# modify value here

如果你想要处理任意类型的对象,可以使用sentinel

sentinel = object()

def myfunc(value=sentinel):
if value is sentinel:
value = expression
# use/modify value here

在比较老的代码中,written before “object” was introduced,你有时会看到

sentinel = ['placeholder']

译者注:太水,真的不知道怎么翻译了。我说下我的理解 有时逻辑上可能需要传递一个None,而你的默认值可能又不是None,而且还刚好是个列表,列表不
可以写在默认值位置,所以你需要占位符,但是用None,你又不知道是不是调用者传递过来的那个

正确地使用可变参数

最后需要注意的是一些高深的Python代码经常会利用这个机制的优势;举个例子,如果在一个循环里创建一些UI上的按钮,你可能会尝试这样去做:

for i in range(10):
def callback():
print "clicked button", i
UI.Button("button %s" % i, callback)

但是你却发现callback打印出相同的数字(在这个情况下很可能是9)。原因是Python的嵌套作用域只是绑定变量,而不是绑定数值的,所以callback只看到了变量i绑定的最后一个数值。为了避免这种情况,使用显示绑定。

for i in range(10):
def callback(i=i):
print "clicked button", i
UI.Button("button %s" % i, callback)

i=i把callback的参数i(一个局部变量)绑定到了当前外部的i变量的数值上。(译者注:如果不理解这个例子,请看http://stackoverflow.com/questions/233673/lexical-closures-in-python)

另外的两个用途local caches/memoization

def calculate(a, b, c, memo={}):
try:
value = memo[a, b, c] # return already calculated value
except KeyError:
value = heavy_calculation(a, b, c)
memo[a, b, c] = value # update the memo dictionary
return value

(对一些递归算法非常好用)

对高度优化的代码而言, 会使用局部变量绑全局的变量:

import math

def this_one_must_be_fast(x, sin=math.sin, cos=math.cos):
...

这是如何工作的?

当Python执行一条def语句时, 它会使用已经准备好的东西(包括函数的代码对象和函数的上下文属性),创建了一个新的函数对象。同时,计算了函数的默认参数值。

不同的组件像函数对象的属性一样可以使用。上文用到的'function'

>>> function.func_name
'function'
>>> function.func_code
<code object function at 00BEC770, file "<stdin>", line 1>
>>> function.func_defaults
([1, 1, 1],)
>>> function.func_globals
{'function': <function function at 0x00BF1C30>,
'__builtins__': <module '__builtin__' (built-in)>,
'__name__': '__main__', '__doc__': None}

这样你可以访问默认参数,你甚至可以修改它。

>>> function.func_defaults[0][:] = []
>>> function()
[1]
>>> function.func_defaults
([1],)

然而我不推荐你平时这么使用。

另一个重置默认参数的方法是重新执行相同的def语句,Python将会和代码对象创建一个新的函数对象,并计算默认参数,并且把新创建的函数对象赋值给了和上次相同的变量。但是再次强调,只有你清晰地知道在做什么的情况下你才能这么做。

And yes, if you happen to have the pieces but not the function, you can use the function class in the new module to create your own function object.

Python中的默认参数(转)的更多相关文章

  1. python中的默认参数

    https://eastlakeside.gitbooks.io/interpy-zh/content/Mutation/ 看下面的代码 def add_to(num, target=[]): tar ...

  2. python 中函数的参数

    一.python中的函数参数形式 python中函数一般有四种表现形式: 1.def function(arg1, arg2, arg3...) 这种是python中最常见的一中函数参数定义形式,函数 ...

  3. Python进阶-函数默认参数

    Python进阶-函数默认参数 写在前面 如非特别说明,下文均基于Python3 一.默认参数 python为了简化函数的调用,提供了默认参数机制: def pow(x, n = 2): r = 1 ...

  4. C++中的默认参数规则

    C++中的默认参数规则 C++的默认参数规则其实是一个非常容易掉坑的规则,尤其是当一个函数拥有多个声明的时候,每个声明的默认参数可以各不相同,在调用时又可能与每个声明都不同:这篇博客稍微列举一下C++ ...

  5. Python中函数传递参数有四种形式

    Python中函数传递参数有四种形式 fun1(a,b,c) fun2(a=1,b=2,c=3) fun3(*args) fun4(**kargs) 四种中最常见是前两种,基本上一般点的教程都会涉及, ...

  6. 讲讲python中函数的参数

    python中函数的参数 形参:定义函数时代表函数的形式参数 实参:调用函数时传入的实际参数 列如: def f(x,y): # x,y形参 print(x, y) f(1, 2) # 1, 2 实参 ...

  7. python中的魔法参数:*args和**kwargs

    python中的魔法参数:*args和**kwargs def foo(*args, **kwargs):print 'args = ', argsprint 'kwargs = ', kwargsp ...

  8. 深入理解python中函数传递参数是值传递还是引用传递

    深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用 ...

  9. python函数参数中带有默认参数list的坑

    在python中函数参数中如果带有默认参数list遇到问题 先看一段代码 def f(x,l=[]): for i in range(x): l.append(i*i) print(l) print( ...

随机推荐

  1. (二)Redis 笔记——发布&订阅、事务、数据库操作

    1. Redis 发布订阅 1.1 概述 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. Redis 客户端可以订阅任意数量的频道. 下 ...

  2. C++中#include用错的后果

    今天做一个模块的时候遇到的这个问题.抽象一下,代码如下: 文件结构: 报错: 这个报错貌似说的是Box.h中的代码有问题.其实问题的根源在于Cup.h中多余的#include "Box.h& ...

  3. hmtl表单

    表单: <form id="" name="" method="post/get" action="负责处理的服务端&quo ...

  4. Java序列化的几种方式

    本文着重解说一下Java序列化的相关内容. 假设对Java序列化感兴趣的同学能够研究一下. 一.Java序列化的作用    有的时候我们想要把一个Java对象变成字节流的形式传出去,有的时候我们想要从 ...

  5. Linux中解压缩命令gzip和unzip的一点说明

    inux中解压缩命令gzip和unzip的一点说明 转载 2014年10月29日 20:37:35 20741   Linux 常用的压缩命令有 gzip 和 zip,两种压缩包的结尾不同:zip 压 ...

  6. OC 内存管理-02 autorelease 概念 以及用法

    (1) @autoreleasepool { }//自动释放池代表,池子将要被销毁,对池子中所有的对象进行一次release操作 (2) 不管你这个对象时在@autoreleasepool 之内创建的 ...

  7. LeetCode 递归(Recursion) 培训专题 讲解文章翻译 (附链接)

     递归 - 时间复杂度 在本文中, 我们主要介绍如何分析递归算法程序中的时间复杂度.. 在一个递归程序中, 它的时间复杂度 O(T) 一般来说就是他总共递归调用的次数 (定义为 R) 以及每次调用时所 ...

  8. Spring MVC Xml视图解析器

    XmlViewResolver用于在xml文件中定义的视图bean来解析视图名称.以下示例演示如何在Spring Web MVC框架使用XmlViewResolver. XmlViewResolver ...

  9. python 面向对象类成员(字段 方法 属性)

    一.字段 字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同, 普通字段属于对象 静态字段属于类 class Province: # 静态字段 countr ...

  10. 模式识别之概率分布---平均分布,正态分布,一阶滑动和,一阶线性回归 C语言编程

    http://wenku.baidu.com/view/11cb1669a98271fe910ef9c6.html