1. 术语

程序中所存储的所有数据都是对象。每个对象都有一个身份、一个类型和一个值。对象的身份可以看作是指向它在内存中所处位置的指针,变量名就是引用这个具体位置的名称。
对象的类型也称作类别,用于描述对象的内部表示及它支持的方法与操作。创建特定类型的对象时,有时也将该对象称为该类型的实例。实例被创建之后,它的身份和类型就不可改变。如果对象的值是可以修改的,称为可变对象,反之称为不变对象。如果某个对象包含对其他对象的引用,则将其称为容器或集合。
大多数对象拥有大量特有的数据属性和方法。属性就是与对象相关的值。方法就是被调用时将在对象上执行某些操作的函数。使用点"."运算符可以访问属性和方法。
 

2. 对象的身份与类型

内置函数id()可返回一个对象的身份,返回值为整数。is运算符用于比较两个对象的身份。内置函数type()则返回一个对象的类型。例如:

def compare(a, b): if a is b: # 同一个对象 if a == b: # 具有相同的值 if type(a) is type(b): # 具有相同类型

对象的类型本身也是一个对象,称为对象的类。所有类型对象都有一个指定的名称,可用于执行类型检查,例如:

if type(s) is list: s.append(item)if type(d) is dict: d.update(t)

检查类型的更佳方式是用内置函数isinstance(object, type),例如:

if isinstance(s, list): s.append(item)if isinstance(d, dict): d.update(t)

因为isinstance()函数能够实现继承,因此是检查所有Python对象类型的首选方式。
 

3. 引用计数与垃圾收集

所有对象都有引用计数。无论是给对象分配一个新名称,还是将其放入一个容器,该对象的引用计数就会增加,例如:

a = 37 # 创建值为37的对象b = a # 增加37的引用计数c = []c.append(b) #增加37的引用计数

这个例子创建了一个包含值37的对象,a只是引用这个新创建对象的一个名称,将a赋值给b时,b就成了同一对象的新名称,而且该对象的引用计数会增加。类似地,将b放到一个列表中时,该对象的引用计数将再次增加。
使用del语句或者引用超出作用域时(或者被重新赋值),对象的引用计数就会减少,例如:

del a # 减少37的引用计数b = 42 #减少37的引用计数c[0] = 2.0 #减少37的引用计数

使用sys.getrefcount()函数可以获得对象的当前引用计数,例如:

a = 37import sysprint(sys.getrefcount(a))

多数情况下,引用计数比猜测的要大得多,对于不可变数据(如数字和字符串),解释器会主动在程序的不同部分共享对象,以便节约内存。
当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。在某些情况下,很多已不再使用的对象间可能存在循环依赖关系,例如:

a = {}b = {}a['b'] = bb['a'] = adel adel b

在以上例子中,del语句将会减少a和b的引用计数,并销毁用于引用底层对象的名称。然而因为每个对象都包含一个对其他对象的引用,所以引用计数不会归零,对象也不会被销毁,从而导致内存泄露。为了解决这个问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。
 

4. 引用与复制

在程序进行像a = b这样的赋值时,就会创建一个对b的引用。对于像数字和字符串这样的不可变对象,这种赋值实际上创建了b的一个副本。然而,对于可变对象(如列表和字典)引用行为会完全不同,例如:

a = [1, 2, 3, 4]b = aprint(b is a) # Trueb[2] = -100print(a[2]) #-100

因为a和b引用的同一个对象,修改其中任意一个变量都会影响到另一个。所以必须创建对象的副本而不是新的引用。对于像列表和字典这样的容器对象,可以使用两种复制操作: 浅复制和深复制。浅复制将创建一个新对象,但它包含的是对原始对象中包含的项的引用,例如:

a = [1, 2, [3, 4]]b = list(a)print(b is a) #Falseb.append(100)print(b) # [1, 2, [3, 4], 100]print(a) # [1, 2, [3, 4]]b[2][0] = -100print(b) # [1, 2, [-100, 4], 100]print(a) # [1, 2, [-100, 4]]

