大家都知道python的默认值是在函数定义时计算出来的, 也就是说默认值只会计算一次, 之后函数调用时, 如果参数没有给出,
同一个值会赋值给变量, 这会导致, 如果我们想要一个list默认值, 新手通常这么写:

def foo(a=[]):
a.append(3)
print a

其实是错误的,两次调用会这样的结果:

[3]
[3, 3]

其实应该这么写

def baz(a=None):
  a = a or []
a.append(3)
print a

两次调用输出以下结果:

[3]
[3]

这样好挫啊, 搞的虽然有默认值用法,但是我们却需要写的和js,lua一样, 我们不能像c++一样, 在函数运行时每次执行默认值么.
用decorator可以模拟下

import functools
import copy
def compute_default_value_for_each_call(func):
defaults = func.__defaults__
if not defaults:
return None
defaults = tuple(copy.copy(x) for x in defaults) @functools.wraps(func)
def wrapper(*args, **kwargs):
if func.__defaults__ != defaults:
func.__defaults__ = tuple(copy.copy(y) for y in defaults)
return func(*args, **kwargs) return wrapper @compute_default_value_for_each_call
def foo(b, a=[]):
if b:
a.append(3)
return a import timeit

这样两次调用foo(1), 结果为:

[3]
[3]

这个decorator有对未修改默认参数值做优化, 在我们不对默认值修改的情况下(比如打印变量a的内容), 性能有很大提升:

@compute_default_value_for_each_call
def foo(b, a=[]):
if b:
a.append(3)
return a def foo2(b, a=None):
a = a or []
if b:
a.append(3)
return a import timeit print timeit.timeit('foo(1)', setup='from __main__ import foo')
print timeit.timeit('foo(0)', setup='from __main__ import foo')
print timeit.timeit('foo2(1)', setup='from __main__ import foo2')
print timeit.timeit('foo2(0)', setup='from __main__ import foo2')

执行结果(调用1000000次的总时间)

4.32704997063
0.630109071732
0.445858955383
0.26370882988

性能上还过得去....

觉得这种方法性能低的同学, 还可以使用ast模块, 直接修改函数源代码, 达到a = a or []这样的性能. 但是实现起来略麻烦, 有机会我再试试.

====================================================

修改函数代码版本如下:

import inspect
import dis
import ast
import re
import traceback
import timeit def compute_default_value_for_each_call(func):
source = inspect.getsource(func)
m = re.search(r'^\s+', source)
if m:
m.group(0)
n = len(m.group(0))
lines = [line[n:] for line in str.splitlines(source)]
source = '\n'.join(lines)
root = ast.parse(source)
root.body[0].decorator_list.pop()
arg_names = [arg.id for arg in root.body[0].args.args]
default_nodes = root.body[0].args.defaults
arg_names = arg_names[len(arg_names) - len(default_nodes):]
body = root.body[0].body
n = len(arg_names)
for i in reversed(xrange(n)):
arg_name = arg_names[i]
default_node = default_nodes[i]
lineno = default_node.lineno
col_offset = default_node.col_offset
body.insert(0, ast.Assign(targets=[ast.Name(id=arg_name, ctx=ast.Store(),
lineno=lineno, col_offset=col_offset)],
value=ast.BoolOp(op=ast.Or(), lineno=lineno, col_offset=col_offset,
values=[ast.Name(id=arg_name, ctx=ast.Load(), lineno=lineno, col_offset=col_offset),
default_node]),
lineno=lineno, col_offset=col_offset))
root.body[0].args.defaults = [ast.Name(id='None', ctx=ast.Load(), lineno=old.lineno, col_offset=old.col_offset)
for old in default_nodes]
root.body[0].body = body
l = {}
exec compile(root, '<string>', mode='exec') in globals(), l
func = l[func.__name__]
return func def root2():
def main(): @compute_default_value_for_each_call
def root(a=([])):
a.append(3)
return a
print root()
print root()
main() print 'used in local function'
root2() def foo():
return 42 bar_count = 0 def bar():
global bar_count
if bar_count:
raise RuntimeError
bar_count += 1 @compute_default_value_for_each_call
def f1(a=foo()):
return a def f2(a=None):
a = a or foo()
return a @compute_default_value_for_each_call
def f3(a=bar()):
return a def f4(a=None):
a = a or bar()
return a print 'f1:'
dis.dis(f1)
print 'f2:'
dis.dis(f2) print 'f2 running time:'
print timeit.timeit('f2', setup='from __main__ import f2')
print 'f1 running time:'
print timeit.timeit('f1', setup='from __main__ import f1')
print 'f2 running time:'
print timeit.timeit('f2', setup='from __main__ import f2') print 'f3:'
dis.dis(f3)
print 'f4:'
dis.dis(f4) try:
f3()
except:
print traceback.format_exc()
try:
f4()
except:
print traceback.format_exc()

输出:

