特殊方法、属性通常以双下划线__开头,开发者可以直接调用也可以重写它们
一、 常见的特殊方法
1. __repr__()方法
输出对象的“自我描述”信息,返回该对象实现类的“类名+object at+内存地址”
class Item:
def __init__(self,name,price):
self.name=name
self.price=price
#创建一个Item对象并赋值给im变量
im=Item('鼠标',28.9)
#输出im所引用的Item对象
print(im) #<__main__.Item object at 0x000002580B687438>
#上面实际输出的是Item对象__repr__()方法的返回值
print(im.__repr__()) #<__main__.Item object at 0x000002580B687438>
#输出为 类名+object at+内存地址
可重写__repr__()方法,返回用户实际需要的信息
class Item:
def __init__(self,name,price):
self.name=name
self.price=price
#重写__repr__()方法
def __repr__(self):
return "Item[name="+self.name+",price="+str(self.price)+"]"
#创建一个Item对象并赋值给im变量
im=Item('鼠标',28.9)
print(im) # Item[name=鼠标,price=28.9]
print(im.__repr__()) # Item[name=鼠标,price=28.9]
2. 析构方法 __del__()
析构方法 __del__()与构造方法__init__()对应,用于销毁python对象。任何python对象被系统回收之前,系统都会自动调用该对象析构方法。
python自动进行对象垃圾回收过程(释放不再需要的对象占用的内存空间)
python采用自动引用计数(ARC)方式回收对象占用的空间,当一个对象的引用计数变为0(即没有被引用了),python就会回收该对象。
3. __dir__()方法
用于列出对象内部所有属性及方法名(包括自定义的和内置的)
当执行dir(对象名)时,实际就是将__dir__()方法的返回值进行排序并且包装成列表
class Item:
def __init__(self,name,price):
self.name=name
self.price=price
#重写__repr__()方法
def printItem(self):
print("Item[name="+self.name+",price="+str(self.price)+"]")
#创建一个Item对象并赋值给im变量
im=Item('鼠标',28.9)
print(im.__dir__())
print(dir(im))
4. __dict__属性
用于查看对象内部存储的所有属性名和属性值组成的字典
既可使用__dict__属性查看对象内部状态,也可通过字典语法来访问或修改指定属性的值
class Item:
def __init__(self,name,price):
self.name=name
self.price=price
#重写__repr__()方法
def printItem(self):
print("Item[name="+self.name+",price="+str(self.price)+"]")
#创建一个Item对象并赋值给im变量
im=Item('鼠标',28.9)
print(im.__dict__) # {'name': '鼠标', 'price': 28.9}
print(im.__dict__['name']) # 鼠标
im.__dict__['price']=100
print(im.name) # 鼠标
print(im.price) # 100
5. 其他常用方法
__getattribute__(self,name):程序访问对象name属性时被自动调用
__getattr__(self,name):程序访问对象name属性且该属性不存在时被自动调用
__setattr__(self,name):程序对对象name属性赋值时被自动调用
__delattr__(self,name):程序删除对象name属性时被自动调用
二、 与反射相关的属性和方法
用于在程序运行过程中动态判断是否包含某个属性、方法,查看及动态设置属性值
1. 动态操作属性
- hasattr(obj,name):检查obj对象是否包含名为name的属性或方法
- getattr(obj,name[,default]):获取obj对象中名为name的属性的值(不包括方法)
- setattr(obj,name,value,/):设置obj对象中名为name的属性的值(包括方法),若设置的name不存在,相当于添加新属性
class Item:
def __init__(self,name,price):
self.name=name
self.price=price
#重写__repr__()方法
def printItem(self):
print("Item[name="+self.name+",price="+str(self.price)+"]")
c=Item('鼠标',28.9)
#判断是否包含指定的属性或方法
print(hasattr(c,'name')) #True
print(hasattr(c,'printItem')) #True
print(hasattr(c,'info')) #False
#获取属性值(不能用于方法)
print(getattr(c,'name')) #鼠标
#设置属性值
setattr(c,'price',100) #100
print(c.price)
#setattr设置新方法
def newfunc():
print("一个新方法")
setattr(c,'printItem',newfunc)
c.printItem() #一个新方法
#setattr还可将方法替换为普通字符串,此时printItem就变为一个属性
setattr(c,'printItem','test strings')
print(c.printItem) #test strings
2. __call__属性
hasattr方法检查对象是否包含指定属性或方法,而__call__属性就用于判断这个对象是否可调用,进而判断该对象到底是属性还是方法。
实际上方法可执行关键就在于__call()__方法,func(arg1,arg2)实际上只是func.__call__(arg1,arg2)的快捷写法
class Item:
def __init__(self,name,price):
self.name=name
self.price=price
def printItem(self):
print("Item[name="+self.name+",price="+str(self.price)+"]")
c=Item('鼠标',28.9)
#判断对象是否可调用
print(hasattr(c.name,'__call__')) #False
print(hasattr(c.price,'__call__')) #False
print(hasattr(c.printItem,'__call__')) #True
三、自定义序列
__len__(self):返回值决定元素个数 __contains__(self, item):判断序列是否包含指定元素 __getitem__(self, key):获取指定索引对应元素,key应为整数或slice,否则该方法会引发KeyError异常 __setitem__(self, key, value):设置指定索引对应元素值为value __delitem__(self, key):删除指定索引对应的元素
若要实现自定义不可变序列,通常需指定前3个方法;若要实现自定义可变序列,通常需指定上面5个方法
'''
下面非程序实现一个字符串序列,新序列中每个元素长度都为3个字符,类似'aac','fff','kkk'
'''
def check_key(key):
'''
检查序列的索引,索引必须为非负整数值。
若不为整数,引发TypeError异常;若为负数,引发IndexError异常
'''
if not isinstance(key,int): #若不为整数,引发TypeError异常
raise TypeError('索引值必须为整数')
if key<0: #若为负数,引发IndexError异常
raise IndexError('索引值不能为负数')
if key>= 26**3:
raise IndexError('索引值不能超过%d' % 26**3)
class StringSeq:
def __init__(self):
#用于存储被修改的数据
self.__changed={}
# 用于存储已删除的元素索引
self.__delete=[]
#返回序列元素个数
def __len__(self):
return 26**3
#根据索引获取序列中元素
def __getitem__(self, key):
check_key(key)
#若在self.__changed中找到修改后的数据
if key in self.__changed:
return self.__changed[key]
# 若key在self.__delete中,说明元素已被删除
if key in self.__delete:
return None
#否则根据计算规则返回序列元素
three = key // (26*26)
two = (key-three*26*26) // 26
one = key % 26
return chr(65+three)+chr(65+two)+chr(65+one)
#根据索引修改序列中元素
def __setitem__(self, key, value):
check_key(key)
#将修改的元素以key-value对的形式保存在__changed中
self.__changed[key]=value
#根据索引删除序列中的元素
def __delitem__(self, key):
check_key(key)
#若__delete列表中没有包含被删除的key,则添加被删除的kwy
if key not in self.__delete:
self.__delete.append(key)
#__changed中包含被删除的key,则删除它
if key in self.__changed:
del self.__changed[key]
#创建序列
sq=StringSeq()
#获取序列长度
print(len(sq)) #17576
print(sq[26*26]) #BAA
print(sq[1]) #AAB
#修改sq[1]元素
sq[1]='hhh'
print(sq[1]) #hhh
#删除sq[1]
del sq[1]
print(sq[1]) #None
2. 自定义迭代器
可迭代对象(元组、列表、字典等)都属于迭代器
若需要自行实现迭代器,通常只要实现如下两个方法:
- __iter__(self):该方法返回一个迭代器,迭代器必须包含一个next()方法,该方法返回迭代器的下一个元素
- def __reversed__(self):为内建的reversed()反转函数提供支持(可选)
#定义斐波那契数列迭代器
class Fibs:
def __init__(self,len):
self.first=0
self.sec=1
self.__len=len
#定义迭代器所需的__next__方法
def __next__(self):
if self.__len==0:
# 若__len__属性为0,结束迭代
raise StopIteration
#完成数列计算
self.first,self.sec=self.sec,self.first+self.sec
#数列长度减一
self.__len -= 1
return self.first
#定义__iter__方法,该方法返回迭代器
def __iter__(self):
return self
#创建Fibs对象
fibs=Fibs(10)
#获取下一个元素
print(next(fibs)) # 1
#遍历迭代器
for i in fibs:
print(i,end=' ') # 1 2 3 5 8 13 21 34 55
3. 扩展列表、元组和字典
有时标准的列表、元组和字典无法满足业务要求,我们可以重新自定义一个全新的,也可以选择扩展它们——继承系统已有的列表、元组和字典,然后重写或新增方法。
class ValueDict(dict):
def __init__(self,*args,**kwargs):
#调用父类构造方法
super().__init__(*args,**kwargs)
#新增getkeys方法
def getkeys(self,val):
result = []
for key,value in self.items():
if value == val:
result.append(key)
return result
my_dict=ValueDict(Chinese=92,Math=93,English=92)
#获取92对应所有key
print(my_dict.getkeys(92)) #['Chinese', 'English']
my_dict['History']=92
print(my_dict.getkeys(92)) #['Chinese', 'English', 'History']
四、 生成器
生成器是一个特殊的迭代器,它保存的是算法,每次调用next()或send()就计算出下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration。
生成器有两种类型,一种是生成器表达式(又称为生成器推导),一种是生成器函数。
生成器表达式是通过一个Python表达式语句去计算一系列数据,但生成器定义的时候数据并没有生成,而是返回一个对象,这个对象只有在需要的时候才根据表达式计算当前需要返回的数据。
生成器函数是一种语句中包含yield关键词的特殊的函数,它本身是一个迭代器,外部需要访问该迭代器数据的代码通过调用next函数(或迭代器的next方法)或send方法,触发函数执行计算并通过yield返回一个计算结果数据,返回数据后该函数立即停止执行,函数状态会保存在本地变量中,直到外部下次调用再激活,从上次停止执行部分开始执行。
1. 创建生成器
有2种方法:
- 一是前面文章记录过的使用for循环创建生成器
- 二是定义一个包含yield语句的函数,再调用该函数创建生成器
#生成val范围内差值递增的数列
def test(val,step):
cur=0
for i in range(val):
cur += i*step
yield cur
t=test(10,2)
print(next(t))
print(next(t))
#可使用函数将生成器能生成的所有值转换为列表或元组
t2=test(10,1)
print(list(t2)) # [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
t2=test(10,1)
print(tuple(t2)) # (0, 1, 3, 6, 10, 15, 21, 28, 36, 45)
yield语句作用如下:
- 每次返回一个值,类似return语句
- 程序每执行到yield语句时就会暂停,冻结执行
- 程序被yield语句冻结后,当程序再调用next函数获取生成器下一个值时,程序才会向下执行
调用包含yield语句的函数并不会立即执行,只是返回一个生成器。只有当程序通过next()函数调用或遍历生成器时,函数才会真正执行。
2. 生成器的方法
send()
send()方法可实现生成器与外部程序动态交换数据,它与next功能类似,都用于获取生成的下一个值,不同之处在于它可以接收一个参数(外部程序可向生成器发送数据)
程序可通过yield表达式来获取send()方法所发送的值
只有等生成器被冻结后,外部程序才能使用send方法向生成器发送数据,所以获取生成器第一次生成的值需要通过next()方法,如果一定要用send(),只能传入None参数
#若使用send指定参数则求指定参数平方值,若不指定则依次求生成器下个值的平方
def square_gen(val):
i=0
out_val=None
# 使用yield语句生成值,使用out_val接收send方法发送的参数值
while True:
out_val = (yield out_val**2) if out_val is not None else (yield i**2)
if out_val is not None:
print("====%d" % out_val)
i+=1
sg=square_gen(5)
#第一次调用,只能传入None作参数
print(sg.send(None)) #未指定,相当于求0的平方;运行时i==0,运行完后i==1
print(next(sg)) #next函数取生成器下个值的平方;运行时i==1,运行完后i==2
#调用send方法获取生成器下一个值,参数9会被发送给生成器
print(sg.send(9)) #指定9,求9的平方;运行时i==2,运行完后i==3
print(next(sg)) #next函数取生成器下个值的平方;运行时i==3(即求3的平方),运行完后i==4
#输出如下
0
1
====9
81
9
生成器还有两个常用方法
- close():停止生成器
- throw():在生成器内部(yield语句内)引发一个异常
最后,总结一下:
- 什么是可迭代对象?可迭代对象实现了能返回迭代器的iter或getitem方法,且其参数是从零开始的索引。
- 什么是迭代器?迭代器实现了无参数的 next 方法,返回下一个元素,如果没有元素了,那么抛出 StopIteration 异常;并且实现iter方法,返回迭代器本身。
- 什么是生成器?生成器是带有 yield 关键字的函数。调用生成器函数时,会返回一个生成器对象。
- 什么是生成器表达式? 生成器表达式是创建生成器的简洁句法,这样无需先定义函数再调用。
参考
https://www.jianshu.com/p/891701629974