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. [转自知乎] 从github上下载单个文件夹

    原文地址: 如何从 GitHub 上下载单个文件夹?  注意:如果是在公司网络环境的话需要配置可以访问外网的代理,否则 svn checkout 时会出错.

  2. 基于Maven的S2SH(Struts2+Spring+Hibernate)框架搭建

    1. 前言 基于Maven的开发方式开发项目已经成为主流.Maven能很好的对项目的层次及依赖关系进行管理.方便的解决大型项目中复杂的依赖关系.S2SH(Struts2+Spring+Hibernat ...

  3. Oracle/PLSQL: BitAnd Function

    BITAND 函数 本文介绍 Microsoft Excel 中 BITAND 函数的公式语法和用法. 说明 返回两个数的按位“与”. 语法 BITAND( number1, number2) BIT ...

  4. Nodejs-- web服务器

    第一篇关于nodejs的东西,代码在此作为备份. 该代码目前未完成,是一个阻塞式的代码. 1.index.js ar server=require('./server'); var route=req ...

  5. CUDA开发 - CUDA 版本

    "CUDA runtime is insufficient with CUDA driver"CUDA 9.2: 396.xx CUDA 9.1: 387.xx CUDA 9.0: ...

  6. ASP.NET Core 2 学习笔记(四)依赖注入

    ASP.NET Core使用了大量的依赖注入(Dependency Injection, DI),把控制反转(Inversion Of Control, IoC)运用的相当巧妙.DI可算是ASP.NE ...

  7. OpenStack 虚机网卡的创建过程

    原文链接:https://www.cnblogs.com/potato-chip/p/9127083.html OpenStack虚机网卡的创建过程 OpenStack最基本和常用的操作就是启动虚机. ...

  8. 纯文本-FileOutputStream的解码方式

    1.通过string.getBytes(charsetNane)获得的字节数组,字节数组的编码方式,决定了FileOutStream写出文件的格式 例1:字节数组采用“GBK”编码,write生成的文 ...

  9. XCode10.0遇到的问题

    1:编译时报info.plist冲突. 解决方法,XCode上 File -> Workspace Settings ... 将Build System改为 Legacy Build Syste ...

  10. Binary Search Tree-530. Minimum Absolute Difference in BST

    Given a binary search tree with non-negative values, find the minimum absolute difference between va ...