疯狂python讲义学习日志07——Python类的特殊方法
引言
在python类中有些方法名、属性名的前后都添加了双下画线,这种方法、属性通常都属于python的特殊方法和特殊属性,开发者可以通过重写或者直接调用这些方法实现特殊功能。比如__ini__可以重写类中的__ini__方法实现自己的初始化逻辑。
1 常见的特殊方法
1.1 重写__repr__方法
当需要将任何对象与字符串进行连接时,都可调用__repr__方法将对象转换成字符串(用于对象的自我描述),然后将两个字符串连接在一起。
每个python对象都具有__repr__方法,python提供的该方法总是返回该对象实现类的“类名+object at+内存地址”值,如果用户需要自定义类能实现自我描述的功能,就必须重写__repr__方法。
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
def __repr__(self):
return "Item[name=" +self.name+\
", pric =" + str(self.price) +']'
a = Item("abc",8.00)
print(a)
1.2 析构方法__del__
__del__方法用于销毁python对象,python对象要被系统回收时,都会调用该对象的__del__方法。只有当对象的引用计数为0时该对象才会被回收。
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
def __repr__(self):
return "Item[name=" +self.name+\
", pric =" + str(self.price) +']'
def __del__(self):
print("del删除对象")
a = Item("abc",8.00)
x = a #因为此处引用了所以程序结束才会执行析构
del a
print("----------------")
1.3__dir__方法
对象的__dir__方法用于列出该对象内部的所有属性(包括方法)名,该方法将会返回所有属性(方法)名的序列。当程序对某个对象执行dir(object)时,实际上就是将该对象的__dir__方法返回值进行排序,然后包装成列表。
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
def __repr__(self):
return "Item[name=" +self.name+\
", pric =" + str(self.price) +']'
def __del__(self):
print("del删除对象")
def info(self):
pass
a = Item("abc",8.00)
print(a.__dir__()) #返回所有属性(包含方法)组成的列表
print(dir(a)) #返回所有属性(包含方法)排序后组成的列表
1.4 __dic__属性
__dic__属性用于查看对象内部存储的所有属性名和属性值组成的字典,既可以使用__dic__属性查看对象所有的内部状态,也可以通过字典语法来访问或修改指定属性的值。
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
def __repr__(self):
return "Item[name=" +self.name+\
", pric =" + str(self.price) +']'
def __del__(self):
print("del删除对象")
def info(self):
pass
a = Item("abc",8.00)
print(a.__dict__)
print(a.__dict__['name'])
print(a.__dict__['price'])
a.__dict__['name'] = "键盘"
a.__dict__['price'] = 56.9
print(a.name)
print(a.price)
1.5__getattr__、__setattr__等
当程序操作(包括访问、设置、删除)对象的属性时,python系统会执行特定的方法:
1__getattribute__(self,name):当程序访问对象的name属性时被自动调用
2__getattr__(self,name):当程序访问对象的name属性,且该对象不存在时被自动调用
3__setattr__(self,name,value):当程序对对象的name属性赋值时被自动调用
4__delattr__(self,name):当程序删除对象的name属性时被自动挡调用
class Rectangle:
def __init__(self,width,height):
self.width = width
self.heigth = height
def __setattr__(self,name,value):
print('---设置%s的属性---' % name)
if name == 'size':
self.width,self.heigth = value
else:
self.__dict__[name] = value
def __getattr__(self, name):
print('---读取%s的属性---' % name)
if name == 'size':
return self.width,self.heigth
else:
raise AttributeError
def __delattr__(self, name):
print('---删除%s的属性---' %name)
if name == 'size':
self.__dict__['width'] = 0
self.__dict__['height'] = 0
rect = Rectangle(3,4)
print(rect.size)
rect.size = 6,8
print(rect.width)
del rect.size
print(rect.size)
如果程序需要在读取、设置属性之前进行某种拦截处理,也可以通过重写__setattr__()或__getattribute__()方法实现。
class User:
def __init__(self,name,age):
self.name = name
self.age = age
def __setattr__(self, key, value):
if key == 'name':
if 2<len(value) <=8:
self.__dict__['name'] = value
else:
raise ValueError('name的长度必须在2-8之间')
elif key == 'age':
if 10 < value < 60:
self.__dict__['age'] = value
else:
raise ValueError('age值必须在10-60之间')
u = User('fkit',24)
print(u.name)
print(u.age)
u.age = 2
2 与反射相关的属性和方法
这个概念听着还是很高级的(c++没有类似的操作的),其实就是如果在程序运行过程中要动态判断是否包含某个属性(包括方法),甚至动态设置某个属性值,则可通过Python的反射支持来实现。学好这一小节,应用得当在有些时候可能会有很巧妙的应用。
2.1 动态操作属性
检查对象是否包含某些属性相关的函数如下:
1)hasattr(obj,name):检查obj对象是否包含名为name的属性或方法。
2) getattr(obj,name[,default]):获取obj对象中名name的属性的属性值。
3) setattr(obj,name,value,/):将obj对象的name属性设置为value。
示例代码如下:
class Comment:
def __init__(self,detail,view_times):
self.detail = detail
self.view_times = view_times
def info(self):
print("一条简单的评论,内容是%s" % self.detail)
c = Comment("Python还凑合",20)
print(hasattr(c,'detail'))
print(getattr(c,'detail'))
print(getattr(c,'info','默认值'))
setattr(c,'detail','天气不错')
print(c.detail)
def bar():
print('hello world')
setattr(c,'info',bar)#setattr还可以对方法进行设置,使用setattr()方法重新设置对象的方法时,新设置的方法就是未绑定方法
c.info()
2.2 __call__属性
程序可通过判断该属性是否包含__call__属性来确定它是否可以调用。
class User:
def __init__(self,name,passwd):
self.name = name
self.passwd = passwd
def ValidLogin(self):
print('验证%s的登陆' % self.name)
u = User('crazyit','xiaoming')
print(hasattr(u.name,'__call__'))
print(hasattr(u.ValidLogin,'__call__'))
实际上一个函数之所以被执行关键在于__call__()方法,甚至可以自定义类添加__call__方法,从而使得该类的实例也可变成可调用的。
class Role:
def __init__(self,name):
self.name = name
def __call__(self):
print('执行Role对象')
r = Role('管理员')
r()
3 与序列相关的特殊方法
3.1 序列相关方法
与序列有关的特殊方法有如下几个:
1__len__(self):返回序列中元素的个数
2__getitem__(self,key):该方法获取指定索引对应的元素。该方法的Key应该是整数值或slice对象(说明见下图),否则会引发keyError异常
3__contains__(self,item):该方法判断序列是否包含指定元素
4__setitem__(self,key,value):该方法设置指定索引对应的元素。key也要是整数或slice对象
5__delitem__(self,key):删除指定索引的元素
如果程序要实现只能获取序列中的元素,不能修改,只要实现上面前3个方法就可以,如果要实现可变序列那么5个方法都要实现。
下图是python chr()函数的使用方法
def check_key(key):
if not isinstance(key,int):raise TypeError('索引值必须是整数')
if key < 0: raise IndexError('索引值必须是非负整数')
if key >= 26 ** 3: raise IndexError('索引值不能超过%d' % 26**3)
class StringSeq:
def __init__(self):
self.__changed = {} #存储被修改的数据
self.__deleted = [] #保存被删除的数据
def __len__(self):
return 26**3
def __getitem__(self, item):
check_key(item)
if item in self.__changed:
return self.__changed[item]
if item in self.__deleted:
return None
three = item // (26*26)#//向下取整
two = (item - three *26 * 26) // 26
one = item % 26
return chr(65 + three) + chr(65+two) + chr(65+one)
def __setitem__(self, key, value):
check_key(key)
self.__changed[key] = value
def __delitem__(self, key):
check_key(key)
if key not in self.__deleted: self.__deleted.append(key)
if key in self.__changed:del self.__changed[key]
sq = StringSeq()
print(len(sq))
print(sq[26*26])
print(sq[1])
sq[1] = 'fkit'
print(sq[1])
del sq[1]
print(sq[1])
sq[1] = 'crazit'
print(sq[1])
3.2 实现迭代器
1、如果开发者要实现迭代器,只要实现如下两个方法即可:
1)iter(self):该方法返回一个迭代器,迭代器必须包含一个__next__()方法,该方法返回迭代器的下一个元素。
2)__reversed(self):该方法主要为内建的reversed()反转函数提供支持。
class Fibs:
def __init__(self,len):
self.first =0
self.sec = 1
self.__len = len
def __next__(self):
if self.__len == 0:
raise StopIteration
self.first ,self.sec = self.sec , self.sec + self.first
self.__len-=1
return self.first
def __iter__(self):
return self
fibs = Fibs(10)
print(next(fibs))
for el in fibs:
print(el,end=' ')
2、程序可以使用内置的Iter()函数将列表、元组等转换为迭代器。
my_iter = iter([2,3,4])
print(my_iter.__next__())
print(my_iter.__next__())
3.3 扩展列表、元组和字典
如果程序中有需要一个特殊列表、元组或字典类的需求,可以扩展快速实现需求。详细可以参考下面的示例代码:
class ValueDict(dict):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
def getkeys(self,val):
result = []
for key, value in self.items():
if value ==val : result.append(key)
return result
my_dict = ValueDict(语文=90,数学=89,英语=92)
print(my_dict.getkeys(92))
my_dict['c++']=92
print(my_dict.getkeys(92))
4 生成器
生成器和迭代器功能非常相似,它也会提供__next__()方法。区别在于:迭代器通常是先定义一个迭代器类,然后通过实例创建迭代器类;而生成器先定义一个包含yield语句的函数,然后通过调用该函数创建生成器。
4.1 创建生成器
1、创建生成器需要两个步骤:
1)定义一个包含yield语句的函数。
2)调用第一步创建的函数的到生成器。
def test(val,step):
print("-----------函数开始执行------------------")
cur = 0
for i in range(val):
cur += i*step
yield cur
2、yield cur语句的作用有两点:
1)每次返回一个值,类似return
2) 冻结执行,程序每次执行到yield就会被暂停。
3、调用包含yield语句的函数并不会立即执行,它只是返回一个生成器。只有当程序通过next()函数调用生成器遍历生成树时,函数才会真正运行。
def test(val,step):
print("-----------函数开始执行------------------")
cur = 0
for i in range(val):
cur += i*step
yield cur
t = test(10,2)
print("==========================")
print(next(t))
print(next(t))
程序可以使用list()函数将生成器能生成的所有值转换为列表,或者使用tuple()函数将生成器生成的所有值转换为元组。
def test(val,step):
print("-----------函数开始执行------------------")
cur = 0
for i in range(val):
cur += i*step
yield cur
t = test(10,2)
print(list(t))
t = test(10,3)
print(tuple(t))
3、程序可使用for循环来遍历生成器,相当于不断使用next()函数获取下一个值。示例代码如下:
def test(val,step):
print("-----------函数开始执行------------------")
cur = 0
for i in range(val):
cur += i*step
yield cur
t = test(10,2)
print(next(t))
print(next(t))
for ele in t:
print(ele,end = ' ')
4、总的来说使用生成器有以下几个优势:
1)程序按需获取数据:使用生成器产生数据时,不会一次性就将所有的数据生成出来,而是调用next()获取下一个数据时才会生成该数据。
2)当函数需要返回多个数据时,如果不使用生成器,程序就需要使用列表或元组来收集返回的多个值,这些列表元组会带来一定的内存开销。
3)使用生成器的代码更加简洁(姑妄言之)
4.2 生成器的方法
当生成器运行起来之后,开发者还可以为生成器提供值,通过这种方式让生成器与“外部程序”动态的交换数据。
1、python使用send发送数据,使用yiedl接收数据。
只有当生成器冻结之后,外部程序才可以使用send()方法向生成器发送数据。获取生成器第一次生成的值,应该使用next()函数;如果程序非要使用send()方法获取生成器第一次生成的值,只能为该方法传入None参数。
def square_gen(val):
i = 0
out_val = None
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)
#第一次调用send方法获取值只能传入None
print(sg.send(None))
print(next(sg))
print(sg.send(9))
print(next(sg))
5 运算符重载的特殊方法
5.1 与数值运算符相关的特殊方法
开发人员可以为自定义类型提供如下方法:
下面给出使用使用这些方法的示例代码
#加法使用示例代码:
class Rectangle:
def __init__(self,width,height):
self.width = width
self.height = height
def SetSize(self,size):
self.width,self.height = size
def getSize(self):
return self.width,self.height
size = property(getSize,SetSize)
def __add__(self, other):
if not isinstance(other,Rectangle):
raise TypeError('+运算要求目标是Rectangle')
return Rectangle(self.width+other.width,self.height+other.height)
def __repr__(self):
return 'Rectangle(width = %g,height = %g)' % (self.width,self.height)
r1 = Rectangle(4,5)
r2 = Rectangle(3,4)
r = r1 + r2
print(r)