《Python参考手册》3 类型与对象

文章目录

    • 3.2 对象标识与类型
    • 3.3 引用计数和垃圾回收
    • 3.4 引用和复制
    • 3.5 第一类对象
    • 3.6 表示数据的内置类型
      • 3.6.1 None
      • 3.6.2 数值类型
      • 3.6.3 序列
        • 3.6.3.1 序列通用操作
        • 3.6.3.2 列表
        • 3.6.3.3 字符串
        • 3.6.3.4 range()对象
        • 3.6.3.5 元组
      • 3.6.4 映射类型
      • 3.6.5 集合类型
    • 3.7 表示程序结构的内置类型
      • 3.7.1 可调用类型
          • 3.7.1.1 用户定义的函数
          • 3.7.1.2 方法
          • 3.7.1.3 内置函数与方法
          • 3.7.1.4 可调用的类与实例
        • 3.7.2 类、类型与实例
        • 3.7.3 模块
    • 3.8 解释器内部使用的内置类型
    • 3.9 对象行为与特殊方法
      • 3.9.1 对象创建与销毁
      • 3.9.2 对象字符串表示
      • 3.9.3 对象比较与排序
      • 3.9.4 类型检查
      • 3.9.5 属性访问
      • 3.9.6 属性包装与描述符
      • 3.9.7 序列与映射方法
      • 3.9.8 迭代
      • 3.9.9 数学操作
      • 3.9.10 可调用接口
      • 3.9.11 上下文管理协议
      • 3.9.12 对象检查与dir()

3.2 对象标识与类型

内置函数id(obj)返回一个对象的标识,它是一个整数值。

#a和b是同一个对象
a is b 
#a和b具有相同的值,由具体实现决定
a == b

type(obj)可以检查对象的类型,对象的类型在解释器当中应该只需要存在一个,因此可以使用type(x) is type(y)判断两个对象的类型是否完全一样。
检查类型还可以使用内置函数:isinstance(obj,classname),它能够识别继承体系。

3.3 引用计数和垃圾回收

所有对象都有引用计数,给一个对象分配一个新名称,或是将对象放入一个容器中,都会增加引用计数。使用del obj或者超出对象作用域范围,计数就会减少。使用sys.getrefcount()可以获得对象当前的引用计数。

当对象的引用计数为0,它将被垃圾回收机制处理掉。
如果两个对象存在循环引用,那么它们的引用计数永远大于0。解释器会周期性地检查不可访问对象并删除它们。

3.4 引用和复制

一个常见的安全问题:

a=[1,2,3]
b=a
b[1]=4
print(a) #1,4,3

对于容器对象,Python提供2种复制:浅复制,深复制。浅复制创建一个新的容器对象,但它的元素仍然原容器元素的引用。深复制将创建一个新对象,并递归地复制所有元素。
浅复制

a=[1,2,[3,4]]
b=list(a) #浅复制
b[1]=3
print(a) # [1, 2, [3, 4]]
b[2][0]=1
print(a) #[1, 2, [1, 4]]

深复制

import copy
a=[1,2,[3,4]]
b=copy.deepcopy(a)
b[1]=3
print(a) # [1, 2, [3, 4]]
b[2][0]=1
print(a) # [1, 2, [3, 4]]

3.5 第一类对象

Python所有对象都属于first class,所有能用标识符命名的对象都具有平等的身份,这还意味着能被命名的对象都可以当作数据处理。

line='good,100,490.0'
fieldTypes=[str,int,float]
rawFields=line.split(",")
fields=[]
for i in range(len(rawFields)):
    val=rawFields[i]
    ty=fieldTypes[i]
    fields.append(ty(val))  #str(x),int(x),float(x)
print(fields) #['good', 100, 490.0]

在这个例子中,函数str(x)的名称str被当作数据处理。

3.6 表示数据的内置类型

同一个类型包含一些通用的操作:

  • None
  • 数字:int,long,float,complex,bool
  • 序列:字符串,列表,元组,range()
  • 映射:dict
  • 集合:set ,frozenset

序列包含一系列的通用操作。

