其实我们一直在跟特殊方法函数打交道,除了生成对象时暗地里调用__init__(),还有做3+4,’鸟宿池边树,’ + ’僧敲月下门’字符串连接,print(‘海上明月共潮生’),len(‘对酒当歌,人生几何’)等操作时,都在不知不觉中调用了__add__(),__str__(),__len__()这些魔力方法函数(magic methods)。
魔力方法函数(magic methods)的函数名是固定的;格式也是固定的:函数名前后都要添加两个下划线,比如我们熟悉的对象初始化函数__init__(),读成dunder init dunder,所以这种方法函数又叫作dunder methods。
介绍几个比较常用的魔力方法函数:
1.__str__()与__repr__():
先试试直接打印对象empLeader1: print(empLeader1)
得到的输出是:<__main__.Leader object at 0x10a7896a0>,不是一个很友好的输出。通过在Leader类内部定义一个__repr__()方法函数,可以定义print(Leader类对象)的输出内容,repr是represent代表的前四个字母:
……
class Leader(Employee):
……
def __repr__(self):
return '使用魔力方法函数__repr__(): {}'.format(self.fullname)
……
print(empLeader1) (1)
# 输出是:使用魔力方法函数__repr__: Julius Caesar
(1)相当于:print(empLeader1.__repr__())
__str__()也可用来定义print的输出,如果把它加入到上面的代码段里:
……
class Leader(Employee):
……
def __repr__(self):
return '使用魔力方法函数__repr__(): {}'.format(self.fullname)
def __str__(self):
return '使用魔力方法函数__str__: {}'.format(self.fullname)
……
print(empLeader1) (1)
# 输出是:使用魔力方法函数__str__: Julius Caesar
由(1)的输出结果可以看出同时存在__str__()和__repr__()时,print()执行时暗暗调用的是__str__(),而不是__repr__()。
(1)相当于print(empLeader1.__str__())。
为什么不干脆用__str__()替代__repr__()而是要两者并存呢?
因为两者的“目标客户”不同。__str__()的目标客户是用户,__repr__()的目标客户则是开发者。
在下面的例子里,定义一个日期变量a和一个字符串变量b,然后调用__str__()和__repr__(),对比运行结果:
from datetime import date
a = date.today() # a是日期类型的一个实例,赋值为今日的日期
b = str(a) # 将a转成字符串后赋给b,b是字符串类型的一个实例
print(a.__str__()) # 输出:2019-01-01
print(b.__str__()) # 输出:2019-01-01
# 可见调用日期对象a和字符串对象b的__str__()输出结果是一样的,只看输出结果是看不出a和b其实是不同类的实例的
print(a.__repr__()) # 输出:'2019-01-01',看出是字符串类型
print(b.__repr__()) # 输出:datetime.date(2018, 9, 17),看出是日期类型
# 从a和b的__repr__()可以看出a和b是不同类的对象,所以说__repr__()的输出结果是给开发者看的
print(a) # 输出:2019-01-01
print(b) # 输出:2019-01-01
# 这两条语句同print(a.__str__())和print(b.__str__())
2.__len__()
len(‘对酒当歌,人生几何’)等同于‘对酒当歌,人生几何’.__len__()。在类Leader里也定义一个__len__()方法函数,len(Leader对象)返回Leader对象下属的个数:
……
class Leader(Employee)
……
def __len__(self):
if self.subordinates is None: # 下属列表为None,则返回0
return 0
else: # 否则返回列表长度
return len(self.subordinates)
……
print(empLeader1.__len__()) # 返回对象empLeader1下属的个数,假如是3
print(len(empLeader1)) # 也是返回对象empLeader1下属的个数,也是3
print(len('blue moon')) # 字符串长度,输出9
print('blue moon'.__len__()) # 也是字符串长度,输出也是9
3.__add__()
平时使用的+,无论是数字相加的+还是字符串连接的+,实际上都调用了__add__()。
比如1+2,相当于int.__add__(1, 2);’glory’+’ dream’相当于str.__add__(‘glory’, ‘ dream’)。
在Writer类里加一个方法函数__add__(),使得两个Writer类对象可以直接相加,就设定为相加后返回两个对象的代表作吧:
……
class Writer(Employee):
……
def __add__(self, otherWriter): # 形参otherWriter接收另一个对象
if isinstance(otherWriter, Writer):
# 判断另一个对象是不是Writer类,如果是返回两个Writer类对象的代表作;
return '{} 和 {}'.format(self.masterwork, otherWriter.masterwork)
else: # 如果不是,返回只有两个Writer类才能相加的提示
return '两个Writer类的对象才可以相加'
……
print(empWriter2 + empWriter1)
# 因为+两边的对象都是Writer类,故输出两个对象的代表作:Lord of Rings 和 The adventure of Tom Sawyer
print(empWriter2 + empLeader1)
# 因为empLeader1不是Writer类的对象,故输出:两个Writer类的对象才可以相加
跟魔力方法函数__add__()类似的还有:__eq__ : 是否相等;__ne__ : 是否不相等;__lt__ : 小于;__gt__ : 大于;__le__ : 小于等于;__ge__ : 大于等于;
__sub__ : 相减;__mul__ : 相乘;__div__ : 除法;__mod__ : 取模。
4.__slot__
__slot__不是方法函数,而是一个特殊变量,用于限制类属性的添加。
用Employee类做例子,目前是可以在类外给生成的对象直接添加一个属于自己的属性的,比如给employee1对象添加一个nickname的属性:employee1.nickname = ‘Pikachu’ 或setattr(employee1, ‘nickname’, ‘Pikachu’)都可以实现。
通过输出dir(employee1)或employee1.__dict__或help(employee1)的内容可以看到nickname作为属性位列其中。
可如果不允许在类外给对象添加属性就需要用到__slot__变量了,比如现在限定Employee类除了__init__()内定义的几个类外,不允许在类外添加其它类:
class Employee(object):
__slot__ = ('__fname', '__surname', '__salary')
# 元组里放着允许添加的属性
……
employ1.nickname = ‘Pikachu’
# 运行出错,错误提示是:AttributeError: 'Employee' object has no attribute 'nickname',提示没有属性nickname
print(employ1.fullname)
# 正常输出,类内部定义的已有属性的基础上生成的属性不受影响
即使在类内部__init__()方法函数内部定义的属性也受__slot__的限制:
class Employee(object):
__slot__ = ('__fname', '__surname')
……
print(employ1.salary)
# 运行出错,错误提示是:AttributeError: 'Employee' object has no attribute '_Employee__salary',提示没有属性salary
除非把允许添加的属性(如’__salary’和’nickname’加入到__slot__变量内:
class Employee(object):
__slot__ = ('__fname', '__surname',’nickname’,’__salary‘)
……
print(employee1.salary) # 运行正常
setattr(‘employ1’,’nickname’,’Pikachu’)
print(employee1.nickname) # 运行正常
添加成’salary’会出错:
class Employee(object):
__slot__ = ('__fname', '__surname',‘salary’)
……
运行直接出错,错误提示:ValueError: 'salary' in __slots__ conflicts with class variable,提示salary与类已有变量冲突
即将推出的Python ABC教程对PythonABC视频内容进行了梳理,修正了发现的错误、对代码做了些许优化、替换掉视频中的英文注释、替换掉国内不能访问的资源……敬请关注,谢谢
欢迎访问PythonABC网站:pythonabc.org