目錄
十五.多繼承的繼承順序 - mro
調用父類方式不同導致結果不同
單繼承中的super
簡單總結
面試題
十六.魔術方法
魔術方法概述
魔術方法概覽
__getattribute__屬性
__getattribute__注意事項
常用的魔術方法
__doc__
__module__和__class__
__init__
__del__
__call__
__dict__
__getitem__、__setitem__、__delitem__
十五.多繼承的繼承順序 - mro
調用父類方式不同導致結果不同
單獨調用父類的方法
class Parent:def __init__(self, name):print('parent的init開始被調用...')self.name = nameprint('parent的init結束被調用...')class Son1(Parent):def __init__(self, name, age):print('Son1的init開始被調用...')self.age = ageParent.__init__(self, name)print('Son1的init結束被調用...')class Son2(Parent):def __init__(self, name, gender):print('Son2的init開始被調用...')self.gender = genderParent.__init__(self, name)print('Son2的init結束被調用...')class GrandSon(Son1, Son2):def __init__(self, name, age, gender):print('GrandSon的init開始被調用...')Son1.__init__(self, name, age) # 單獨調用父類的初始化方法Son2.__init__(self, name, gender)print('GrandSon的init結束被調用...')gs = GrandSon('grandson', 12, '男')
print('-' * 30)
print('姓名:', gs.name)
print('年齡:', gs.age)
print('性別:', gs.gender)運行結果:
GrandSon的init開始被調用...
Son1的init開始被調用...
parent的init開始被調用...
parent的init結束被調用...
Son1的init結束被調用...
Son2的init開始被調用...
parent的init開始被調用...
parent的init結束被調用...
Son2的init結束被調用...
GrandSon的init結束被調用...
------------------------------
多繼承中super調用被重寫的父類方法
class Parent:def __init__(self, name, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數,接受參數print('parent的init開始被調用...')self.name = nameprint('parent的init結束被調用...')class Son1(Parent):def __init__(self, name, age, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數,接受參數print('Son1的init開始被調用...')self.age = agesuper().__init__(name, *args, **kwargs) # 為避免多繼承報錯,使用不定長參數,接受參數print('Son1的init結束被調用...')class Son2(Parent):def __init__(self, name, gender, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數,接受參數print('Son2的init開始被調用...')self.gender = gendersuper().__init__(name, *args, **kwargs) # 為避免多繼承報錯,使用不定長參數,接受參數print('Son2的init結束被調用...')class GrandSon(Son1, Son2):def __init__(self, name, age, gender):print('GrandSon的init開始被調用...')# 多繼承時,相對于使用類名.__init__方法,要把每個父類全部寫一遍# 而super只用一句話,執行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因# super(GrandSon, self).__init__(name, age, gender)super().__init__(name, age, gender)print('GrandSon的init結束被調用...')gs = GrandSon('grandson', 12, '男')
print('-' * 30)
print(GrandSon.__mro__) # 輸出繼承順序
print('姓名:', gs.name)
print('年齡:', gs.age)
print('性別:', gs.gender)運行結果:
GrandSon的init開始被調用...
Son1的init開始被調用...
Son2的init開始被調用...
parent的init開始被調用...
parent的init結束被調用...
Son2的init結束被調用...
Son1的init結束被調用...
GrandSon的init結束被調用...
------------------------------
(<class '__main__.GrandSon'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
姓名: grandson
年齡: 12
性別: 男
上述兩種調用父類的方法是有區別的:
- 如果兩個子類中都繼承了父類,當在子類中通過父類名調用時,parent被執行了兩次
- 如果兩個子類中都繼承了父類,當在子類中通過super調用時,parent被執行了一次
單繼承中的super
class Parent:def __init__(self, name):print('parent的init開始被調用...')self.name = nameprint('parent的init結束被調用...')class Son(Parent):def __init__(self, name, age):print('Son的init開始被調用...')self.age = agesuper().__init__(name) # 單繼承不能提供全部參數print('Son的init結束被調用...')class Grandson(Son):def __init__(self, name, age, gender):print('GrandSon的init開始被調用...')self.gender = gendersuper().__init__(name, age) # 單繼承不能提供全部參數print('GrandSon的init結束被調用...')gs = Grandson('grandson', 12, '男')
print('-' * 30)
print('姓名:', gs.name)
print('年齡:', gs.age)
print('性別:', gs.gender)運行結果:
GrandSon的init開始被調用...
Son的init開始被調用...
parent的init開始被調用...
parent的init結束被調用...
Son的init結束被調用...
GrandSon的init結束被調用...
------------------------------
姓名: grandson
年齡: 12
性別: 男
簡單總結
- super().__init__相對于類名.__init__,在單繼承上用法基本無差。
- 但在多繼承上有區別,super方法能保證每個父類的方法只會執行一次,而使用類名的方法會導致方法被執行多次,具體看前面的輸出結果。
- 多繼承時,使用super方法,對父類的傳參數,由于super的算法導致的原因,必須把參數全部傳遞,否則會報錯。
- 單繼承時,使用super方法,則不能全部傳遞,只能傳父類方法所需的參數,否則會報錯。
- 多繼承時,相對于使用類名.__init__()方法,要把每個父類全部寫一遍, 而使用super方法,只需寫一句話便執行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因。
面試題
以下代碼將會輸出什么?
class Parent(object):x = 1class Child1(Parent):passclass Child2(Parent):passprint(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)輸出結果:
1 1 1
1 2 1
3 2 3
使你困惑或是驚奇的是關于最后一行的輸出是3 2 3而不是3 2 1。為什么改變了Parent.x的值還會改變Child2.x的值,但是同時Child1.x值卻沒有改變?
這個答案的關鍵是,在Python中,類變量在內部是作為字典處理的。如果一個變量的名字沒有在當前類的字典中發現,將搜索祖先類(比如父類)直到被引用的變量名被找到(如果這個被引用的變量名既沒有在自己所在的類又沒有在祖先類中找到,會引發一個AttributeError異常 )。
因此,在父類中設置x = 1會使得類變量x在引用該類和其任何子類中的值為1。這就是因為第一個print語句的輸出是1 1 1。
隨后,如果任何它的子類重寫了該值(例如,我們執行語句Child1.x = 2),然后,該值僅僅在子類中被改變。這就是為什么第二個print語句的輸出是1 2 1。
最后,如果該值在父類中被改變(例如,我們執行語句Parent.x = 3),這個改變會影響到任何未重寫該值的子類當中的值(在這個示例中被影響的子類是Child2)。這就是為什么第三個print輸出是3 2 3
十六.魔術方法
魔術方法概述
Python中的魔術方法是一組預定義的方法,它們以雙下劃線開頭和結尾(例如__init__、__str__ 等)。它們有特別的意義,因為Python的解釋器會在某些內置行為發生時自動調用它們。魔術方法允許開發者定義或修改類的默認行為。
Python3中定義的類都是新式類,無論是否寫明一個類繼承object,都會間接或直接繼承object。
class Person(object):pass
In [2]: dir(Person)Out[2]:
['__class__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__getattribute__','__gt__','__hash__','__init__','__init_subclass__','__le__','__lt__','__module__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__','__weakref__']
Python2默認不繼承object。
# Python2中無繼承父類,稱之經典類。Python3中已默認繼承object
class Person:pass
Python 2.7.16 (default, Mar 25 2021, 18:52:10)
[GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.37.14)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class Person:
... pass
...
>>> dir(Person)
['__doc__', '__module__']
>>>
魔術方法概覽
方法名稱 | 說明 | 觸發方式 |
| 構造初始化函數 | 創建實例后賦值時使用,在 |
| 創建類的實例 | 創建實例時被調用 |
| 實例所在的類的地址 |
|
| 返回實例對象的字符串信息 |
|
| 返回實例對象的字符串信息 |
|
| 析構(對象被刪除前做清理工作) |
|
| 實例自定義屬性 |
|
| 類文檔說明,子類不繼承 |
|
| 屬性訪問攔截器 | 訪問實例屬性時 |
| 獲取類所有的直接父類:不能獲取父類的父類 |
|
__getattribute__屬性
__getattribute__功能很強大:能夠完成屬性訪問時進行攔截
class Student:country = '中國'def __init__(self, name, age, address):self.name = nameself.age = ageself.__address = addressdef info(self):print(self.name, self.age, self.__address, Student.country) # 使用類對象獲取國家def __info_method(self):print(self.name, self.age, self.__address, self.country) # 使用實例對象獲取國家def __getattribute__(self, item): # item傳入的是屬性名而不是屬性值print('開始校驗屬性或方法:', item)return object.__getattribute__(self, item) # 返回屬性值stu = Student('安娜', 18, '長沙')print('實例對象打印屬性: ', stu.name, stu.age, stu.country) # 獲取屬性會觸發__getattribute__
print('-' * 30)stu.info() # 調用實例方法
print('-' * 30)stu._Student__info_method() # 調用私有方法
print('-' * 30)
print('類對象打印屬性:', Student.country) # 使用類對象獲取類屬性不會觸發__getattribute__'''
1.直接使用類名.類屬性的形式調用類屬性不會觸發__getattribute__方法
2.如果在當前這個類中調用了方法則__getattribute__會獲取當前這個方法的名稱
'''運行結果:
開始校驗屬性或方法: name
開始校驗屬性或方法: age
開始校驗屬性或方法: country
實例對象打印屬性: 安娜 18 中國
------------------------------
開始校驗屬性或方法: info
開始校驗屬性或方法: name
開始校驗屬性或方法: age
開始校驗屬性或方法: _Student__address
安娜 18 長沙 中國
------------------------------
開始校驗屬性或方法: _Student__info_method
開始校驗屬性或方法: name
開始校驗屬性或方法: age
開始校驗屬性或方法: _Student__address
開始校驗屬性或方法: country
安娜 18 長沙 中國
------------------------------
類對象打印屬性: 中國
__getattribute__注意事項
class Person:def __getattribute__(self, item):print("查詢指定的屬性或方法是否存在...")if item.startswith("h"):return "world"else:return self.set_attribute_method() # 會出現遞歸調用# try:# return super().__getattribute__(item)# except AttributeError:# self.set_attribute_method()@staticmethoddef set_attribute_method():return "模擬設置屬性的方法..."p = Person()# 返回world
print(p.hello)# 會讓程序死掉
# print(p.python)"""
不要在__getattribute__方法中使用self關鍵字只要self關鍵字使用類中的屬性和方法就會觸發__getattribute__
"""
常用的魔術方法
__doc__
表示類的描述信息
class Foo:""" 描述類信息,這是一個類的簡單描述 """def func(self):passprint(Foo.__doc__)
__module__和__class__
- __module__表示當前操作的對象在哪個模塊
- __class__表示當前操作的對象的類是誰
test.py文件class Person:def __init__(self):self.name = '安娜'
from test import Personobj = Person()
print(obj.__module__) # 輸出 test 即:輸出模塊
print(obj.__class__) # 輸出 test.Person 即:輸出類
__init__
構造方法:通過類創建對象時,自動觸發執行
class Person:def __init__(self, name):self.name = nameself.age = 18obj = Person('安娜') # 自動執行類中的 __init__ 方法
__del__
當對象在內存中被釋放之前,自動觸發執行
注:此方法一般無須定義。因為Python是一門高級語言,程序員在使用時無需關心內存的分配和釋放,因為此工作都是交給Python解釋器來執行,所以__del__的調用是由解釋器在進行垃圾回收前自動觸發,執行一些完善工作。
class Foo:def __del__(self):pass
__call__
對象后面加括號,觸發執行
注:__init__方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對于__call__方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()。
class Foo:def __init__(self):passdef __call__(self, *args, **kwargs):print('__call__')obj = Foo() # 執行 __init__
obj() # 執行 __call__
__dict__
類或對象中的所有屬性
類的實例屬性屬于對象,類中的類屬性和方法等屬于類。
class Province:country = 'China'def __init__(self, name, count):self.name = nameself.count = countdef func(self, *args, **kwargs):print('func')# 獲取類的屬性,即:類屬性、方法、
print(Province.__dict__)
# 輸出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}obj1 = Province('山東', 10000)
print(obj1.__dict__)
# 獲取 對象obj1 的屬性
# 輸出:{'count': 10000, 'name': '山東'}obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 獲取 對象obj1 的屬性
# 輸出:{'count': 20000, 'name': '山西'}
__str__
如果一個類中定義了__str__方法,那么在打印對象時,默認輸出該方法的返回值
class Foo:def __str__(self):return '雙雙'obj = Foo()
print(obj)
# 輸出:雙雙
__getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分別表示獲取、設置、刪除數據
class Foo:def __getitem__(self, key):print('__getitem__', key)def __setitem__(self, key, value):print('__setitem__', key, value)def __delitem__(self, key):print('__delitem__', key)obj = Foo()result = obj['k1'] # 自動觸發執行 __getitem__
obj['k2'] = '安娜' # 自動觸發執行 __setitem__
del obj['k1'] # 自動觸發執行 __delitem__