深复制将创建一个新对象,并且递归地复制它包含的所有对象。可以使用标准库中的copy.deepcopy()函数完成该工作,例如:

import copya = [1, 2, [3, 4]]b = copy.deepcopy(a)b[2][0] = -100print(b) # [1, 2, [-100, 4]]print(a) # [1, 2, [3, 4]]

 

5. 表示数据的内置类型

大约有12种数据类型可用于表示程序中用到的大多数数据。如下表所示:

类型分类类型名称描述NoneType(None)null对象None数字int整数数字float浮点数数字complex复数数字bool布尔值序列str字符串序列list列表序列tuple元组序列range创建的整数范围映射range创建的整数范围集合set可变集合集合frozenset不可变集合

None类型表示一个没有值的对象,在程序中表示为None。如果一个函数没显示返回值,则返回该对象。None常用于可选参数的默认值,以便让函数检测调用者是否为该参数实际传递了值。
Python使用4种数字类型:布尔型、整数、浮点数以及复数。除了布尔值,所有数字对象都是有符号的。所有数字类型都不可变。数字类型拥有大量的属性和方法,可以简化涉及混合算术的运算。为了与有理数兼容,整数使用了属性x.numerator和x.denominator。为了兼容复数,整数或浮点数y拥有属性y.real和y.imag,以及方法y.conjugate()。使用y.as_interger_ratio()可将浮点数y转换为分数形式的一对整数。方法y.is_interger()用于测试浮点数y是否表示整数值。通过方法y.hex()和y.fromhex()可用低级二进制形式使用浮点数。
序列表示索引为非负整数的有序对象集合,包括字符串、列表和元组。所有序列支持的方法如下表:

项目描述s[i]返回一个序列的元素is[i:j]返回一个切片s[i:j:stride]返回一个扩展切片lens(s)s中的元素数min(s)s中的最小值max(s)s中的最大值sum(s [, initial])s中各项的和all(s)检查s中的所有项是否为Trueany(s)检查s中的任意项是否为True

适用于可变序列的方法如下表:

项目描述s[i] = v项目赋值s[i:j] = t切片赋值s[i:j:stride] = t扩展切片赋值del s[i]项目删除del s[i:j]切片删除del s[i:j:stride]扩展切片删除

列表支持的方法如下表:

方法描述list(s)将s转换为一个列表s.append(x)将一个新元素x追加到s末尾s.extend(x)将一个新列表追加到s末尾s.count(x)计算s中x的出现次数s.index(x [, start [, stop]])找到x首次出现的位置s.insert(i, x)在索引i处插入xs.pop([i])返回元素i并从列表中移除它,省略i则返回列表中最后一个元素s.remove(x)搜索x并从s中移除它s.reverse()颠倒s中的所有元素的顺序s.sort([key [, reverse]])对s中的所有元素进行排序。key是一个键函数。reverse表明以倒序对列表进行排序

list(s)可将任意可迭代类型转换为列表。如果s已经是列表,则该函数构造的新列表是s的一个浅复制。
字符串支持的方法如下表:

方法描述s.captitalize()首字符变大写s.center(width [, pad])在长度为width的字段内将字符串居中。pad是填充字符s.count(sub [, start [, end]])计算指定子字符串sub的出现次数s.decode([encoding [, errors]])解码一个字符串并返回一个Unicode字符串s.encdoe([encoding [, errors]])返回字符串的编码版本s.endswith(suffix [, start [, end]])检查字符串是否以suffix结尾s.expandtabs([tabsize])使用空格替换制表符s.find(sub [, start [, end]])找到指定子字符串sub首次出现的位置,否则返回-1s.format(args, *kwargs)格式化ss.index(sub [, start [, end]])指到指定子字符串sub首次出现的位置,否则报错s.isalnum()检查所有字符是否都为字母或数字s.isalpha()检查所有字符是否都为字母s.isdigit()检查所有字符是否都为数字s.islower()检查所有字符是否都为小写s.isspace()检查所有字符是否都为空白s.istitle()检查字符串是否为标题字符串(每个单词首字母大写)s.isupper()检查所有字符是否都为大写s.join(t)使用s作为分隔符连接序列t中的字符串s.ljust(width [, fill])在长度为width的字符串内左对齐ss.lower()转换为小写形式s.lstrip([chrs])删掉chrs前面的空白或字符s.partition(sep)使用分隔符字符串sep划分一个字符串。返回一个元组(head, sep, tail)s.replace(old, new [, maxreplace])替换一个子字符串s.rfind(sub [, start [, end]])找到一个子字符串最后一次出现的位置s.rindex(sub [, start [, end]])找到一个子字符串最后一次出现的位置,否则报错s.rjust(width [, fill])在长度为width的字符串内右对齐ss.rpartition(sep)使用分隔符sep划分字符串,但是从字符串的结尾处开始搜索s.rsplit([sep [, maxsplit]])使用sep作为分隔符对一个字符串从后往前进行划分。maxsplit是最大划分次数s.rstrip([chrs])删掉chrs尾部的空白或字符s.split([sep [, maxsplit]])使用sep作为分隔符对一个字符串进行划分。maxsplit是划分的最大次数s.splitlines([keepends])将字符串分为一个行列表。如果keepends为1,则保留各行最后的换行符s.startswith(prefix [, start [, end]])检查一个字符串是否以prefix开头s.strip([chrs])删掉chrs开头和结尾的空白或字符s.swapcase()将大写转换为小写,或者相反s.title()将字符串转换为标题格式s.translate(table [, deletechars])使用一个字符转换表table转换字符串,删除deletechars中的字符s.upper()将一个字符串转换为大写形式s.zfill(width)在字符串的左边填充0,直至其宽度为width

很多字符串方法都接受可选的start和end参数,其值为整数,用于指定s中起始和结束位置的索引。大多数情况下,这些值可以为负值,表示索引是从字符串结尾处开始计算的。
映射类型表示一个任意对象的集合,而且可以通过另一个几乎是任意键值的集合进行索引。和序列不同,映射对象是无序的,可以通过数字、字符串和其他对象进行索引。映射是可变的。
字典是唯一内置的映射类型,任何不可变对象都可以用作字典键值,如字符串、数字、元组等。字典的方法如下表:

项目描述len(m)返回m中的项目数m[k]返回m中键k的项m[k] = x将m[k]的值设为xdel m[k]从m中删除m[k]k in m如果k是m中的键,则返回Truem.clear()删除m中的所有项目m.copy()返回m的一个副本m.fromkeys(s [, value])创建一个新字典并将序列s中的所有元素作为新字典的键,这些键的值均为valuem.get(k [, v])返回m[k],如果找不到m[k],则返回vm.items()返回由(key, value)对组成的一个序列m.keys()返回键值组成的一个序列m.pop(k [, default])如果找到m[k],则返回m[k]并从m中删除,否则返回default的值m.popitem()从m中删除一个随机的(key, value)对,并把它返回为一个元组m.setdefault(k [, v])如果找到m[k],则返回m[k],不则返回v,并将m[k]的值设为vm.update(b)将b中的所有对象添加到m中m.values()返回m中所有值的一个序列

集合是唯一的无序集。与序列不同,集合不提供索引或切片操作。它们和字典也有所区别,即对象不存在相关的键值。放入集合的项目必须是不可变的。集合分为两种类型,set是可变的集合,而frozenset是不可变的集合,这两类集合都是用一对内置函数创建的,例如:

s = set([1, 5, 10, 15])f = frozenset(['a', 37, 'hello'])

所有集合支持的方法如下表:

项目描述len(s)返回s中项目数s.copy()制作s的一份副本s.difference(t)求差集。返回所有要s中,但不在t中的项目s.intersection(t)求交集。返回所有同时在s和t中的项目s.isdisjoint(t)如果s和t没有相同项,则返回Trues.issubset(t)如果s是t的一个子集,则返回Trues.issuperset(t)如果s是t的一个超集,则返回Trues.symmetric_difference(t)求对称差集。返回所有在s或t中,但又不同时在这两个集合中的项s.union(t)求并集。返回所有在s或t中的项

可变集合还另外提供了一些方法,如下表:

