Python 第八章 魔法方法
1.常见特殊方法
1.1 重写__repr__方法
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
# 创建一个item对象,赋值给im变量
im = Item('鼠标',29.8)
print(im)
# <__main__.Item object at 0x0000023F3C30A5B0>
print函数只能在控制台打印字符串,而Item实例是内存中的一个对象,实际上该方法输出的是Item对象的_repr_()方法的返回值。也就是说下面两行代码一样:
print(im)
print(im.__repr__())
程序需要将任何对象与字符串进行连接时,都可先调用该方法将对象转换成字符串,然后将两个字符串拼接在一起。
__repr__是一个自我描述的方法,通常用于实现:当程序员打印该对象时,系统会输出该对象的自我描述信息,用来告诉外界该对象的状态信息。
该方法总是返回该对象实现类的“类名+object at +内存地址”值,这个返回值并不能真正实现自我描述的功能,因此用户需要自定义类能实现自我描述功能,重写_repr_()方法:
class Apple:
def __init__(self,color,weight):
self.color = color
self.weight = weight
def __repr__(self):
return "Apple[color=" + self.color +\
",weight=" + str(self.weight) + "]"
a = Apple("红色",5.68)
print(a)
Apple[color=红色,weight=5.68]
可以看出,通过重写Apple类的__repr__方法打出自我描述信息。
1.2析构方法:_del_
与__init__()对应的是____del__()方法,前者用于初始化Python对象,而__del__用于销毁python对象。任何python对象将被系统回收时,系统会自动调用del方法。
不要以为对一个变量执行del操作,该变量所引用的对象就会被回收,只有当对象的引用计数变成0才会被回收。因此,如果一个对象有多个变量引用它,那么del其中一个变量是不会回收该对象的。
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
def __del__(self):
print('删除对象')
im = Item('鼠标',22.8)
x = im
del im
print('---------')
#---------
删除对象
im 和x两个变量指向Item对象,删除了im对象还有x变量引用Item对象,所以不会回收Item对象。
如果将8行注释,删除im后无变量引用Item对象,会立即回收该对象,无需等到程序结束。
1.3__dir__方法
用于列出该对象内部的所有属性名,返回序列。
当程序对某个对象执行dir(object)函数时,实际上就是将该对象的__dir__方法返回值进行排序,然后包装成列表。
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
@staticmethod
def info():
pass
im = Item('鼠标',23.3)
print(im.__dir__()) # 返回所有属性包括方法组成的列表
print(dir(im)) # 返回所有属性包括方法排序之后组成的列表
1.4__dict__属性
用于查看对象内部存储的所有属性名和属性值组成的字典。
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
im = Item('鼠标',22.3)
print(im.__dict__)
print(im.__dict__['name'])
print(im.__dict__['price'])
im.__dict__['name'] ='键盘'
im.__dict__['price'] ='44.5'
print(im.name)
print(im.price)
#{'name': '鼠标', 'price': 22.3}
#鼠标
#22.3
#键盘
#44.5
1.5_getattr_、__setattr__等
当程序操作(包括访问、设置、删除)对象的属性时,python系统同样会执行该对象特定的方法。有以下方法:
- _getattribute_(self,name):当程序访问对象的name属性时被自动调用。
- _getattr_(self,name):当程序访问对象的name属性且该属性不存在时被自动调用。
- _setattr_(self,name,value):当程序对对象的name属性赋值时被自动调用。
- _delattr_(self,name):当程序删除对象的name属性时被自动调用。
通过重写上面的方法,可以为python类合成属性——当属性不存在时,程序会委托给上面的后三个方法实现:
class Rectangle:
def __init__(self,width,height):
self.width = width
self.height = height
def __setattr__(self, name, value):
print('----设置%s属性----' % name)
if name == 'size':
self.width,self.height = value
else:
self.__dict__[name] = value
def __getattr__(self, name):
print('----读取%s属性----' % name)
if name == 'size':
return self.width,self.height
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)
#
----设置width属性----
----设置height属性----
----读取size属性----
(3, 4)
----设置size属性----
----设置width属性----
----设置height属性----
6
----删除size属性----
----读取size属性----
(0, 0)
上面程序实现了_setattr_()和_getattr()方法,并在实现这两个方法时对size属性进行了判断,如果程序正在获取size属性,_getattr()方法将返回self.width和self.height组成的元组,如果获取其他属性直接报错,如果程序正在设置size属性,则转换为对self.width和self.height属性的赋值,如果是对其他属性赋值,则通过对象的__dict__属性赋值。
- 对于_getattr_()方法:只处理程序访问指定属性且该属性不存在的情形。比如程序访问width和height属性,Rectangle有就不会触发这个方法。所以重写该方法只需处理我们需要合成的属性(比如size),加入程序试图访问其他不存在的属性,当然直接引发异常即可。
- 对于_setattr_()方法:只要程序试图对指定属性赋值总会触发该方法,所以重写该方法既要处理对size属性赋值的情形,也要处理对width、height属性赋值的情形。尤其是处理对width、height属性赋值时,千万不要在__setattr__方法中再对他们赋值。因为会再次出发该方法进入死循环。
如果程序需要在读取、设置属性之前进行某种拦截处理(比如检查数据是否合法之类),也可通过重写__setattr__或__getattribute__方法来实现。
class User:
def __init__(self, name, age):
self.name = name
self.age = age
# 重写__setattr__()方法对设置的属性值进行检查
def __setattr__(self, name, value):
# 如果正在设置name属性
if name == 'name':
if 2 < len(value) <= 8:
self.__dict__['name'] = value
else:
raise ValueError('name的长度必须在2~8之间')
elif name == '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.name = 'fk' # 引发异常
u.age = 2 # 引发异常
2.与反射相关的属性和方法
2.1动态操作属性
- hasattr(obj,name):检查obj对象是否包含名为name的属性或方法。
- getattr(object,name[,default]):获取object对象中名为name的属性的属性值。
- 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():
print("一条简单的评论,内容是%s" % self.detail)
c = Comment('python讲义不错',20)
# 判断是否包含指定的属性或方法
print(hasattr(c,'detail')) # True
print(hasattr(c,'view_times')) # True
print(hasattr(c,'info')) # True
# 获取指定属性的属性值
print(getattr(c,'detail')) # python讲义不错
print(getattr(c,'view_times')) # 20
# 获取info属性的值,由于info是方法,因此输出绑定方法(bound method):Comment.info
print(getattr(c,'info','默认值'))#<bound method Comment.info of <__main__.Comment object at 0x000002B1038BA5B0>>
# 为指定属性设置属性值
setattr(c,'detail','天气不错')
setattr(c,'view_times',32)
print(c.detail) # 天气不错
print(c.view_times) # 32
# 设置不存在的属性,即为对象添加属性
setattr(c,'test','新增的属性')
print(c.test) # 新增的属性
实际上,setattr函数还可以对方法进行设置,在使用时新设置的方法是未绑定方法。
def bar():
print('一个bar方法')
# 将c的info方法设为bar函数
setattr(c,'info',bar)
c.info() # 一个bar方法
定义了一个bar函数,在该函数中不能定义self参数,否则需要显示的为参数传入参数中,系统不会自动为该参数绑定参数值。接着调用info函数实际是调用bar方法;还可以将info方法变成一个属性,而不是方法。
2.2__call__属性
hasattr函数可以判断指定属性或方法是否存在,要进一步判断是属性还是方法,要判断它是否可调用。程序可以通过判断该属性或方法是否包含__call__属性来确定它是否可被调用。
class User:
def __init__(self,name,passwd):
self.name = name
self.passwd = passwd
def ValidLogin(self):
print('验证%s的登陆' % self.name)
u = User('crazyit','leegang')
print(hasattr(u.name,'__call__')) # False
print(hasattr(u.passwd,'__call__')) # False
print(hasattr(u.ValidLogin,'__call__')) # True
事实上,一个函数(甚至类)之所以能执行,关键就在于__call__()方法。实际上x(arg1,arg2....)只是x._call_(arg1,arg2....)的快捷写法。
因此可以为自定义类添加该方法,从而使该类的实例也变成可调用的:
class Role:
def __init__(self,name):
self.name = name
def __call__(self, *args, **kwargs):
print('执行Role对象')
r = Role('管理员')
# 直接调用Role对象,就是调用__call__方法
r()
执行Role对象
最后一行用调用函数的语法来调用对象,看上去错误,但该Role类提供了__call__方法,因此调用对象的本质就是执行该对象的call方法。
3.与序列相关的特殊方法
3.1序列相关方法
- _len_(self):该方法的返回值决定序列中元素的个数。
- _getitem_(self,key):获取指定索引对应的元素。key应该是整数或slice对象,否则报错。
- _contains_(self,item):判断序列是否包含指定元素。
- _setitem_(self,key,value):设置指定索引对应的元素。key应该是整数或slice对象,否则报错。
- _delitem_(self,key):删除指定索引对应的元素。
下面程序会实现一个字符串序列,在该字符串序列默认每个字符串的长度都是3,该序列的元素按AAA、AAB、AAC。。这种格式排列。
def check_key (key):
'''
该函数将会负责检查序列的索引,该索引必须是整数值,否则引发TypeError
且程序要求索引必须为非负整数,否则引发IndexError
'''
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, key):
'''
根据索引获取序列中元素
'''
check_key(key)
# 如果在self.__changed中找到已经修改后的数据
if key in self.__changed :
return self.__changed[key]
# 如果key在self.__deleted中,说明该元素已被删除
if key in self.__deleted :
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)
# 如果__deleted列表中没有包含被删除key,添加被删除的key
if key not in self.__deleted : self.__deleted.append(key)
# 如果__changed中包含被删除key,删除它
if key in self.__changed : del self.__changed[key]
# 创建序列
sq = StringSeq()
# 获取序列的长度,实际上就是返回__len__()方法的返回值
print(len(sq))
print(sq[26*26])
# 打印没修改之后的sq[1]
print(sq[1]) # 'AAB'
# 修改sq[1]元素
sq[1] = 'fkit'
# 打印修改之后的sq[1]
print(sq[1]) # 'fkit'
# 删除sq[1]
del sq[1]
print(sq[1]) # None
# 再次对sq[1]赋值
sq[1] = 'crazyit'
print(sq[1]) # crazyit
3.2实现迭代器
- __iter__(self):该方法返回一个迭代器(iterator),迭代器必须包含一个__next__()方法,该方法返回迭代器的下一个元素。
- _reversed_(self):该方法主要为内建的reversed()反转函数提供支持,调用reversed函数对指定迭代器执行反转时,实际上由该方法实现。
下面程序将会定义一个代表斐波那契数列(数列的元素等于前两个元素之和)的迭代器。
class Fibs:
def __init__(self,len):
self.first = 0
self.sec = 1
self.__len =len
def __next__(self):
# __len__属性为0就结束迭代。
if self.__len == 0:
raise StopIteration
# 完成数列计算
self.first,self.sec = self.sec,self.first + self.sec
# 数列长度减1
self.__len -=1
return self.first
# 定义iter方法,返回迭代器
def __iter__(self):
return self
fibs = Fibs(10)
print(next(fibs))
for el in fibs:
print(el,end='')
Fibs类,该类实现了__iter__方法,该方法返回self,因此他要求该类必须提供__next__方法,该方法会返回数列的下一个值。程序使用__len属性控制数列的剩余长度,当为0时会停止遍历。
可以使用iter()函数将列表、元组等转换成迭代器:
my_iter = iter([2,'fkit',4])
print(my_iter.__next__()) # 2
print(my_iter.__next__()) #fkit
3.3生成器
生成器和迭代器功能相似,也会提供_next_()方法,这意味着程序同样可以调用next()函数获取生成器的下一个值,也可使用for循环来遍历生成器。
生成器和迭代器的区别在于:迭代器通常先定义一个迭代器类,然后通过创建实例来创建迭代器;生成器则是先定义一个包含yield语句的函数,然后通过调用该函数来创建生成器。
创建生成器:
- 定义一个包含yield语句的函数。
- 调用第一步创建的函数得到生成器。
def test(val,step):
print('开始执行')
cur = 0
for i in range(val):
cur += i*step
yield cur
yield cur 语句的作用有
- 每次返回一个值,有点类似于return
- 冻结执行,程序每次执行到yield语句就会被暂停
在程序被冻结后,当程序再次调用next函数获取生成器的下一个值时,程序才会继续向下执行。
调用包含yield语句的函数并不会立即执行,只是返回一个生成器,只有程序通过next函数调用生成器或遍历生成器时才会真正执行。
程序可使用for循环来遍历生成器,相当于不断地使用next函数获取生成器的下一个值:
for ele in t:
print(ele,end = ' ')
生成器的方法:
当生成器运行起来后还可以为生成器提供值,让生成器与外部程序动态交换程序。
为了实现生成器与外部程序动态的交换数据,需要借助于生成器的send方法,该方法的功能与前面的next函数功能非常相似,都用于获取生成器的下一个值并将生成器冻结在yield语句处;但send方法可以接受一个参数,该参数值会被发送给生成器函数。在生成器函数内部,程序可通过yield表达式获取send方法所发送的值,这意味着此时程序应该使用一个变量来接收yield语句的值。如果程序依然使用next函数来获取生成器产生的下一个值,那么yield语句返回None。
也就是:外部程序通过send方法发送数据;生成器函数使用yield语句接收数据。
只有等到生成器被冻结之后,外部程序才能使用send方法向生成器发送数据。获取生成器第一次所生成的值,应该使用next函数,如果程序非要使用send方法获取生成器第一次所生成的值,也不能向生成器发送数据,只能为该方法传入None参数。
下面程序是向生成器发送数据。会依次生成每个整数的平方值,但外部程序可以向生成器发送数据,当生成器接受到外部数据之后会生成外部数据的平方值。
def square_gen(val):
i = 0
out_val = None
while True:
# 使用yield语句生成值,使用out_val接收send()方法发送的参数值
out_val = (yield out_val **2) if out_val is not None else (yield i**2)
# 如果程序使用send方法获取下一个值,out_val获取send方法的参数值
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('----------')
# 调用send方法获取生成器的下一个值,参数9会被发送给生成器。
print(sg.send(9))
print(next(sg))
#
0
1
----------
===9
81
9
out_val变量用于接收生成器send方法所发送的值。
上面程序第一次使用生成器的send方法获取生成器的下一个值,因此只能为send方法传入None作为参数。程序执行到第六行,此时out_val为None,因此程序执行yield i **2(生成器返回0),程序被冻结,还未对out_val变量赋值,所以第一次是0。
接下来调用next(sg)获取生成器的下一个值,程序从冻结处(对out_val赋值)向下执行。由于此处调用next()函数获取生成器的下一个值,因此out_val被赋值为None,所以程序执行yield**2(生成器返回1),程序再次被冻结。
接下来程序调用sg.send(9)获取生成器的下一个值,程序从冻结处(对out_val赋值)向下执行。由于此处调用send(9)获取下一个生成器的值,因此out_val被赋值为9,所以程序执行yield i**2,生成器返回81,程序再次被冻结。
再次调用next(sg)获取生成器的下一个值,程序从冻结处向下执行,由于此时调用了next函数获取生成器的下一个值,因此out_val被赋值为None,执行yield i**2,此时i递增到3,生成器返回9,程序再次被冻结。
此外,生成器还提供了如下两个常用方法:
close():用于停止生成器。
throw():用于在生成器内部(yield语句内)引发一个异常。
4.与数值运算符相关的特殊方法
python允许为自定义类提供特殊方法,这样就可以让自定义类的对象也支持各种运算符的运算了。
一旦为自定义类提供了特殊方法,程序就可以直接用运算符来操作该类的实例。
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
# 定义setSize()函数
def setSize (self , size):
self.width, self.height = size
# 定义getSize()函数
def getSize (self):
return self.width, self.height
# 使用property定义属性
size = property(getSize, setSize)
# 定义__add__方法,该对象可执行+运算
def __add__(self, other):
# 要求参与+运算的另一个运算数必须是Rectangle
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)
# 对两个Rectangle执行加法运算
r = r1 + r2
print(r) # Rectangle(width=7, height=9)
5.与比较运算符相关的特殊方法
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
# 定义setSize()函数
def setSize (self , size):
self.width, self.height = size
# 定义getSize()函数
def getSize (self):
return self.width, self.height
# 使用property定义属性
size = property(getSize, setSize)
# 定义__gt__方法,该对象可支持>和<比较
def __gt__(self, other):
# 要求参与>运算的另一个运算数必须是Rectangle
if not isinstance(other, Rectangle):
raise TypeError('>运算要求目标是Rectangle')
return True if self.width * self.height > other.width * other.height else False
# 定义__eq__方法,该对象可支持==和!=比较
def __eq__(self, other):
# 要求参与==运算的另一个运算数必须是Rectangle
if not isinstance(other, Rectangle):
raise TypeError('==运算要求目标是Rectangle')
return True if self.width * self.height == other.width * other.height else False
# 定义__ge__方法,该对象可支持>=和<=比较
def __ge__(self, other):
# 要求参与>=运算的另一个运算数必须是Rectangle
if not isinstance(other, Rectangle):
raise TypeError('>=运算要求目标是Rectangle')
return True if self.width * self.height >= other.width * other.height else False
def __repr__(self):
return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r1 = Rectangle(4, 5)
r2 = Rectangle(3, 4)
print(r1 > r2) # True
print(r1 >= r2) # True
print(r1 < r2) # False
print(r1 <= r2) # False
print(r1 == r2) # False
print(r1 != r2) # True
print('------------------')
r3 = Rectangle(2, 6)
print(r2 >= r3) # True
print(r2 > r3) # False
print(r2 <= r3) # True
print(r2 < r3) # False
print(r2 == r3) # True
print(r2 != r3) # False
6.与单目运算符相关的特殊方法
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
# 定义setSize()函数
def setSize (self , size):
self.width, self.height = size
# 定义getSize()函数
def getSize (self):
return self.width, self.height
# 使用property定义属性
size = property(getSize, setSize)
# 定义__neg__方法,该对象可执行求负(-)运算
def __neg__(self):
self.width, self.height = self.height, self.width
def __repr__(self):
return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4, 5)
# 对Rectangle执行求负运算
-r
print(r) # Rectangle(width=5, height=4)
7.与类型转换相关的特殊方法
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
# 定义setSize()函数
def setSize (self , size):
self.width, self.height = size
# 定义getSize()函数
def getSize (self):
return self.width, self.height
# 使用property定义属性
size = property(getSize, setSize)
# 定义__int__方法,程序可调用int()函数将该对象转成整数
def __int__(self):
return int(self.width * self.height)
def __repr__(self):
return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4, 5)
print(int(r)) # 20
8.与常见的内建函数相关的特殊方法
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
# 定义setSize()函数
def setSize (self , size):
self.width, self.height = size
# 定义getSize()函数
def getSize (self):
return self.width, self.height
# 使用property定义属性
size = property(getSize, setSize)
# 定义__round__方法,程序可调用round()函数将该对象执行四舍五入取整
def __round__(self, ndigits=0):
self.width, self.height = round(self.width, ndigits), round(self.height, ndigits)
return self
def __repr__(self):
return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4.13, 5.56)
# 对Rectangle对象执行四舍五入取整
result = round(r, 1)
print(r) # Rectangle(width=4.1, height=5.6)
print(result) # Rectangle(width=4.1, height=5.6)