3.6.1 None

None表示一个null对象。如果一个方法没有返回值,那么它会返回None对象,它的布尔求值是False.

3.6.2 数值类型

Python的数值类型都是不可变对象。
Python3中int,long已经整合为一种类型了。
兼容措施举例:Python为了兼容复数运算,给整数增加了属性y.real,y.imag
decimal:十进制算术模块
fractions:有理数模块

3.6.3 序列

序列有以下几种:

  • 字符串str,不可变
  • 列表list,可变
  • 元组tuple,不可变
  • range()对象

3.6.3.1 序列通用操作

所有序列均支持以下操作

s='abcdefghijk'
'ab' in s #如果s中的某项等于x
'ab' not in s
s+t # s与t拼接
s*n #s与自身进行n次拼接,它是一种浅复制.
n*s #s与自身进行n次拼接,它是一种浅复制。
s[2] # c
s[1:6] #bcdef,切片
s[1:6:2] # bdf ,跨步切片,步长为2
len(s) # 11
min(s) #a
max(s) #f
sum(s[,initial]) # 只支持数字
all(s) #True 检查所有项是否为True 
any(s) #True 检查任意项是否为True

#还有一个常见的in操作,它不是所有序列都支持。
# in 操作需要注意,序列的迭代对象
if 'a' in s:
	pass

注意:只有可变序列支持以下操作.

c=list(s)
s[1]='v' #赋值
s[1:3]='v' #切片赋值,左右两边个数不必相等
s[1:5,2]='cv' #跨步切片赋值,左右两边个数必须相等
del s[i] # 删除 
del s[1:3]
del s[1:4:2]

3.6.3.2 列表

列表的创建方法:

l=[]
l=list()

s='abc'

chars=list(s) abc

将s转换为一个列表,s必须是一个序列。它是一种浅复制。

chars.append('d') abcd
追加一个元素

chars.extend('ef') abcdef
追加一个序列

chars.count('e') 1
计算e出现的次数

chars.index('a') 0
第一个a出现的索引位置

chars.index('a',1) 报错
从第1位开始,第一个a出现的位置

同样的有:
chars.index('a',1,3)

chars.insert(1,a) aabcdef
在索引1处插入字符

chars.pop([i])
返回元素i并从列表中移除。如果省略i,则从列表尾部开始

chars.remove('a')
搜索x并移除,一次最多移除1个。

chars.reverse()
颠倒

chars.sort([key[,reverse]])
排序,它会修改原列表

s in chars
是否包含

3.6.3.3 字符串

字符串是不可变对象

s='abcdD'
s.capitalize() #大写
'Abcdd'

s.center(2)     
'abcdD'
s.center(10)   
'  abcdD   '
s.center(10,'-') #按指定长度居中,并填充指定字符,默认是空格
'--abcdD---'

s.count('a')    #计数
1
s.count('d')
1
s.count('d',1)  # 从第1位以后开始计数
1
s.count('d',9)
0

s.encode('utf-8') # 按指定编码格式编码
b'abcdD'
s.encode(encoding='gb2312')
b'abcdD'
b'abcd'.decode()    # 字节码 解码
'abcd'

s.endswith('D')
True


s.expandtabs()
'abcdD'
'\tabcd'.expandtabs()   # 转换tab为空格
'        abcd'


s.find('e')  #查询某字符或字符串,如果存在返回索引
-1
s.find('D')
4
'abcda'.rfind('a') #从右开始找
4


s.index('e') #查询某字符/字符串的索引,如果不存在会报错,不推荐。
s.rindex('e') #从右开始查找

'abcd{age}'.format(age='e') 
'abcde'
'abcd{1}'.format(age='e') #Error

'abcd{0}'.format(age='e') #Error

'abcd{0}'.format('e')
'abcde'

s.isalnum() #全部是字母或数字
True
s.isalpha() 
True
s.isdigit()
False

s.islower()
False
s.isupper()
False
s.isspace() #全是空格
False
s.istitle() #姓名格式
False




s.join('d')
'd'
'0'.join('abc')  #用s连接指定序列
'a0b0c'