used in local function
[3]
[3]
f1:
2 0 LOAD_FAST 0 (a)
3 JUMP_IF_TRUE_OR_POP 12
6 LOAD_GLOBAL 0 (foo)
9 CALL_FUNCTION 0
>> 12 STORE_FAST 0 (a) 3 15 LOAD_FAST 0 (a)
18 RETURN_VALUE
f2:
83 0 LOAD_FAST 0 (a)
3 JUMP_IF_TRUE_OR_POP 12
6 LOAD_GLOBAL 0 (foo)
9 CALL_FUNCTION 0
>> 12 STORE_FAST 0 (a) 84 15 LOAD_FAST 0 (a)
18 RETURN_VALUE
f2 running time:
0.0288169384003
f1 running time:
0.0251071453094
f2 running time:
0.025267124176
f3:
2 0 LOAD_FAST 0 (a)
3 JUMP_IF_TRUE_OR_POP 12
6 LOAD_GLOBAL 0 (bar)
9 CALL_FUNCTION 0
>> 12 STORE_FAST 0 (a) 3 15 LOAD_FAST 0 (a)
18 RETURN_VALUE
f4:
93 0 LOAD_FAST 0 (a)
3 JUMP_IF_TRUE_OR_POP 12
6 LOAD_GLOBAL 0 (bar)
9 CALL_FUNCTION 0
>> 12 STORE_FAST 0 (a) 94 15 LOAD_FAST 0 (a)
18 RETURN_VALUE
Traceback (most recent call last):
File "./b/b.py", line 114, in <module>
f3()
File "<string>", line 2, in f3
File "./b/b.py", line 73, in bar
raise RuntimeError
RuntimeError Traceback (most recent call last):
File "./b/b.py", line 118, in <module>
f4()
File "./b/b.py", line 93, in f4
a = a or bar()
File "./b/b.py", line 73, in bar
raise RuntimeError
RuntimeError

可以看到性能完全一样了, 在调试信息也还不错

python 在调用时计算默认值的更多相关文章

  1. input 默认值为灰色,输入时清楚默认值

    input 默认值为灰色,输入时清楚默认值 <input value="please input your name" onFocus="if(value==def ...

  2. python定义函数时的默认返回值

    python定义函数时,一般都会有指定返回值,如果没有显式指定返回值,那么python就会默认返回值为None, 即隐式返回语句: return None 执行如下代码 def now(): prin ...

  3. python学习Day12 函数的默认值、三元表达式、函数对象(函数名)的应用场景、名称空间与作用域

    复习 1.字符串的比较: -- 按照从左往右比较每一个字符,通过字符对应的ascii进行比较 2. 函数的参数 : 1)实参与形参:       -- 形参:在函数定义时()中出现的参数       ...

  4. .NET DateTime类型变量作为参数时设置默认值

    一个小的 Tips. .NET 中函数参数的默认值需要是编译时常量.如果参数是引用类型,可以设置Null,如果是值类型,可以设置相应的编译时常量,如整型可以用整数,但对于DateTime(结构体,值类 ...

  5. 其他函数:值为NULL时的默认值NVL,DECODE

    NVL(列,默认数字值),此函数返回值为数值型,非NULL时返回原始值,NULL时返回默认数字值. DECODE:

  6. JavaScript 中对象解构时指定默认值

    待解构字段为原始值 正常情况下, const obj = { a: 1, b: 2, }; const { a, b } = obj; console.log(a, b); // 1 2 当被解构字段 ...

  7. ODOO的命令行调用以及config默认值

    通过odoo-bin 可以启动odoo server ,启动的过程中需要提供一些args,包括数据库设置,ip设置等 如果不想每次输入这些参数,可以直接修改odoo/tools/config.py中的 ...

  8. 001. 为input type=text 时设置默认值

    1. 前端HTML代码 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Defa ...

  9. select2 插件编辑时设置默认值

    function htDate(selectCustomerId, val) { var customerId = selectCustomerId; var values = val; ajaxJs ...

随机推荐

  1. 10个强大的Apache开源模块

    1.单点登录模块 LemonLDAP LemonLdap可以很棒地实现Apache的SSO功能,并且可以处理超过 20 万的用户请求.LemonLdap支持Java, PHP, .Net, Perl, ...

  2. 通过分析 JDK 源代码研究 Hash 存储机制

    通过 HashMap.HashSet 的源代码分析其 Hash 存储机制 实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元 ...

  3. SAP 使用SQL Trace(ST05)

    SAP 使用SQL Trace(ST05) SAP R/3 提供标准ABAP SQL 跟踪工具.使用T-Code:ST05 可以进入追踪设定画面:          在Trace Modes 区域中选 ...

  4. SNMP: Simple? Network Management Protocol(转)

    转自:http://www.rane.com/note161.html An SNMP Overview The Message Format The Actual Bytes Introductio ...

  5. Laravel Quickstart

    Installation Via Laravel Installer First, download the Laravel installer using Composer. composer gl ...

  6. 用lambda构建ORM查询语句

    本文介绍如何解析lambda表达式来获取一个满足条件的查询语句. 先看个截图  通过设置实体对象Article_Content的查询表达式,就可以获取对应的参数化SQL语句,使用起来很方便,减少了代码 ...

  7. Cocos2d-x MultipleTouch & CCControllButton's confusion

    在cocos2dx的程序设计中有时候会遇到需要多点触摸的功能,下面先介绍一下在cocos2dx中多点触摸的一般规则,然后介绍我遇到的一个有关多点触摸的情景的解决方案. (一)使用多点触摸规则: 关于多 ...

  8. salt-minion安装脚本

    #!/bin/bash cd /usr/local/src/ wget http://mirrors.sohu.com/fedora-epel/6/x86_64/epel-release-6-8.no ...

  9. [Javascript] Implement zip function

    1. Use a for loop to traverse the videos and bookmarks array at the same time. For each video and bo ...

  10. OOM-killer 线上设置 +vm +OOM机制

    http://blog.csdn.net/tenfyguo/article/details/9409743 http://blog.csdn.net/tenfyguo/article/details/ ...