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——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)的更多相关文章

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

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

  2. 【转】Python函数默认参数陷阱

    [转]Python函数默认参数陷阱 阅读目录 可变对象与不可变对象 函数默认参数陷阱 默认参数原理 避免 修饰器方法 扩展 参考 请看如下一段程序: def extend_list(v, li=[]) ...

  3. Python面试题目之Python函数默认参数陷阱

    请看如下一段程序: def extend_list(v, li=[]): li.append(v) return li list1 = extend_list(10) list2 = extend_l ...

  4. 使用可变对象作为python函数默认参数引发的问题

    写python的都知道,python函数或者方法可以使用默认参数,比如 1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello ...

  5. python函数默认参数为可变对象的理解

    1.代码在执行的过程中,遇到函数定义,初始化函数生成存储函数名,默认参数初识值,函数地址的函数对象. 2.代码执行不在初始化函数,而是直接执行函数体. 代码实例 这要从函数的特性说起,在 Python ...

  6. python函数默认参数作用域

    当def函数参数默认值为对象时,例如列表[],字典{} 示例1:猜测一下,会输出什么??? def ddd(a,b=[]): b.append(a) return b print(ddd(1)) pr ...

  7. [python]函数默认参数顺序问题

    python 函数参数定义有四类: 1.必选参数:调用函数时候必须赋值的参数. a,须以正确的顺序传入函数b,调用时的数量必须和声明时的一样 def exa(x): return x #b作为参数进入 ...

  8. python之函数默认参数的坑

    坑 当你的默认参数如果是可变的数据类型,你要小心了 例题 # 正常没毛病的操作 def func(a,b=False): print(a) print(b) func(1,True) # 在实参角度, ...

  9. python函数默认参数陷阱

    对于学习python的人都有这样的困惑 def foo(a=[]): a.append(5) return a Python新手希望这个函数总是返回一个只包含一个元素的列表:[5].结果却非常不同,而 ...

随机推荐

  1. hdu 4004 最大值最小化

    http://acm.hdu.edu.cn/showproblem.php?pid=4004 一条线段长度为L,线段上有n个点,最多选取 m-1 个点,使得包括线段端点在内的相邻点之间的最大距离值最小 ...

  2. 菜鸟——springboot+mybatis+maven

    网上找了很多资料,学习如何搭建springboot,由于刚刚接触springboot,不是很熟练,通过参考网上别人搭建的例子,自己也搭建了一个简单的springboot+mybaits+maven 网 ...

  3. EGOCache缓存框架详细讲解

    EGOCache是一个轻量级的缓存框架.用法简单方便,在现在的项目中,我就用到EGOCache来缓存下载过的照片和字符串. 有人可能会问到,缓存照片还需要用EGOCache吗?AFNetworking ...

  4. 构造回文-C++实现

    腾讯2017暑期实习生招聘笔试题……做了一个世纪才做出来 //腾讯2017暑期实习生招聘第一道题.做了一个世纪才做出来………………太菜了 /** 题目: 给定一个字符串s,你可以从中删除一些字符,使得 ...

  5. MongoDB复制集成员及状态转换

    此文已由作者温正湖授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 复制集(Replica Set)是MongoDB核心组件,相比早期版本采用的主从(Master-Slave) ...

  6. 雨天的尾巴(bzoj3307)(线段树合并+树上差分)

    \(N\)个点,形成一个树状结构.有\(M\)次发放,每次选择两个点\(x,y\) 对于\(x\)到\(y\)的路径上(含\(x,y\))每个点发一袋\(Z\)类型的物品.完成 所有发放后,每个点存放 ...

  7. Android 美学设计基础 <3>

    本期接着对Android的美学设计的分享. 1.3 Light and shadows 光学与阴影 1.3.1 Light 在素材设计的环境中,我们会用虚拟的光来照亮UI界面.主灯光会产生尖锐,有方向 ...

  8. 微信Netting-QRLJacking分析利用-扫我二维码获取你的账号权限

    首先我们来看一下QRLJacking的实际原理:.攻击者首先进行客户端QR会话,并将登录QR码复制到网络钓鱼网站.“现在,一个精心制作的网络钓鱼页面有一个有效和定期更新的QR码可以被发送给受害者.” ...

  9. 2 rocketmq mqadmin 的用法详解

    参考文档 http://jameswxx.iteye.com/blog/2091971 1.1. 控制台使用 RocketMQ 提供有控制台及一系列控制台命令,用于管理员对主题,集群,broker 等 ...

  10. 解决ORA-21561: OID generation failed

    解决ORA-21561 在linux上使用sqlplus连接oracle数据库 [root@china ~]# sqlplus test/test@ORCL SQL Production :: Cop ...