Python——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)
Python 的内建标准类型有一种分类标准是分为可变类型与不可变类型:
- 可变类型:列表、字典
- 不可变类型:数字、字符串、元组
因为变量保存的实际都是对象的引用,所以在给一个不可变类型(比如 int)的变量 a 赋新值的时候,你实际上是在内存中新建了一个对象,并将 a 指向这个新的对象,然后将原对象的引用计数 –1.
比如下面的示例:
>>> id(1),id(2)
(507098784, 507098816)
>>> a = 1
>>> id(a)
507098784
>>> a = 2
>>> id(2)
507098816
这里的第一步显示 1 和 2 的 id 就已经在内存中保存了这两个对象,随后给 a 赋不同的值,也只是在改变它的引用而已。
但列表就不同:
>>> b = [0]
>>> id(b)
171805000
>>> b[0] = 1
>>> id(b)
171805000
看起来虽然 b 的 id 没有变,其值却变化了。真的是这样吗?
>>> b = [0]
>>> id(b[0])
507098752
>>> b[0] = 1
>>> id(b[0])
507098784
其实只是一个嵌套引用啦,容器类型管理着对复数个元素的引用,这些元素可以是可变类型也可以是不可变类型,对于不可变类型的元素,当你改变他的值的时候,所发生的事情和最前面举得例子是一样的。另外注意不要简单将容器类型与可变类型划等号,因为还有一个“元组”特例。
变量类型是否可变有一个很重要的应用之处就是作为定义函数的默认参数的时候,形如 def foo(attrs=(1,2)): return 之类。这里设定容器类型的默认参数 attrs 使用了元组而不用列表的原因在于:列表作为一种可变类型非常的不靠谱。当脚本执行到函数定义之处的时候,解释器会对参数表达式做一次“预演算”,并把值保存到内存之中,之后每次调用这个函数的时候,都不会再重新运算其参数表达式,而是直接从“预演算”的结果处取值(引用)。所以如果你的默认参数写了一个列表进去,那么每次你调用这个函数时对这个列表所做的更改都会被保存下来。就像这样:
>>> def biggest(n, store=[0]):
store[0] = max(n, store[0])
return store[0]
>>> biggest(3)
3
>>> biggest(9)
9
>>> biggest(5)
9
这是一个返回历史最大值(仅限正数,因为我不知道该怎么表示极小值)的函数,这个函数没有使用全局变量,却可以在重复调用的时候记录下曾输入过的最大值,靠的就是这个 store=[0] 的默认参数。这个参数的值 [0] 在函数定义的时候就被写入内存之中了,之后每次调用都会引用那个地址,所以是可以当一个全局变量一样使用,却比直接定义的全局变量安全。
其实我想说的是,一般情况下不要用列表做参数,而是用元组。当然除非你知道自己在干什么,否则这只会带来麻烦。上面举了一个正面的例子是因为我看到这个话题的地方就是拿这个特性来做一个 trick 在用。他实现的是一个简单的 timer,每一次调用这个计时函数,都会打印出自上一次调用起到本次调用所经过的时间:
import time
def dur( op=None, clock=[time.time()] ):
if op != None:
duration = time.time() - clock[0]
print '%s finished. Duration %.6f seconds.' % (op, duration)
clock[0] = time.time()
if __name__ == '__main__':
import array
dur() # Initialise the timing clock
opt1 = array.array('H')
for i in range(1000):
for n in range(1000):
opt1.append(n)
dur('Array from append')
opt2 = array.array('H')
seq = range(1000)
for i in range(1000):
opt2.extend(seq)
dur('Array from list extend')
opt3 = array.array('H')
seq = array.array('H', range(1000))
for i in range(1000):
opt3.extend(seq)
dur('Array from array extend')
上例来自于 http://code.activestate.com/recipes/578776-a-simple-timing-function/,并且是 Python2 版本
这个函数里除了用于计时的 clock=[time.time()] 参数外,还有一个用于打印的表示当前 _main_ 函数进度的 op 字符串参数。他的函数里设计了一个逻辑:如果调用时没有给出 op 参数,那么就认为这次调用是在初始化,就像 _main_ 函数里第二行那样。其实我觉得这样不太好,因为完全可以改成:
import time
def dur(op='Undefined',clock=[time.time()]):
duration = time.time() - clock[0]
print('%s finished\t Duration:%.6f seconds.'%(op,duration))
clock[0] = time.time()
if __name__ == '__main__':
import array
dur('Initialise') # Initialise the timing clock
这样直接通过 op='Initialise' 来显式初始化计时器更直观一些,而且可以避免在已经初始化后因为忘记给出 op 参数而造成计时错误的可能,虽然可能性很小。
Python——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)的更多相关文章
- Python进阶-函数默认参数
Python进阶-函数默认参数 写在前面 如非特别说明,下文均基于Python3 一.默认参数 python为了简化函数的调用,提供了默认参数机制: def pow(x, n = 2): r = 1 ...
- 【转】Python函数默认参数陷阱
[转]Python函数默认参数陷阱 阅读目录 可变对象与不可变对象 函数默认参数陷阱 默认参数原理 避免 修饰器方法 扩展 参考 请看如下一段程序: def extend_list(v, li=[]) ...
- Python面试题目之Python函数默认参数陷阱
请看如下一段程序: def extend_list(v, li=[]): li.append(v) return li list1 = extend_list(10) list2 = extend_l ...
- 使用可变对象作为python函数默认参数引发的问题
写python的都知道,python函数或者方法可以使用默认参数,比如 1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello ...
- python函数默认参数为可变对象的理解
1.代码在执行的过程中,遇到函数定义,初始化函数生成存储函数名,默认参数初识值,函数地址的函数对象. 2.代码执行不在初始化函数,而是直接执行函数体. 代码实例 这要从函数的特性说起,在 Python ...
- python函数默认参数作用域
当def函数参数默认值为对象时,例如列表[],字典{} 示例1:猜测一下,会输出什么??? def ddd(a,b=[]): b.append(a) return b print(ddd(1)) pr ...
- [python]函数默认参数顺序问题
python 函数参数定义有四类: 1.必选参数:调用函数时候必须赋值的参数. a,须以正确的顺序传入函数b,调用时的数量必须和声明时的一样 def exa(x): return x #b作为参数进入 ...
- python之函数默认参数的坑
坑 当你的默认参数如果是可变的数据类型,你要小心了 例题 # 正常没毛病的操作 def func(a,b=False): print(a) print(b) func(1,True) # 在实参角度, ...
- python函数默认参数陷阱
对于学习python的人都有这样的困惑 def foo(a=[]): a.append(5) return a Python新手希望这个函数总是返回一个只包含一个元素的列表:[5].结果却非常不同,而 ...
随机推荐
- MySQL 分表和分区
1.为什么需要分表和分区 在开发的过程中,经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表.这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,如果涉及联合查询的情况,性能更加 ...
- Codeforces801C Voltage Keepsake 2017-04-19 00:26 109人阅读 评论(0) 收藏
C. Voltage Keepsake time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- linux下怎样用c语言调用shell命令
C程序调用shell脚本共同拥有三种法子 :system().popen().exec系列数call_exec1.c , system() 不用你自己去产生进程.它已经封装了,直接增加自己的命令 ex ...
- Should Assertion Library
Should Assertion Library,通常在测试时用到,可以与nunit 结合使用. 已经从codeplex 迁移到 github.网址如下 https://github.com/eric ...
- [javascript library]使用js实现页面刷新后依然保留表单填写的数据
详情请见于该链接:http://sisyphus-js.herokuapp.com/
- [翻译]Writing Custom Wizards 编写自定义的向导
Writing Custom Wizards 编写自定义的向导 You can extend FastReport's functionality with the help of custom ...
- JQuery 的一个轻量级 Guid 字符串拓展插件.
(function ($) { function guid(g) { var arr = new Array(); //存放32位数值的数组 if (typeof (g) == "strin ...
- python——回文函数(reversed)
回文数:正向排列与反向排列所得结果是相等的(即从左到右和从右到左的结果是相等的),例如:“123321”,“0000”等. reversed函数:反转一个序列对象,将其元素从后向前颠倒构建成一个新的迭 ...
- [ASP.NET]关于DOT NET的IIS配置LocalHost访问和127.0.0.1访问的区别
项目上遇到一个问题跟大家分享下,配置的localhost地址本地无法访问接口,外网却可以访问,查其原因百度资料比较全面的解释 localhost与127.0.0.1的概念和工作原理之不同 要比较两个东 ...
- 理解go语言 协程之间的通讯
go已经越来越被重视了,特别适合大型互联网公司基础服务的编写,高效,高并发,可以同时允许多个明星出轨,多个明星结婚 都不在话下,下面介绍下GO协程通讯的过程 直接上代码 package main im ...