惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
The GitHub Blog
The GitHub Blog
C
Check Point Blog
博客园_首页
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
F
Full Disclosure
Microsoft Security Blog
Microsoft Security Blog
爱范儿
爱范儿
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
G
GRAHAM CLULEY
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threat Research - Cisco Blogs
C
Cybersecurity and Infrastructure Security Agency CISA
V
Vulnerabilities – Threatpost
K
Kaspersky official blog
博客园 - 司徒正美
S
Schneier on Security
T
The Exploit Database - CXSecurity.com
Project Zero
Project Zero
云风的 BLOG
云风的 BLOG
Cisco Talos Blog
Cisco Talos Blog
Know Your Adversary
Know Your Adversary
雷峰网
雷峰网
V
V2EX - 技术
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Spread Privacy
Spread Privacy
罗磊的独立博客
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security Affairs
SecWiki News
SecWiki News
Schneier on Security
Schneier on Security
O
OpenAI News
Jina AI
Jina AI
PCI Perspectives
PCI Perspectives
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog RSS Feed
I
InfoQ
D
Docker
P
Palo Alto Networks Blog
Recorded Future
Recorded Future
M
MIT News - Artificial intelligence
博客园 - Franky
B
Blog
Scott Helme
Scott Helme
博客园 - 叶小钗
D
DataBreaches.Net

博客园 - J.D Huang

TinyDO:可能是WP首个支持中文语音识别的待办事项APP Internet TV 影音娱乐新生活 Windows Phone Developer Tools CTP 发布了! Python小技巧 – True or False - J.D Huang Python小技巧 - 子串查找 - J.D Huang 新的个人博客@ http://thinkbot.info MeeGo:下一个Android? Windows Mobile 6.5.3 Developer Tool Kit 发布了 塞班(Symbian)开源了(包括Symbian 3和S60等) Windows Mobile 6.5 SDK 发布了 (2月17日更新) C#4.0新特性之(四)新的LINQ扩展方法-Zip() C#4.0新特性之(三)协变与逆变 C#4.0新特性之(二)命名参数,可选参数与COM互操作 C#4.0新特性之(一)动态查找 - J.D Huang Android手机防盗工具DroidGuard Simple HostMonitor 实用的网管小工具 Office Mobile 2010 Beta 发布了! - J.D Huang 试了一下.Net Fx 4.0中的Parallel - J.D Huang Lambda演算与科里化(Currying)
Python - 默认参数的一次性求值
J.D Huang · 2010-03-07 · via 博客园 - J.D Huang

    和很多高级编程语言一样,Python也有默认参数,当默认参数是数值类型时,一切都很美好:

>>> def function(a, b = 1000000):
	b +=a
	return b
   如果你喜欢,你可以在一段代码中无数次的调用这个函数,只要你参数一样,结果应该都一样。比如:

function(1)总是会返回1000001。但是默认参数是其他类型(如列表)时就没那么美好了:

>>> def function(a, b = []):
	b.append(a)
	print(b)


     这时你如果在一段代码中持续的调用该函数,将会发生或许令人意外的情况:第一次调用function(1)的时候,很正常,会打印出[1],但是第二次再调用function(1),将会打印出[1,1]。这是为什么呢?不要紧,使用Python我们有办法检查一下是哪里出了毛病。这里我们可以在每一次调用函数的时候打印出b的ID。Python中一个对象的ID在其生命周期中是唯一的,和其他高级语言中所说的对象的地址一样。如果第二段代码中的b对象其ID一样,说明两次调用都使用的同一个对象,换句话说,Python函数对默认参数的求值操作在其生命周期中只发生一次(第一次)。可以使用以下的代码测试我们的想法:

def function1(a,b=100000):
    b+=a
    print("b = {0} with the id of {1}".format(b,id(b)))
def function2(a,b=[]):
    b.append(a)
    print("b = {0} with the id of {1}".format(b,id(b)))
    
def test():
    function1(1)
    function1(1)
    function2(1)
    function2(1)
    
if __name__ == '__main__':
    test()

    得到的输出如下:

b = 100001 with the id of 33384304
b = 100001 with the id of 33384304
b = [1] with the id of 33341848
b = [1, 1] with the id of 33341848

      果然,从后面两条结果中可以看到列表b在两次调用时都是使用的同一个对象,看来之前的猜想是正确的。对非数值类型的默认参数,只会在第一次调用时进行求值(取地址)操作。后面的所有调用都发生在同一个位置的对象上。只有字符串类型不受此限制,因为string本身是不可变的(immutable)的,每一次修改它都会创建一个新的对象。

      Python的这个小陷阱和它的灵活性是分不开的,在其他的强类型语言如C#中,类似Python的情况是不会发生的,C#4.0严格将引用类型的默认参数值限定为Null(除了String类型),否则会在编译时报错:

cs4namedarg     那么在Python中有办法使得每一次函数调用时都会使用最初设定的默认值么?办法有两种(有其他的办法欢迎在留言中告诉我),要么把默认值设为一个不可变(immutable)的值,比如string或者None,要么就每次调用的时候保留最初的默认值,并赋给调用函数。

     第一种方法很简单,在此不再赘述,不过需要注意以字符串为默认值时,如果频繁的调用函数可能会导致性能问题,因为每一次发生在该默认值上的操作,会创建一个新的string对象。对于第二种办法,可以考虑用Python的装饰器(decorator)实现,下面的代码演示了一个每一次调用都保存默认参数的装饰器:

def keepDefault(f):
    defArgs = f.__defaults__
    def keeper(*args,**kwArgs):
        f.__defaults__ = deepcopy(defArgs)
        return f(*args,**kwArgs)
    keeper.__name__ = f.__name__
    return keeper

然后我们将该装饰器应用到之前定义的function2中:

@keepDefault
def function2(a,b=[]):
    b.append(a)
    print("b = {0} with the id of {1}".format(b,id(b)))

然后我们像先前一样连续的调用function2,结果输出如下:

b = [1] with the id of 33892912
b = [1] with the id of 33892592

哈~ 我们如愿得到了结果。而且注意这里两次b对象的的ID不一样,这是因为每一次调用时,函数的参数都被deepcopy完整的克隆一遍。重新构造了新对象b。

Enjoy python!   ;-)