s.rjust(10)  #右对齐,左边补齐指定字符
'     abcdD'
s.rjust(10,-)  
'-----abcdD'
s.ljust(10)  #左对齐
'abcdD     '


s.rsplit('d')   #分隔
['abc', 'D']

s.lstrip() #去掉左边空格
'abcdD'
s.rstrip() #去掉右边空格
'abcdD'
s.strip() #去掉两边的空格
'abcdD'


s.partition('bc') # 将字符串分为三部分:[head,sep,tail],如果不存在sep,则[str,'','']
('a', 'bc', 'dD')


#将包含换行符的字符串变换成行
print('ab\nc'.splitlines())
['ab', 'c']
print('ab\nc'.splitlines(True)) #保留换行符
['ab\n', 'c']


s.swapcase() #变换大小写
'ABCDd'

s.zfill(10) #s.rjust(10,'0')
'00000abcdD'

len(s.ljust(10))
10

s.translate(str.maketrans('abc','321')) #转换表
'321dD'

3.6.3.4 range()对象

range([i,] j [,stride])
i的默认值为为0,stride默认值为1.
它有不同点:它不支持所有的切片操作。

3.6.3.5 元组

不可变列表。元组可以充当对象使用,如(学号,姓名,班级…)作为学生信息对象元组,元组的创建方法(忽略括号,就是元组):

person=(item,) #,是为了语法识别,
a=()
a=item,
b=item0,item1

for a,b,c in persons:
	pass

3.6.4 映射类型

序列有序,映射无序。
dict是唯一的映射类型。
任何不可变的对象都可以作为字典的键值:字符串,数字,元组。列表、字典包含可变对象不能作为键,元组如果包含可变对象,也不能作为键。
dict的迭代部分为keys

m=dict()
m['a']='a';m[1]='b';m[2]='c'

m
{'a': 'a', 1: 'b', 2: 'c'}

len(m)
3

m[2]
'c'
m[3] # Error 找不到会报错,所以这种索引模式不推荐使用

del m[1] #删除某key ,key不存在会报错


2 in m # 判断2是否是key
True


m.copy() #复制字典
{'a': 'a', 2: 'c'}
m.fromkeys('abc')  #创建一个新字典并将序列s中的所有元素作为新字典的键,而且这些键的值均为value
{'a': None, 'b': None, 'c': None}
m.fromkeys('abc','1')
{'a': '1', 'b': '1', 'c': '1'}


m.get('a',1) #查询某key的推荐用法,如果不存在会返回指定默认值
'a'
m.get('c','1')
'1'


m.items()  # 返回键值对的序列,注意它不是list,类型是dict_items
dict_items([('a', 'a'), (2, 'c')])
m.items() is list
False

m.pop('e') #弹出指定key的值,如果不存在则Error

m.pop('e','1') #弹出指定的值,如果不存在则返回指定的值 。推荐使用
'1'
m.popitem(2) # 弹出指定的键值队。
(2, 'c')


m.setdefault('e','1') # 查找指定的key对应的value,如果key不存在:m[key]=value,且返回value

m.update({'c':'c'}) # 将b对象的所有键值对加入m中

m.values()  # m的值的序列,类型是dict_values
dict_values(['a', 'c', 2, '2', 'c'])

3.6.5 集合类型

集合是不同元素的无序集合。
集合不提供索引和切片操作。
集合的元素必须是不可变的。
集合分为:setfrozenset(不可变集合)

创建:
s=set('abcd')
fs=frozenset('abcd')

set和frozenset都支持的操作:

len(s)
长度

s.copy()
制作副本

s.difference(t)
差集。s-t,t是一个序列

s.intersection(t)
交集。s&t

s.isdisjoint(t)
如果st交集为空,则返回True

s.issubset(t)
s是否是t的子集,t是一个序列

s.issuperset(t)
s是否是t的超集,t是一个序列

s.symmetric_diffenence(t)
s^t对称差集:(s+t-s^t)

s.union(t)
并集:s+t

可变集合set支持的操作:
s.add(item)
增加元素。如果item已经在s中,则无任何效果。

