温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

[Python] 类型与对象

发布时间:2020-07-23 07:49:08 来源:网络 阅读:652 作者:尔冬 栏目:编程语言

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__ 完全限定包名,只在模块对象引用包时定义
向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI