类型与对象
转载请标明出处(http://blog.csdn.net/lis_12/article/details/52693637)
1 术语
程序中所有数据都是对象(实例),每个对象都有一个身份,一个类型和一个值.如a = 10,使用值10创建一个整数对象,对象的身份可以看做它在内存中所处位置的指针(id(a)获取),a就是引用这个位置的名称.
实例的类型决定了实例内部的表示以及它支持的方法.
实例被创建后,它的身份和类型就不可改变,如果对象的值是可以修改的称为可变对象(list,dict);如果对象的值不可修改称为不可变对象(int,str,tuple).
2 身份与类型
a = 10,b = 10
身份的获取,id(a);
类型的获取,type(a)
is用来比较地址是否相同,即两个对象身份是否相同,==用来比较两个对象的值是否相同;
type(a) is type(b) # true
对象的类型本身也是一个对象,称为对象的类,该对象的定义是唯一的,对于某个类型的所有实例都是相同的.
检查某个实例的类型最佳方式是用,isinstance(object,(int,str,list…..))
3 引用计数与垃圾收集
所有对象都有引用计数,无论是给一个对象分配一个新名称,还是放入容器当中,都会让该对象的引用计数增加.
a = 10 #创建一个值为10的对象
b = a #增加10的引用计数
c = []
c.append[a] #增加10的引用计数
#上面只有一个包含10的对象,其他操作都只是创建了该对象的引用.
del a #减少10的引用计数,此时a也就无法访问了
b = 10 #减少10的引用计数
c[0] = 5 #减少10的引用计数
'''
使用del或者重新赋值时,对象的引用计数会减少
'''
print sys.getrefcount(b) #查看b的引用次数,一般情况下会比预想情况多- -
a = {}
b = {}
a['a'] = b
b['b'] = a
del a
del b
'''
虽然del语句销毁了a与b,但是他们相互引用,所以引用计数不会归零,对象也不会销毁(从而导致内存泄漏),为了解决这个问题,解释器会有一个循环检测器,搜索不可访问对象并删除.
'''
4 引用和复制
a = 1
b = 1
print id(a) #0
print id(b) #0
#a与b的地址相同,因为1是不可变对象,不可改变,a和b都是对1的引用.
a = range(10)
b = range(10)
print id(a) #0
print id(b) #1
#a与b的地址不同,因为list是可变对象,可变对象是可修改的
for i,j in zip(a,b):
print id(i) == id(j)
#result:10个True,因为list里面的对象都是不可变对象,a和b里面的元素分别引用了0,1,...,8,9
#可变对象
a = range(4)
b = a
b[1] = 100
print a #[0,100,2,4]
#不可变对象
c = 1
d = c
d = 2
print c #1
a,b引用的是同一个对象,修改其中任何一个都会影响另一个,为了避免这种情况,必须创建对象的副本而不是新的引用.
可变对象的复制分为浅,深复制.
- 浅复制将创建一个新对象,但它包含的是对原始对象中包含的项的引用.
a = [0,[1,2]]
b = a #此时b就相当于a的别名
c = list(a)#创建a的一个浅复制
a is b #True
a is c #False
b.append(2)
c.append(3)
print a #[0, [1, 2], 2]
print b #[0, [1, 2], 2]
print c #[0, [1, 2], 3]
c[1][0] = 10
print a #[0, [10, 2], 2]
print b #[0, [10, 2], 2]
print c #[0, [10, 2], 3]
a[1][0]=20 #可变对象
print a #[0, [20, 2], 2]
print b #[0, [20, 2], 2]
print c #[0, [20, 2], 3]
a[0] = 3 #不可变对象
print a #[3, [20, 2], 2]
print b #[3, [20, 2], 2]
print c #[0, [20, 2], 2]
a和c是单独的列表对象,但是他们包含的公共元素是共享的,修改a的元素就会修改c(可变类型才会变哦,如果是不可变类型会新建对象的).
- 深复制将创建一个新对象,并且递归地复制它包含的所有对象.可以使用copy.deepcopy()完成该工作.
import copy
a = [0,[1,2]]
b = copy.deepcopy(a)
b.append(2)
print a #[0, [1, 2]]
print b #[0, [1, 2], 2]
b[1][0] = 10
print a #[0, [1, 2]]
print b #[0, [10, 2], 2]
b[0] = 3
print a #[0, [1, 2]]
print b #[3, [10, 2], 2]
5 数据类型
None | Type(None无任何属性,值为false) |
---|---|
数字 | int,Long,float,cmplex,bool |
序列 | str,unicode,list,tuple,xrange |
映射 | dict |
集合 | set(可变集合),frozenset(不可变集合) |
5.1 None
None类型表示一个null对象,如果一个函数没有显式地返回值,则返回None,如果None在布尔表达式求值时为False。
5.2 序列
序列表示索引为非负值整数的有序对象集合(list,tuple,str).字符串是字符的序列,而列表和元组是任意Python对象的序列,字符串和元组是不可变的,而列表支持插入,删除,替换元素.索引序列都支持迭代.
1) 序列通用操作
- s[i]
- s[i:j] 切片,返回s[i]至s[j-1]的元素
- s[i:j:step],返回s[i],s[i+step],s[i+2*step],…..直至i + n * step >= j,如a = range(10) a[0:5:2] = [0,2,4],
如果i < j,step < 0,则返回[],(step不能为0)- len(s)
- min(s),max(s)
- sum(s)
- all(s),检查s中所有项是否为True
- any(s),检查s中任意项为True,有一个元素为真则返回True
- s[i] = value
- s[i:j] = t,切片赋值,
- s[i:j:step],扩展切片赋值
a = range(5) print a[0:5:2] #[0,2,4] print a[0:5:-1] #[] print a[4:0:-1] #[4,3,2,1] a = range(5) a[0:2] = [10] print a #[10,2,3,4] a[0:3] = range(10) #[1,2,3,4,5,6,7,8,9,4] a = range(5) a[0:5:2] = [10,20,30] #个数必须相同 print a #[10, 1, 20, 3, 30]
2) 列表,特有的函数
内置函数list(s)可以将任意可迭代对象转换为列表,如果s已经是一个列表,则该函数构造的新列表是s的一个浅复制.
list(s) | 将s转换为一个列表,浅复制 |
---|---|
s.append(x) | x添加到s尾部 |
s.extend(x) | 将列表x添加到x尾部 |
s.count(x) | 计算s中x的出现次数 |
s.index(x) | 搜索s中首次出现的x,并且返回索引,如果没有则会触发异常 |
s.insert(i,x) | 在索引i处插入x |
s.pop([i]) | 返回索引i处的元素并删除,如果省略i则返回最后一个元素并且删除 |
s.remove(x) | 从s中搜索x并删除 |
s.reverse() | 翻转s |
s.sort(cmp=None, key=None, reverse=False) | cmp比较规则,key在进行比较之前应用于每个元素的函数,reverse是否反转 |
a = [0,5,3,6,9,4]
f = lambda a:a%8
a.sort(key = f)
#[0, 9, 3, 4, 5, 6]
3) 字符串,特有的函数
通用操作虽然都是字符串实例,但是他们并不会修改原始的字符串数据(因为是不可变类型啊).
可利用help(str)查看所有方法
操作 | 解释 | return |
---|---|---|
s.capitalize(),capitalize = 以大写字母写 | 首字母大写 | string |
s.center(width[, fillchar]) | 在长度为width的字段内将字符串s居中,fillchar是填充字符(默认为空格),如果width还没有字符串本身长的话直接返回字符串s本身 | string |
s.count(sub[, start[, end]]) | 返回sub在s中出现的次数,start,end为s的范围 | int |
s.decode([encoding[,errors]]) | 编码,errors为出现错误时候的处理情况(默认为’strict’,还有’ignore’, ‘replace’ and’xmlcharrefreplace’) | object |
s.encode([encoding[,errors]]) | 解码 | object |
s.endswith(suffix[, start[, end]]) | 检查字符串s是否以suffix结尾 | bool |
s.expandtabs([tabsize]) | 使用空格替换制表符 | string |
s.find(sub [,start [,end]]) | 找到指定的子串首次出现的位置,未找到返回-1 | int |
s.format(*args, **kwargs) | 格式化s | string |
s.index(sub [,start [,end]]) | 找到指定的子串首次出现的位置,否则报异常 | int |
s.isalnum() | 检查所有字符是否都为字母或者数字 | bool |
s.isalpha() | 检查所有字符是否都为字母 | bool |
s.isdigit() | 检查所有字符是否都为数字 | bool |
s.islower() | 检查所有字符是否都为小写 | bool |
s.isspace() | 检查所有字符是否都为空白 | bool |
s.istitle() | 检查字符串是否为标题字符串(每个单词的首字母大写) | bool |
s.isupper() | 检查所有字符是否都为大写 | bool |
s.join(iterable) | 使用s作为分隔符连接迭代器中的字符串(必须为字符串) | string |
s.ljust(width[, fillchar]) | 在长度为width的字符串内左对齐s,右侧用fillchar来填充 | string |
s.lower() | 转化为小写 | string |
s.lstrip([chars]),strip = 剥夺,去掉 | 从索引0处开始,去掉字符串s中出现在chars中出现的字符,直至s中的字符不在chars中,chars默认为空格(从左侧开始,去掉s中出现在chars中的字符,当s中出现了不在chars中的字符,则停止,详细使用见代码) | string |
s.partition(sep),partition = 划分,分割,区分 | 使用seq将字符串s划分,返回(head,seq,tail),找到后停止查找,如果未找到seq,则返回(s,”,”),详见代码 | (head, sep, tail) |
s.replace(old, new[, count]) | 将出现在s中的old子串用new替换,count为次数,默认为全部替换 | string |
s.rfind(sub [,start [,end]]) | 找到一个子串最后出现的位置,未找到返回-1 | int |
s.rindex(sub [,start [,end]]) | 找到一个子串最后出现的位置,否则报错 | int |
s.rjust(width[, fillchar]) | 在长度为width的字符串内右对齐s,左侧用fillchar来填充 | string |
s.rpartition(sep) | s.partition(sep)相反 | string |
s.rsplit([sep [,maxsplit]]) | 从右侧,将s用sep拆分,如果省略了maxsplit则结果与split一样,(划分方向不一样) | list |
s.rstrip([chars]) | s.lstrip([chars])相反 | string |
s.split([sep [,maxsplit]]) | 从左侧,将s用sep拆分,maxsplit为划分次数 | list |
s.splitlines(keepends=False) | 将字符串分为一个行列表,如果keepends为1,则保留最后的换行符(以’\n’分割) | list |
s.startswith(prefix[, start[, end]]) | 检查s是否以prefix开头 | bool |
s.strip([chars]) | 删除s的开头和结尾出现在chrs中的字符,如果出现了一个不匹配的则停止 | string |
s.swapcase() | 将大写转换为小写,小写转换为大写 | string |
s.title() | 将s转换为标题格式,即每个单词的首字母大写 | string |
s.translate(table [,deletechars]) | 根据table给出的表(包含256个字符)转换string的字符,要过滤掉的字符放在deletechars中(感觉有点像加密- -) | string |
s.upper() | 转化为大写 | string |
s.zfill(width) | 在s左边填充0,直至宽度为width,和rjust(width,’0’)功能一样a,如果width没有s长的话直接返回s | string |
a = '123123'
b = '1234123'
c = ' 123123'
d = '123444451\n222\n2'
#测试lstrip([chars])
print a.lstrip() #123123
print c.lstrip() #123123,去掉了空格
print a.lstrip('123') #空,去掉了123123
print b.lstrip('123') #4123,去掉了123
print c.lstrip('123') # 123123,啥都没去掉
#测试partition
print a.partition('23') #('1', '23', '123')
print a.partition('3') #('12', '3', '123')
#rsplit
print b.rsplit('3') #['12', '12', '']
print b.rsplit('3') #['12', '412', '']
#splitlines
print d.splitlines() #['123444451', '222', '2']
#format
a = "{0}-{1}-{1}-{2}"
a.format(4,5,6) #'4-5-5-6'
4) xrange()
xrange([i,]j[,step])创建的对象表示一个整数范围k,如果i<= k < j.
i,step是可选的默认值为0和1.
很像list,但是并不是list,不支持切片等操作
a = list(xrange(0,10,2)) #[0,2,4,6,8]
print type(a) #<type 'xrange'>
5) 映射类型dict
dict,无序,任何不可变对象都可以用作字典键值,包含可变对象的列表,字典,元组不能作为键值,因为字典类型要求键值保持不变.
a = ([1,2],3) #a是元组不可变
print id(a[0]) #48835016
print id(a[0][0]) #31359704
a[0][0] = 100
print id(a[0]) #48835016 与上述一样
print id(a[0][0]) #31361312,改变了哦
print a
'''([100,2],3),我擦改变了!因为tuple是不可变类型,a[0]未改变,符合tuple要求,
但是list是可变类型啊,只要不改变a[0]的引用即可(a[0] = range(5)这样是不可以的哦),对list内部的操作是可以的(如a[0].append(3).
这就是为什么键值不能为包含可变类型的不可变类型的对象
'''
a[0].append(3)
print a #([100, 2, 3], 3)
dict内置方法
内置方法 | 含义 |
---|---|
len(D) | 键-值个数 |
D[k] | 键k对应的值 |
D[k] = x | 键k的值改为x |
del D[k] | 删除键k |
k in D | 如果k在D的键中返回True |
D.clear() | 删除所有元素 |
D.copy() | 浅复制字典的副本 |
dict.fromkeys(seq,val = None) | 创建一个新字典,seq(list,tuple)中的元素作为键,值为val |
D.get(k,default = None) | 如果键k在D中则返回对应的值,否则返回default |
D.has_key(k) | 查看键k是否在D中 |
D.items() | 返回由(key,value)组成的list |
D.iteritems() | 返回由(key,value)组成的迭代器 |
D.iterkeys() | 返回由key组成的迭代器 |
D.itervalues() | 返回由value组成的迭代器 |
D.keys() | 返回由key组成的list |
D.pop(k,[,default]) | 如果找到了D[k],则返回D[k]并删除,否则,如果给了default的值则返回default,如果没有提供,则引发KeyError异常 |
D.popitem() | 随机删除一个key-value,并返回成一个元组 |
D.setdefault(k[,d]) | 如果找到了D[k],返回,否则返回d,并将D[k] = d |
D.update(D2) | 将D2中的所有对象添加到D中 |
D.values() | 返回由value组成的list |
D.viewitems() | 查看所有键-值的方法 |
D.viewkeys() | 查看所有键的方法 |
D.viewvalues() | 查看所有值的方法 |
6) 集合
集合是唯一项的无序集,无切片操作,放入集合的项目必须是不可变的.
set,可变集合
frozenset,不可变集合
函数 | 含义 |
---|---|
s.copy() | 制作s的一份副本,浅复制 |
s.different(t) | 差集,在s不在t |
s.difference_update(t) | 删除s中所有在t中出现的元素 |
s.discard(mem) | 删除s中的mem,如果mem不在s中,不作任何操作 |
s.intersection(t) | 交集 |
s.intersection_update(t) | 将s更新为s与t的交集 |
s.isdisjoint(t) | 如果s和t没有相同项,则返回True |
s.issubset(t) | 如果s是t的子集,返回True |
s.issuperset(t) | 如果s是t的一个超集,返回True |
s.pop() | 返回集合中的任意一个元素,并删除 |
s.remove(m) | 删除m,如果m不在s,则报出异常 |
s.symmetric_difference(t) | s与t的对称差集 |
s.symmetric_difference_update() | 将s更新为s与t的对称差集 |
s.union() | 求并集 |
s.update(t) | 将t中所有的元素添加到s中,t可以是一个集合,一个序列,一个可迭代的任意对象 |
5.3 表示程序结构的内置类型
在Python中,函数,类,模块都可以当做数据操作的对象.
类型分类 | 类型名称 | 描述 |
---|---|---|
可调用 | types.BuiltinFunctionType | 内置方法或函数 |
可调用 | type | 内置类型和类的类型 |
可调用 | object | 所有类型和类的祖先 |
可调用 | types.FunctionType | 用户定义的函数 |
可调用 | types.MethodType | 类方法 |
模块 | types.ModuleType | 模块 |
类 | object | 所有类型和类的祖先 |
类型 | type | 内置类型和类的类型 |
1) 可调用类型
用户自定义函数,具有如下属性
def f():
pass
属性 | 描述 | 结果 |
---|---|---|
f.__name__ | 函数名称 | ‘f’ |
f.__dict__ | 包含函数属性的字典 | {} |
f.__code__ | 字节编译的代码 |
2) 类,类型与实例
定义类的时候,通常会生成一个type类型的对象
class Foo(object):
pass
type(Foo) # <type 'type'>
下表表示一个类型对象Foo = t的常用属性
属性 | 描述 |
---|---|
t.__doc__ | 文档字符串 |
t.__name__ | 类名称 |
t.__bases__ | 由基类组成的元组 |
t.__dict__ | 保存类方法和变量的字典 |
t.__module__ | 定义类的模块名称,也就是文件名字,如果是在本本文件调用的话就是__main__ |
t.__abstractmethods__ | 抽象方法名称的集合,如果不存在抽象方法,就是未定义 |
下面为实例 i = Foo()的特殊属性
i.__class__ | 实例所属的类 |
---|---|
i.__dict__ | 保存实例数据的字典 |
__dict__属性通常用于存储与一个实例相关的所有数据.就像i.var = value,这样的值就会保存在dict里面.但是如果用户使用的类使用了__slots__,就会使用一种更加有效的内部表示,实例也不会有__dict__属性.
__slots__是一个类变量,由一序列型对象组成,由所有合法标识构成的实例属性来表示.任何试图创建其名不在__slots__中的名字的实例属性都将导致AttributeError异常.
3) 模块
模块类型就是一个容易,可保存使用import语句加载的对象.例如,import foo,就会把名称foo模块的属性赋给调用的模块.可用属性如下:
属性 | 描述 |
---|---|
m.__doc__ | 模块文档字符串 |
m.__dict__ | 模块相关的字典 |
m.__name__ | 模块名称 |
m.__file__ | 用于加载模块的文件 |
m.__path__ | 完全限定包名,只在模块对象引用包时定义 |
5.4 对象行为和特殊方法
Python中的对象通常根据它们的行为和实现的功能进行分类.例如,所有序列类型放在一组,如list,tuple,因为他们都支持一系列相同操作,如s[n],len(s)等.所有基本的解释器操作都通过特殊的对象方法来实现.
特殊的方法名称前后始终带有双下划线__.当程序执行时,这些方法都由解释器自动触发.如x + y被映射为内部方法 x.__add__(y),x[k]映射为x.__getitem__(k).
每种数据类型的行为完全取决于它实现的一组特殊方法.
《对象行为完全取决于特殊方法啊= =》
1) 对象的创建与销毁
方法 | 描述 |
---|---|
__new__(cls[,*args[,**kwargs]) | 创建新实例时调用的类方法 |
__init__(self[,*args[,**kwargs]) | 初始化新实例时调用 |
__del__(self) | 销毁实例时候调用 |
调用A(args)创建对象时候,会进行以下步骤:
- x = A.__new__(A,args)
- is isinstance(x,A): x.__init__(args)
自定义的对象中,很少定义__new__()或者__del__()方法.
- new通常只定义在元类或继承不可变类型之一的用户自定义对象中.
- del方法只在有某种关键资源管理问题的情况才会定义,如释放锁定或关闭连接.
2) 对象的字符串表示
方法 | 描述 |
---|---|
__format__(self,format_spec) | 创建格式化后的表示 |
__repr__(self) | 创建对象的字符串表示 |
__str__(self) | 创建简单的字符串表示 |
__repr__(),通常返回一个表达式字符串,可对该字符串重新求值以重新创建对象.
如果无法创建字符串表达式,repr()返回一个\<_message>形式的字符串
a = range(3) #[0,1,2]
s = repr(a) #'[0,1,2]'
b = eval(s) #[0,1,2]
class a(object):
pass
b = a()
print a.__repr__() # error
print b.__repr__() #'<__main__.a object at 0x0000000002EF7EF0>'
__str__()与__repr__()的区别是它返回的字符串更加简明易懂.如果该方法未定义就会调用__repr__()方法
__format__ , “{0}a{0}b{1}”.format(123,456,789) 等价于
“{0}a{0}b{1}”.__format__(123,456,789)
3) 对象比较与排序
对象测试与散列的特殊方法.
方法 | 属性 |
---|---|
__bool__(self) | 用于真值测试,返回值为True或False.如果该方法未定义,Python将调用__len__()方法来确定其对象的真值. |
__hash__(self) | 定义在要作字典中键的对象上.如果两个对象比较厚相等,作为返回值得整数应该完全相同.可变对象不应该定义该方法,因为对一个对象所做的任何改动都将改变散列值,从而在后续的字典查找中无法定位对象. |
用于比较的方法见下表:
方法 | 结果 |
---|---|
__lt__(self,other) | self < other |
__le__(self,other) | self <= other |
__gt__(self,other) | self > other |
__ge__(self,other) | self >= other |
__eq__(self,other) | self == other |
__ne__(self,other) | self < other |
4) 类型检查
方法 | 结果 |
---|---|
__instancecheck__(cls,object) | isinstance(object,cls) |
__subclasscheck__(self,other) | issubclass(sub,cls) |
5) 属性访问
方法 | 描述 |
---|---|
__getattribute__(self,name) | 返回self.name |
__getattr__(self,name) | 返回属性self.name,如果通过常规属性查找未找到属性,则引发AttributeError异常 |
__setattr__(self,name,name) | 设置self.name = value |
__delattr__(self,name) | 删除属性self.name |
访问属性时始终会调用__getattribute__()方法,如果找到属性则返回之,否则调用__getattr__()方法来触发异常.设置属性会调用__setattr__()方法,删除属性时候则会调用__delattr__()方法.
6) 属性包装与描述符
描述符的__get__(),__set__()和__delete__()方法用于同类型的__getattribute__(),__setattr__(),__delattr__()方法进行交互.如果在用户自定义类的主体中放入一个描述符对象的实体,这种交互就会发生.
7) 序列与映射方法
如果对象要模拟序列和映射对象的行为,用下表的方法.
方法 | 描述 |
---|---|
__len__(self) | 返回self的长度 |
__getitem__(self,key) | 返回self[key] |
__setitem__(self,key,value) | 设置self[key] =value |
__delitem__(self,key) | 删除self[key] |
__contains__(self,obj) | 如果在obj在self中,则返回True,否则False |
a = [0,1,2,3,4,5,6]
len(a) #a.__len__()
a[2] #a.__getitem__(2)
a[0:4] #a.__getitem__(slice(0,4,None)),slice对象描述切片范围的属性
a[1] = 7 #a.__setitem__(1,7)
del a[1] #a.__delitem__(1)
5 in a #a.__contains__(5)
8) 迭代器
如果对象obj支持迭代,它必须提供方法obj.__iter__(),该方法返回一个迭代器对象.迭代器对象iter必须返回一个方法iter.next(),该方法返回下一个对象,迭代结束引发异常.
class A(object):
def __init__(self):
self.num = 10
def __iter__(self):
return self
def next(self):
if self.num > 0:
self.num -= 1
return self.num
else:
raise StopIteration
a = A()
b = iter(a)
print b.next() # 9
for i in b:
print i #8,7,6,5,4,3,2,1,0
#for遇到StopIteration会停止循环
9) 可调用接口
对象可提供__call__(self[,*arg[,**kwargs])方法可模拟函数的行为.如果一个对象x提供了该方法,就可以像函数一样调用它.就是说x(arg1,arg2….)等同于调用了x.__call__(self,arg1,arg2…).模拟函数的对象可以用于创建仿函数或者代理.
10) 上下文管理协议
with operate as var:
statements
执行with语句时,就会调用__enter__()方法,该方法的返回值将被放入var中,离开with语句的时候回调用__exit__()方法.__exit__()方法接收当前异常的类型、值和跟踪作为参数.如果没有要处理的错误,所有三个值都将被置为None.
11) 对象检查dir
__dir__(self)获得对象可用函数名称列表.