## 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 = 37 import sys print(sys.getrefcount(a)) ``` 多数情况下,引用计数比猜测的要大得多,对于不可变数据(如数字和字符串),解释器会主动在程序的不同部分共享对象,以便节约内存。 当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。在某些情况下,很多已不再使用的对象间可能存在循环依赖关系,例如: ``` a = {} b = {} a['b'] = b b['a'] = a del a del b ``` 在以上例子中,del语句将会减少a和b的引用计数,并销毁用于引用底层对象的名称。然而因为每个对象都包含一个对其他对象的引用,所以引用计数不会归零,对象也不会被销毁,从而导致内存泄露。为了解决这个问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。   ## 4. 引用与复制 在程序进行像a = b这样的赋值时,就会创建一个对b的引用。对于像数字和字符串这样的不可变对象,这种赋值实际上创建了b的一个副本。然而,对于可变对象(如列表和字典)引用行为会完全不同,例如: ``` a = [1, 2, 3, 4] b = a print(b is a) # True b[2] = -100 print(a[2]) #-100 ``` 因为a和b引用的同一个对象,修改其中任意一个变量都会影响到另一个。所以必须创建对象的副本而不是新的引用。对于像列表和字典这样的容器对象,可以使用两种复制操作: 浅复制和深复制。浅复制将创建一个新对象,但它包含的是对原始对象中包含的项的引用,例如: ``` a = [1, 2, [3, 4]] b = list(a) print(b is a) #False b.append(100) print(b) # [1, 2, [3, 4], 100] print(a) # [1, 2, [3, 4]] b[2][0] = -100 print(b) # [1, 2, [-100, 4], 100] print(a) # [1, 2, [-100, 4]] ``` 深复制将创建一个新对象,并且递归地复制它包含的所有对象。可以使用标准库中的copy.deepcopy()函数完成该工作,例如: ``` import copy a = [1, 2, [3, 4]] b = copy.deepcopy(a) b[2][0] = -100 print(b) # [1, 2, [-100, 4]] print(a) # [1, 2, [3, 4]] ```   ## 5. 表示数据的内置类型 大约有12种数据类型可用于表示程序中用到的大多数数据。如下表所示: | 类型分类 | 类型名称 | 描述 | | -------- | -------- | -------- | | None | Type(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] | 返回一个序列的元素i | | s[i:j] | 返回一个切片 | | s[i:j:stride] | 返回一个扩展切片 | | lens(s) | s中的元素数 | | min(s) | s中的最小值 | | max(s) | s中的最大值 | | sum(s [, initial]) | s中各项的和 | | all(s) | 检查s中的所有项是否为True | | any(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处插入x | | s.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首次出现的位置,否则返回-1 | | s.format(*args, \**kwargs) | 格式化s | | s.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的字符串内左对齐s | | s.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的字符串内右对齐s | | s.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]的值设为x | | del m[k] | 从m中删除m[k] | | k in m | 如果k是m中的键,则返回True | | m.clear() | 删除m中的所有项目 | | m.copy() | 返回m的一个副本 | | m.fromkeys(s [, value]) | 创建一个新字典并将序列s中的所有元素作为新字典的键,这些键的值均为value | | m.get(k [, v]) | 返回m[k],如果找不到m[k],则返回v | | m.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]的值设为v | | m.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没有相同项,则返回True | | s.issubset(t) | 如果s是t的一个子集,则返回True | | s.issuperset(t) | 如果s是t的一个超集,则返回True | | s.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的交集,并将结果放入s | | s.pop() | 返回一个任意的集合元素,并将其从s中删除 | | s.remove(item) | 从s中删除item,如果item不是s的成员,引发异常 | | s.symmetric_difference_update(t) | 计算s与t的对称差集,并将结果放入s | | s.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_method meth(30) ``` 在以上例子中,meth称为绑定方法。绑定方法是可调用对象,它封装了函数和一个相关实例。调用绑定方法时,实例就会作为第一个参数(self)传递给方法。方法查找也可以出现类本身上,例如: ``` umeth = Foo.instance_method umeth(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)) # ``` 下表显示实例拥有的特殊属性: | 属性 | 描述 | | -------- | -------- | | t.\_\_class\_\_ | 实例所属的类 | | t.\_\_dict\_\_ | 保存实例数据的字典 | 模块类型是一个容器,可保存使用import语句加载的对象。模块定义了一个使用字典实现的命名空间,比如,m.x=y等价于m.\_\_dic\_\_["x"]=y。模块的可用属性如下: | 属性 | 描述 | | -------- | -------- | | m.\_\_dict\_\_ | 与模块相关的字典 | | m.\_\_doc\_\_ | 模块文档字符串 | | m.\_\_name\_\_ | 模块名称 | | m.\_\_file\_\_ | 用于加载模块的文件 | | m.\_\_path\_\_ | 完全限定包名,只在模块对象引用包时定义 |