s.clear()
清除所有元素

s.difference_update(t)
删除ss&t的元素

s.discard(item)
删除item,如果不存在无效果

s.remove(item)
删除item,如果不存在,KeyError

s.pop()
弹出任意元素。

s.symmetric_difference_update(t)
计算s^t,结果放入s中。应该不常用。

s.update(t)
t中的元素添加进s中。update通常都表示加入多个元素

参数t均是指可迭代对象(序列)。

3.7 表示程序结构的内置类型

3.7.1 可调用类型

可调用类型表示支持函数调用操作的对象:

  • 用户定义的函数:types.FunctionType
  • 类方法:types.MethodType
  • 内置的类型和类的类型:type
  • 所有类型和类的祖先
  • 内置函数或方法:types.BuiltinFunctionType
3.7.1.1 用户定义的函数

先看一例:

def foo(x,y):
    "return x+y"
    return x+y
    
bar=lambda x,y:x+y
    
foo.z=1
foo.x=2
z=foo(1,2)
print(z)
print(foo.z)
print(foo.__dict__)

# 输出
3
1
{'z': 1, 'x': 2}

请注意foo.z=1之所以能这么使用,是因为foo也是一个对象,它包含一个dict属性。
用户自定义的函数f包含以下属性:

属性 描述
f.__doc__ 文档字符串
f.__name__ 函数名字
f.__dict__ 包含函数属性的字典
f.__code__ 编译的字节码
f.__defaults__ 包含默认参数的元祖
f.__globals__ 定义全局命名空间的字典
f.__closure__ 包含与潜逃作用域相关数据的元祖
3.7.1.2 方法

方法与用户自定义函数的区别是,它在这里指类中定义的函数。
有三种常见的方法:

class Foo(object):
    def instance_method(self,arg): #实例方法
        pass
    @classmethod
    def class_method(cls,arg): # 类方法,不需要实例
        print(cls)
        pass
    @staticmethod
    def static_method(arg): #静态方法,不需要实例
        pass

Foo.class_method(1) # 输出
Foo.instance_method(2)# Error

实例方法默认将一个实例作为第一参数传入(self).
类方法默认将类本身当作对象,并将该对象作为一个第一个参数。
静态方法它不能将self传入。

看一例子:

class Foo(object):
    def sum(self,x,y):
        return x+y
f=Foo()
s=f.sum
print(s(1,2))

>> 3
print(s(1,2))

f.sumf.sum(1,2)的区别是:这是两种不同的操作,前一个的.表示对象属性查找,第二个.表示函数调用。调用方法时两种操作都会发生。
上述s=f.sum是将一个实例和一个方法绑定到了s上,故s又称绑定方法,s封装了一个实例和一个方法。显然创建绑定方法时实例不是必须的,只需要传入类和方法,然后在调用时传入一个实例即可。它同java的反射机制很类似。

types.MethodType的属性如下:

属性 描述
m.__doc__ 文档字符串
m.__name__ 文档字符串
m.__class__ 该方法所在的类
m.__func__ 方法对象
m.__self__ 实例
m.__dict__ 字典
3.7.1.3 内置函数与方法

对象types.BuiltinFunctionType用于表示用C/C++实现的函数和方法。
内置方法只有三个属性:

属性 描述
m.__doc__ 文档字符串
m.__name__ 文档字符串
m.__self__ 实例

对于len(seq)这样的内置函数,self被设置为None,表明函数没有绑定到任何特定的对象。
x.append('a')中self被设置为x

3.7.1.4 可调用的类与实例

类对象和实例也可以当作可调用的对象进行操作。

3.7.2 类、类型与实例

定义类时通常会生成一个type类型对象,使用type(instance)可以查看。
它有以下属性:

属性 描述
t.__doc__ 文档字符串
t.__name__ 类名称
t.__bases__ 由基类构成的元祖
t.__dict__ 保存类方法和变量的字典
t.__module__ 定义类的模块名称
t.__abstractmethods__ 抽象方法名称的集合

3.7.3 模块

