类和对象

其实我们一直在跟特殊方法函数打交道,除了生成对象时暗地里调用__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