项目描述s.add(item)将item添加到s中。如果item已经在s中,则无任何效果s.clear()删除s中的所有项s.difference_update(t)从s中删除同时也在t中的所有项s.discard(item)从s中删除item,如果item不要s的成员,则无任何效果s.intersection_update(t)计算s与t的交集,并将结果放入ss.pop()返回一个任意的集合元素,并将其从s中删除s.remove(item)从s中删除item,如果item不是s的成员,引发异常s.symmetric_difference_update(t)计算s与t的对称差集,并将结果放入ss.update(t)将t中的所有项添加到s中

所有的这些操作都可以直接修改集合s。
 

6. 表示程序结构的内置类型

在Python中,函数、类和模块都可以当做数据操作的对象,如下表:

类型分类类型名称描述可调用types.BuiltinFunctionType内置函数或方法可调用type内置类型和类的类型可调用object所有类型和类的祖先可调用types.FunctionType用户定义的函数可调用types.MethodType类方法模块types.ModuleType模块类object所有类型和类的祖先类型type内置类型和类的类型

可调用类型表示支持函数调用操作的对象。具有这种属性的对象有:用户定义的函数,方法、内置函数与方法,可调用的类与实例。
用户定义的函数是指用def语句或lambda运算符在模块级别上创建的可调用对象,它具有以下属性:

属性描述f.__doc__文档字符串f.__name__函数名称f.__dict__包含函数属性的字典f.__code__字节编译的代码f.__defaults__包含默认参数的元组f.__globals__定义全局命名空间的字典f.__closure__包含与嵌套作用域相关数据的元组

方法是在类定义中定义的函数。有3种常见的方法:实例方法、类方法和静态方法。实例方法是操作指定类的实例的方法,实例作为第一个参数传递给方法,根据约定该参数一般称为self。类方法把类本身当作一个对象进行操作,在第一个参数中将类对象传递给类。静态方法就是打包在类中的函数,它不能使用一个实例或类对象作为第一个参数。例如:

f = Foo()meth = f.instance_methodmeth(30)

在以上例子中,meth称为绑定方法。绑定方法是可调用对象,它封装了函数和一个相关实例。调用绑定方法时,实例就会作为第一个参数(self)传递给方法。方法查找也可以出现类本身上,例如:

umeth = Foo.instance_methodumeth(f, 30)

在以下例子中,umeth称为非绑定方法。非绑定方法是封装了方法函数的可调用对象,但需要传递一个正确类型的实例作为第一个参数。如果传递的对象类型错误,就会引发TypeError异常。
为方法对象定义的属性如下表:

属性描述m.__doc__文档字符串m.__name__方法名称m.__class__定义该方法的类m.__func__实现方法的函数对象m.__self__与方法相关的实例(如果是非绑定方法则为None)

类对象和实例也可以当作可调用对象进行操作。类对象使用class语句创建,并作为函数调用,以创建新实例。在这种情况下,将函数的参数传递给类的__init__()方法,以便初始化新创建的实例。如果实例定义了一个特殊方法__call__(),它就能够模拟函数的行为。如果该方法是为某个实例x而定义,使用x(args)语句等同于调用方法x.__call__(args)。
定义类时,类定义通常会生成一个type类型的对象,一个类型对象t的常用属性如下表:

属性描述t.__doc__文档字符串t.__name__类名称t.__bases__基类的元组t.__dict__保存类方法和变量的字典t.__module__定义类的模块名称t.__abstractmethods__抽象方法名称的集合

创建一个对象实例时,实例的类型就是定义它的类,例如:

f = Foo()print(type(f)) # <class '__main__.Foo'>

下表显示实例拥有的特殊属性:

属性描述t.__class__实例所属的类t.__dict__保存实例数据的字典

模块类型是一个容器,可保存使用import语句加载的对象。模块定义了一个使用字典实现的命名空间,比如,m.x=y等价于m.__dic__["x"]=y。模块的可用属性如下:

属性描述m.__dict__与模块相关的字典m.__doc__模块文档字符串m.__name__模块名称m.__file__用于加载模块的文件m.__path__完全限定包名,只在模块对象引用包时定义