模块类型是一个容器,可保存使用import语句加载的对象。

import foo

foo.x=1
foo.sum(1,2)

显然模块最重要的属性是一个dict,它保存了foo相关的信息,用于调用时查找。
模块的dict结构如下:

{
    'foo':dict,
    'foo0':dict
}

它以import的类名/包名作为key(命名空间)。

3.8 解释器内部使用的内置类型

有以下一些种类:

  • 跟踪对象:出现异常时创建跟踪对象。
  • 代码对象: 即编译过的字节码
  • 帧对象: 执行帧,出现在跟踪对象中。
  • 生成器对象:调用生成器函数时生成(yield)
  • 切片对象:切片对象
  • 省略号(Ellipsis):暂时不懂。

生成器对象有两个用途:迭代器,容器。它有如下主要属性:

属性 描述
g.__next__ 执行函数,直到遇到下一条yield语句为止,并返回该值
g.send(value) 向生成器发送一个值,yield语句会返回该值,生产器将一直执行直到下一次yield
g.close()
g.gi_running 显示生成器函数目前是否正在运行的整数

切片对象是在a[1:3:1]时创建的。它有三个只读属性:

  • start
  • stop
  • step

举一例:

s=slice(10,20)
print(s.indices(100)) #如果序列长100,则该切片如何应用,返回值是(start,stop,step)
print(s.indices(2))     #如果序列长2,则该切片如何应用
print(s.indices(20))
print(s.indices(10))
print('abcdefghijklmn'[s]) #应用切片

# 输出
(10, 20, 1)
(2, 2, 1)
(10, 20, 1)
(10, 10, 1)
klmn

3.9 对象行为与特殊方法

对象实现了某些方法,就可以支持Python内置函数的一些功能。
len(obj)可用的要求是obj实现了__len__(self)方法。

3.9.1 对象创建与销毁

涉及到三个方法:

  • __new__(cls [,*args [,**kwargs] ])
    它是在创建实例时调用,一般不定义,否则需要手动创建实例。
  • __init__(self[,*args [,**kwargs] ])
    它是在创建实例调用。
  • __del__(self)
    它是在实例销毁时调用,需注意调用del x ,是减少引用计数,不会立刻调用此方法。

*args
如果我们不确定要往函数中传入多少个参数,或者我们想往函数中以列表和元组的形式传参数时,那就使要用args
**kwargs
如果我们不知道要往函数中传入多少个关键词参数,或者想传入字典的值作为关键词参数时,那就要使用**kwargs,其中kwargs是一个dict

class Foo(object):
    def add(self,x,y,**kwargs):
        print(kwargs)
        return x+y
m=dict()
m['a']=3
print(Foo().add(3,4,s=5,t=6))

>>>
{'s': 5, 't': 6}
7

3.9.2 对象字符串表示

创建一个对象的各种字符串表示。它涉及:

  • __format__(self,format_spec)
  • __repr__(self)
  • __str__(self)

以上每个方法都可以利用一个Python内置函数访问,这也是Python常见做法。
看一例子:

class FooClass(object):
    def __init__(self):
        print("__init__")
    def __repr__(self):
        return "FooClass repr"
    def __str__(self):
        return "FooClass str"
    def __format__(self, format_spec):
        return "FooClass is {0}".format(format_spec)
        
fc=FooClass()
print(fc)
print(str(fc))
print(repr(fc))
print(format(fc,"fc"))

>>
FooClass str   # str()函数,如果__str__为定义则调用repr
FooClass repr   # repr函数
FooClass is fc # format()函数

这是与Java的不同点,Java实现类似的功能,采取的方法是定义一个接口,而类实现这个接口。Python直接定义一个内置方法,这说明Python的参数校验比Java松散许多,带来的后果是Python程序较为精简,因为它只需要写出实接口的方法即可,而不需要声明实现了某个接口。

3.9.3 对象比较与排序

涉及以下方法:

方法 描述
__bool__(self) 关系到if x:的结果
__hash__(self) hash(x)的结果,理论上==的对象hash应该相等
__lt__(self,other) 返回self的真假
__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的真假

