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]
????????