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)) # <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__ | 完全限定包名,只在模块对象引用包时定义 |