[:PythonEssentialRef3:Python精要参考第三章]

第三章 类型和对象

Python 程序中的一切数据都是对象。对象包括自定义对象及基本的数据类型如数值、字符串、列表、字典等。你能够以类或扩展类型的方式创建自定义对象。本章主要描述 Python对象模型及第四章--运算符与表达式中要用到的一些预备知识。

术语

程序中的一切数据都是对象,每个对象都有三个基本属性,即标识(类似人的标识证号)、类型和值。

例如,当你写下 a = 42 这行代码,你就创建了一个值为 42 的整数对象。 type()id()函数用来查看对象的类型标识。id(a)可以查看该对象的标识(当前的实现是该对象在内存中的位置)。在这个例子中,a就是这个位置的引用。

一个对象的类别决定了可以对该对象进行何种操作(如,这个对象有长度吗?)。当一个特定类型的对象被创建时,这个对象被称为该类型的一个实例(注意:不要将类型的的实例和用户自定义类的实例混淆)。在一个对象被创建之后,它的标识和类型就再不能被改变。某些对象的值是可变的,这些对象就被称为可变对象(mutable);另一些对象的值是不可变的,那就被称为不变对象(immutable)。某类对象可以包含其它对象的引用,我们称这类对象为容器。

注1:关于类型的不可改变
从python2.2开始,Python开发小组开始有步骤的合并某些类别和类,因此书中的某些结论可能不是百分之百精确和完整。在某些特定条件下,有可能允许改变一个对象的类型。但是,在本手册扩展修订之前,我们就应该一如既往的认为这些经典类型是不可改变的。考虑到兼容性,python2.2和2.3也是这样默认处理的。

注2:不变对象的不可变并不是绝对的,当一个不变容器对象包含一个可变对象的引用时,可变对象的值变化会引起该不变容器对象的值发生变化。这种情况下,我们仍然认为该容器对象为不变对象,因为该容器所包含并不是引用对象的值,而仅仅是该对象的引用,这里的引用可以理解为该对象的内存地址。不管被包含对象的值如何变化,被包含对象的引用确实是始终不变的)。一个对象是否可变取决于它的类型,举例来说,数字、字符串、tuple类型是不可变类型,字典与列表是可变类型。

--WeiZhong

除了保存值以外,许多对象还拥有一系列的属性(attribute)。广义的属性是指对象的相关数据或者该对象能够具有的行为(如狗对象拥有颜色体重等相关数据,还拥有叫、吃、跑等行为,这些都是对象的广义的属性),狭义的属性只包含对象的相关数据,对于对象的行为,更常用的叫法是方法(method)。方法是对象可调用的属性,一个对象有多少个方法(method),它就具有多少种行为。要访问一个对象的属性或者调用一个对象方法,使用点(.)操作符:

a = 3 + 4j              # 创建一个复数
r = a.real              # 取得一个复数的实部,访问该对象的一个属性

b = [1, 2, 3]           # 创建一个列表)
b.append(7)             # 使用 append 方法为列表加入新的元素

对象的标识与类型

内建函数id()返回一个对象的标识。该返回值是一个整数,目前的实现该整数通常就是对象在内存中的位置。is 运算符用来比较两个对象的标识。内建函数type()返回一个对象的类型:

   1 # 比较两个对象
   2 def compare(a,b):
   3     print 'The identity of a is ', id(a)
   4     print 'The identity of b is ', id(b)
   5     if a is b:
   6         print 'a and b are the same object'
   7     if a == b:
   8         print 'a and b have the same value'
   9     if type(a) is type(b):
  10         print 'a and b have the same type'

对象的类型也是对象,这个对象具有唯一性。对同一类型的所有实例应用type()函数总是会返回同一个类型对象。因此,类型之间可以使用 is 运算符来进行比较。标准模块 types 内包含所有内建类型对象,我们可以通过它来完成类型检查工作:

   1 import types
   2 if type(s) is types.ListType:
   3     print 'Is a list'
   4 else:
   5     print 'Is not a list'

若要比较两个自定义类实例对象的类型,最好是使用isinstance()函数。 函数 isinstance(s,C)用于测试 s 是否是 C 或 C 的子类的实例。详细内容请参阅第七章--类和面向对象的编程。

引用计数与垃圾收集

一切对象都是引用计数的。当分配一个新的名字给一个对象,或者其将放入到一个容器比如列表、元组、或者字典中,该对象的引用计数就会增加1次。如:

   1 a = 3.4      # 创建一个对象 '3.4',引用计数为 1
   2 b = a        # 对象 '3.4' 引用计数增加 1,此时对象 '3.4' 的引用计数为 2
   3 c = []
   4 c.append(b)  # 对象 '3.4' 引用计数增加 1,此时对象 '3.4' 的引用计数为 3

