1. 多重继承
一个子类可以继承多个父类,这与一些编程语言的规则不通。
如果多个父类中有同名的变量和方法,子类访问的顺序是按照继承时小括号里书写的顺序进行访问的。
可以用issubclass(B, A)方法判断B是否为A的子类。
2. 绑定
类中的方法通过参数self与对象绑定,通过参数cls与类绑定,如果不用self或者cls来访问类中的变量或者方法,会报错找不到变量或者方法,或者达不到预期效果。
class C:x = 100def set_x(self, v):x = vc = C()
c.set_x(520)
上述代码既不会改变对象c的属性x,也不会改变类C的属性x,set_x方法只是在函数内部建立了一个临时变量。
3. 重写与钻石继承
先定义父类:
class C:def __init__(self, x, y):self.x = xself.y =ydef add():return self.x + self.ydef mul():return self.x * self.y
定义子类,并且重写父类的方法,同时调用父类的方法:
class D(C):def __init__(self, x, y, z):C.__init__(self, x, y)self.z = zdef add():return C.add(self) + self.zdef mul():return C.mul(self) * self.z
如果是钻石继承,这种直接通过类名调用类里面方法的方式可能会出现问题。什么是钻石继承?就是有两个类同时继承了同一个父类,然后另外一个类继承了这两个类,继承关系拓扑图类似于一个钻石(菱形):
钻石继承的问题如下:
可以发现,类A初始化了两次!使用super()函数可以解决上述问题,类B1、类B2、类C的代码修改如下:
class B1(A):def __init__(self):super().__init__()print("哈喽,我是B1~")class B2(A):def __init__(self):super().__init__()print("哈喽,我是B2~")class C(B1, B2):def __init__(self):super().__init__()print("哈喽,我是C~")
使用super()方法调用__init__方法,不用传递self参数,因为super方法会自动解决。super函数的原理依赖于Python的MRO(Method Resolution Order:方法解析顺序),这可以通过类的方法mro()或者变量__mro__查看:
关于MRO顺序的一个案例:
class Displayer:def display(self, msg):print(msg)class LoggerMixin:def log(self, msg, filename):with open(filename, 'a') as f:f.write(msg)def display(self, msg):super().display(msg)self.log(msg)class MySubClass(LoggerMixin, Displayer):def log(self, msg):super().log(msg, filename="subclasslog.txt")subcls = MySubClass()
subcls.display("This is a test.")
运行结果是打印This is a test,并且生成subclasslog.txt,里面内容是This is a test。查看MySubClass的MRO顺序:
那么调用subcls的display方法,先去LoggerMixin里面找display方法,找到了,但是第一句是super().display(msg),此时不要以为会去LoggerMixin类的父类object里面找display方法,而是按照MRO顺序去Displayer里面找display方法,从而打印出This is a test,然后执行LoggerMixin力的display方法的第二句:self.log(msg),即调用MySubClass里面的log方法,发现没有,按照MRO顺序去LoggerMixin类里找log方法,找到了,即写入文件内容为msg。
4. __slots__属性
对象的属性除了直接obj.x = valueX这种方式设置外,还可以通过其字典__dict__的方式设置,比如:
c.__dict__['z'] = 666
Python中对象是可以随意添加任何属性的,这些属性存放在字典中,虽然字典访问效率高,但是浪费了很多空间,为了避免空间浪费,Python引入__slots__属性,通过__slots__属性设置一个类的对象只能拥有固定属性:
class C:__slots__ = ["x", "y"]def __init__(self, x)self.x = x
除了动态添加属性不行,在类的方法(包括构造方法)内添加属性也不行:
父类中的slots属性是不会在子类中生效的(但是子类仍然继承了父类的slots属性,只是不起作用了):
5. 魔法方法
__new__方法是在__init__方法之前调用的,常见的self对象就是它返回的。创建一个类,使得传给他的字符串始终为大写:
class CapStr(str):def __new__(cls, string):string = string.upper()return super().__new__(cls, string)
因为CapStr继承自str,所以str有的方法它也有:
对象被销毁时会调用__del__方法:
class C:def __init__(self):print("我来了~")def __del__(self):print("我走了~")
注意,不是使用了del语句就会调用__del__这个魔法方法,而是对象被销毁前才会调用,也就是说调用del语句并不会立即销毁对象,只是销毁对象的引用,只有当对象的引用都被销毁时,才会去销毁对象。比如将c再赋值给d,调用del c不会触发__del__魔法方法,接着调用del d才会触发。
既然__del__方法可以在对象被销毁前动手动脚,那么可以通过将即将要销毁的对象赋值给全局变量的方式,实现对象的重生。但是使用全局变量可能会污染命名空间,那么可以通过闭包的形式将对象传递给函数的参数,从而实现永久保存:
class E:def __init__(self, name, func):self.name = nameself.func = funcdef __del__(self):self.func(self)def outer():x = 0def inner(y=None):nonlocal xif y:x = yelsereturn xreturn inner
重写add方法可以实现自定义的加法功能,比如字符串的相加不再是拼接,而是字符长度的相加:
注意,加号调用的是左边对象的__add__方法,s1 + s2相当于s1.__add__(s2)。
__radd__方法的调用原则:如果加号两边的数据类型不同,并且左侧对象没有定义__add__方法,或者__add__方法实现为NotImplemented,那么Python就会去右侧对象找__radd__方法。
__iadd__方法不仅进行加法运算,还会将运算后的结果赋值给左侧对象,调用时机是使用了运算符+=:
__index__魔法方法是当对象作为索引值才会去调用,而不是对象的索引访问触发:
class C:def __index__(self):print("被拦截了~")return 3
关于属性的方法:hasattr、getattr:
class C:def __init(self, name, age):self.name = nameself.__age = agec = C("小甲鱼", 18)
hasattr(c, "name") # 返回True
getattr(c, "name") # 返回小甲鱼
对于私有属性,依然可以访问:
getattr(c, "_c__age") # 返回18
设置属性:
setattr(c, "_c__age", 19) # 返回18
删除属性:
delattr(c, "_c__age")
魔法方法__getattribute__会拦截getattr方法:
class C:def __init(self, name, age):self.name = nameself.__age = agedef __getattribute__(self, attrname):print("拿来吧你~")return super().getattribute__(attrname)c = C("小甲鱼", 18)
getattr(c, "name")
魔法方法__getattr__只有尝试去获取不存在的属性时才会去触发:
class C:def __init(self, name, age):self.name = nameself.__age = agedef __getattribute__(self, attrname):print("拿来吧你~")return super().getattribute__(attrname)def __getattr__(self, attrname):if attrname == 'FishC':print("I love FishC")elseraise AttributeError(attrname)c = C("小甲鱼", 18)
给属性赋值对应的魔法方法是__setattr__,但是这个方法里面的实现不能是self.attrname=value,因为这个语句又会调用魔法方法__setattr__,所以会死循环,正确做法是:
class D:def __setattr(self, attrname, value):self.__dict__[attrname] = value
同理,del语句删除属性时,也不能在魔法方法__del__中直接使用del self.attrname,否则也会无限循环,正确做法依然是操作self.__dict__这个字典:
class D:def __setattr(self, attrname, value):self.__dict__[attrname] = valuedef __delattr(self, attrname):self.__dict__[attrname]