如果要使用==应定义__eq__;
如果要使用对象排序或者max等,必须至少定义__lt__.

3.9.4 类型检查

应用于定义抽象的基类和接口,它涉及:

  • __instancecheck__(cls,object) : isinstance(object,cls)
  • __subclasscheck_-(cls,sub) : issubclass(sub,cls)

3.9.5 属性访问

属性访问即(.),del,对象的读写删除属性。
对象的属性访问相关的方法如下:

方法 描述
__getattribute__(self,name) 返回self.name
__getattr__(self,name) 返回self.name
__setattr__(self,name,value) self.name=value
__delattr__(self,name) 删除self.name

注意访问属性时,顺序如下:

  1. 调用__getattribute__
  2. 第一步未找到属性则调用__getattr__

3.9.6 属性包装与描述符

它同属性访问有相同的功能,不过它使用一个额外逻辑的描述符对象包装属性,往往用于实现对象系统的底层功能(绑定和非绑定方法,类方法,静态方法和特性),极少情况才需要定义.

方法 描述
__get__(self,name) 返回self.name
__set__(self,name,value) self.name=value
__delete__(self,name) 删除self.name

3.9.7 序列与映射方法

要使对象支持映射的方法,相关方法如下:

方法 描述
__len__(self) 返回self长度,关联len(x)结果
__getitem__(self,key) 关联self[key]
__setitem__(self,key,value) self[key]=value
__delitem__(self,key) 删除self[key]
__contains__(self,obj) 关系到if obj in x的结果

请注意__getitem__同属性访问的区别是,它使用的是索引,而不是属性查找。

class Foo(object):
    def __getitem__(self, item):
        print("do getitem")
        return "__getitem__"
    def __setitem__(self, key, value):
        print("__setitem__")
        pass
f=Foo()
f['a']=3
print(f['a'])

>>>
__setitem__
do getitem
__getitem__

3.9.8 迭代

如果对象obj要支持迭代,它必须提供方法__iter__(),返回一个迭代器对象。而迭代器对象必须实现一个方法:__next__()返回下一个对象或在迭代结束时返回StopIterationError

3.9.9 数学操作

Python实现数学操作的原理:
执行表达式:x + y:

  1. 解释为x.__add__(y)
  2. 如果x没有add,则解释为y.__radd__(x)
  3. 报错

要某类对象支持数学操作,就需要在类中实现对应的方法。

3.9.10 可调用接口

对象通过提供__call(self[ , *args [, **kwargs]])方法可以模拟函数的行为,X(arg1,arg2,...)等同于调用X.__call__(self,arg1,arg2,...).它可用于创建仿函数或代理。

class DistanceFrom(object):
    def __init__(self,origin):
        self.origin=origin
    def __call__(self,x):
        return abs(x-self.origin)

nums=[1,37,42,101,13,9,-20]
nums.sort(key=DistanceFrom(10))
print(nums)

>>
[9, 13, 1, 37, -20, 42, 101]

sort(key=None,reverse=False):其中key接收一个带有一个参数的函数,在运行排序时将元素值当作参数传入该函数,得到的结果即为提取的排序因子。
所以以上函数还可以这样改造:

class DistanceFrom(object):
    def __init__(self, origin):
        self.origin = origin
    @staticmethod
    def distance(x):
        return abs(x-10)

nums = [1, 37, 42, 101, 13, 9, -20]
nums.sort(key=DistanceFrom.distance)
print(nums)

3.9.11 上下文管理协议

语法:

with context_obj [as var]:
    statements

其中context_obj需要实现__enter____exit__两个方法。

class DistanceFrom(object):
    def __enter__(self):
        print('enter')
        return "1"
    def __exit__(self, exception_type, exception_val, exception_stack):
        print("exit")
        pass

df=DistanceFrom(0)
with df as var:
    print("statements")

print(var)

>>
enter
statements
exit
1

3.9.12 对象检查与dir()

dir(obj)函数通常用于检查对象,查看对象可使用信息。要支持该调用,类需要实现__dir__(self)方法。

你可能感兴趣的:(python)