[Python]类型与对象

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种数据类型可用于表示程序中用到的大多数数据。如下表所示:

类型分类类型名称描述
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]返回一个序列的元素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__完全限定包名,只在模块对象引用包时定义
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值