例子中创建了一个包含值3.4的一个对象。变量 a 是一个指向该对象的名字。当用 a 来为 b 赋值时,b 成为同一个对象新的名称,此时对象的引用计数就会增1。同样地, 当你把 b 放入一个列表中时,对象的引用计数再次增1。在例子中,自始至终只有一个值为 3.4 的整数对象,b 与 c[0] 都仅仅是该对象的引用。

del语句、脱离变量作用域或者变量被重新定义,都会使对象的引用计数减少。

   1 del a           # 直接删除一个引用,对象 3.4 引用减1
   2 b = 7.8         # 某个引用被赋新值,对象 3.4 引用减1
   3 c[0]=2.0        # 某个引用被赋新值,对象 3.4 引用减1

当一个对象的引用计数减少至零时,它就会在适当时机被垃圾回收车拉走。然而,特定情况(循环引用)会阻止垃圾回收车销毁不再使用的对象,看下面的例子:

   1 a = { }         # a 的引用为 1
   2 b = { }         # b 的引用为 1
   3 a['b'] = b              # b 的引用增 1,b的引用为2
   4 b['a'] = a              # a 的引用增 1,a的引用为 2
   5 del a           # a 的引用减 1,a的引用为 1
   6 del b           # b 的引用减 1,  b的引用为 1

在这个例子中,del语句减少了 a 和 b 的引用计数并删除了用于引用的变量名,可是由于两个对象各包含一个对方对象的引用,虽然最后两个对象都无法通过名字访问了,但引用计数并没有减少到零。因此这个对象不会被销毁,它会一直驻留在内存中,这就造成了内存泄漏。为解决这个问题,Python解释器会定期的运行一个搜索器,若发现一个对象已经无法被访问,不论该对象引用计数是否为 0 ,都销毁它。这个搜索器的算法可以通过 gc 模块的函数来进行调整和控制。具体内容参阅附录A:Python 库。

引用与副本

当运行语句 a = b 时,就创建了对象 b 的一个新引用a。对于不可变对象(数字或字符串等),改变对象的一个引用就会创建一个新对象。

   1 a=100                   #创建一个新对象 100
   2 b=a                     #对象 100 增加了一个新的引用 b
   3 print id(a),id(b)               #打印 a 和 b 的标识,你会发现两个标识是相同的
   4 b=20                    #现在 b 不再是 a 的引用,变成新对象 20 的一个引用了
   5 print id(a),id(b)               #现在 a 和 b 的标识不再相同

对于可变对象(列表或字典等),改变对象的一个引用就等于改变了该对象所有的引用,见下例:

   1 b = [1,2,3,4]
   2 a = b                   # a 是 b 的一个引用
   3 a[2] = -100             # 改变 a 中的一个元素
   4 print b                 # b的值也随之改变为 '[1, 2, -100, 4]'

因为 a 和 b 指向相同的对象,所以改变了 a 就等于改变了 b 。为了避免这种情况,你应该创建一个可变对象的副本,然后对该副本进行操作。这样就不会影响到原始对象了。

有两种方法用来创建可变对象的副本:浅复制(shallow copy)和深复制(deep copy)。浅复制创建一个新对象,但它包含的子元素仍然是原来对象子元素的引用:

   1 b = [ 1, 2, [3,4] ]
   2 a = b[:]                # 创建b的一个 浅拷贝 a
   3 a.append(100)           # a 对象添加一个新元素
   4 print b                 # 打印 b 的值,得到 '[1,2, [3,4]]', b 没有改变
   5 a[0]=-100               # 改变 a 的一个不可变子对象
   6 print b                 # 打印 b 的值,得到 '[1,2, [3,4]]', b 没有改变
   7 a[2][0] = -100          # 改变 a 的一个可变子对象
   8 print b                 # 打印 b 得到 '[1,2, [-100,4]]',b 被改变了

a 和 b 虽然是彼此独立的对象,但他们包含的元素却是共享的。这样,修改 a 中的一个可变元素也会影响 b 中的这个可变元素。

深复制创建一个新对象,并递归复制所有子对象。python并没有内建的深复制函数,不过在标准库中提供有一个copy模块,该模块有一个deepcopy()函数可以漂亮的干这件事:

   1 import copy
   2 b = [1, 2, [3, 4] ]
   3 a = copy.deepcopy(b)