類的複合
在一個類中包含了另外一個類的物件稱為類的複合。
例如電商系統中的購物車類會包含一個商品類
商品類包含id、name、price、count四個屬性,在__init__方法繫結到屬性上,同時__str__方法顯示商品的屬性資訊。
"""類的複合在一個類中包含了另外一個類的物件稱為類的複合@author tony [email protected]@version 2021-02-07 09:19:21@since python3.9.1""""""定義一個商品類"""class ProductInfo(object): def __init__(self, id, name, price, count): self.id = id self.name = name self.price = price self.count = count def __str__(self): """ :return: """ s = str( "商品的id:" + str(self.id).ljust(10) + "商品的名稱:" + str(self.name).ljust(20) + "商品的價格:" + str(self.price).ljust( 30) + "商品的數量" + str(self.count)) return s
購物車類主要包含新增商品和獲取購物的商品資訊,此時購物車類和商品資訊類就構成了類的複合。
"""定義一個購物車類"""class ShoppingCart(object): def add_product_info(self, product_info): """ 新增產品資訊 :param product_info: :return: """ self.product_info = product_info def get_shopping_cart_info(self): """ 獲取購物車商品資訊 :return: """ print("購物車資訊") print(self.product_info)
測試購物車物件的新增商品和展示購物車資訊
# 建立一個產品物件,透過__init__方法給產品的屬性賦值product_info = ProductInfo(1, "iphone12 pro max", 12999.99, 12)shopping_cart = ShoppingCart()shopping_cart.add_product_info(product_info)# 獲取購物車的商品資訊shopping_cart.get_shopping_cart_info()
程式執行結果
再看一個複合類的例子每件傢俱都有自己的名稱和佔用面積兩個屬性,在__init__方法繫結傢俱物件的屬性,而在__str__方法中列印傢俱的屬性資訊
"""類的複合-房屋、傢俱@author tony [email protected]@version 2021-02-07 11:05:02@since python3.9.1""""""定義傢俱類"""class Furniture(object): def __init__(self, name, area): """ 繫結傢俱物件的name和area屬性 :param name: :param area: """ self.name = name self.area = area def __str__(self): """ 顯示傢俱的名稱和佔用的面積 :return: """ s = "傢俱" + self.name + "佔用了" + str(self.area) + "平方米" return s
每個房屋都有自己的地址和麵積兩個屬性,其中約定30%的可用面積用來存放傢俱。其中__init__方法用於初始化房屋、傢俱,而add_furniture方法用於新增傢俱,當新增傢俱時,需要判斷傢俱的面積是否小於可用面積,如果小於則新增成功,如果大於則新增傢俱失敗。此時房屋和傢俱構成了複合的關係。
"""定義房屋類"""class House(object): def __init__(self, address, area): """ 初始化房屋、傢俱 :param address: :param area: """ # 30%的空間用來擺放傢俱 self.free_area = area * 0.3 self.area = area self.address = address # 一個房間可以存放多個傢俱,建立房屋物件時預設傢俱列表是空的 self.furniture = [] def add_furniture(self, furniture): """ 新增傢俱 :return: """ if furniture.area < self.free_area: self.furniture.append(furniture) self.free_area -= furniture.area else: print("房屋空間不足,無法新增新傢俱") def __str__(self): """ 顯示房屋和傢俱資訊 :return: """ # 拼接房屋資訊 s = f"我的大House在{self.address},佔地面積{self.area}平米,傢俱可用面積{self.free_area}平米\n" # 判斷房屋是否有傢俱 if len(self.furniture) == 0: s += "目前大House沒有新增傢俱" # 拼接傢俱資訊 else: s += "傢俱資訊如下:\n" for furniture in self.furniture: # str(furniture)會呼叫Furniture物件的__str__方法 s += str(furniture) + "\n" # 返回房屋、傢俱資訊 return ss
測試房屋新增傢俱和展示房屋、傢俱資訊
# 建立一個沙發傢俱sofa = Furniture("沙發", 8)# 建立一個房屋house = House("上海市浦東新區濱江大道", 300)# 新增一個沙發house.add_furniture(sofa)# 建立一個電視tv = Furniture("電視", 3)# 新增一個電視house.add_furniture(tv)# 使用匿名物件新增傢俱house.add_furniture(Furniture("雙人床", 5))house.add_furniture(Furniture("椅子1", 1))house.add_furniture(Furniture("椅子2", 1))house.add_furniture(Furniture("椅子3", 1))house.add_furniture(Furniture("椅子4", 1))house.add_furniture(Furniture("桌子", 6))print(house)
程式執行結果
封裝私有屬性在Python中定義類時,預設定義的變數是公有的,公有的變數可以透過物件在任何位置訪問。類中公有的屬性一般不建議使用,破壞程式的封裝性,在類的外部可以透過物件直接訪問,對於資料來說不夠安全。
"""公有屬性的弊端@author tony [email protected]@version 2021-02-07 13:39:46@since python3.9.1"""class Account(object): def __init__(self, name, balance): """ 定義兩個公有屬性 公有屬性可以在外部訪問 :param name: :param balance: """ self.name = name self.balance = balance def __str__(self): s = f"賬戶名稱:{self.name} 賬號餘額:{self.balance}" return stony = Account("tony", 99999999)# 在類的外部訪問物件的公有屬性print(tony.name)print(tony.balance)# 在類的外部訪問修改屬性值,破壞了封裝性,資料不安全tony.name = "jack"print(tony.name)print(tony.balance)
程式執行結果
因為公有的屬性資料不安全,可以將屬性設定成私有,私有屬性只能在類的內部使用,而類的外部是不能夠使用。
在python中,如果在屬性或者方法前加兩個下劃線字首,那麼這個屬性或方法就是私有的。
"""私有屬性@author tony [email protected]@version 2021-02-07 13:36:31@since python3.9.1"""class Account(object): def __init__(self, name, balance): """ 因為類中的公有屬性不安全,破壞封裝性,所有定義成私有的 :param name: :param balance: """ self.__name = name self.__balance = balance def __str__(self): s = f"賬戶名稱:{self.__name} 賬號餘額:{self.__balance}" return stony = Account("tony", 9999999)# 類的外部無法訪問私有屬性print(tony.__name)print(tony.__name)
程式執行結果
當一個類中的屬性和方法全部私有,這個類是無意義的。私有屬性的透過提供公共的get/set方法對外提供訪問,方法名是get_屬性名()和set_屬性名()
私有化屬性並且提供公共的get/set方法後有如下好處
提供精確的訪問許可權控制隱藏實現細節,讓程式碼更安全可以提供更加安全的資料精度控制"""私有屬性@author tony [email protected]@version 2021-02-07 13:36:31@since python3.9.1"""class Account(object): def __init__(self, name, balance): """ 因為類中的公有屬性不安全,破壞封裝性,所有定義成私有的 :param name: :param balance: """ # self.__name = name self.__balance = balance def get_name(self): """ 私有屬性的get方法 因為賬戶名一旦確認後無法修改,所以只提供get方法 :return: """ return self.__name def set_balance(self, balance): """ 設定餘額 :param balance: :return: """ # 判斷引數是否是數字 if isinstance(balance, int): self.__balance = balance else: print("餘額必須是數字") def get_balance(self): return self.__balance def __str__(self): s = f"賬戶名稱:{self.__name} 賬號餘額:{self.__balance}" return stony = Account("tony", 9999999)print(tony._Account__name)# 類的外部無法訪問私有屬性# print(tony.__name)# print(tony.__name)# print(tony.__Account__name)print("賬戶餘額:", tony.get_balance())print("賬戶名:", tony.get_name())tony.set_balance("10個億")print("修改之後的賬戶餘額", tony.get_balance())
程式執行結果
私有方法類中公共的方法作為外部提供的一種介面,透過這個介面可以實現互動操作。而私有方法就是將不想公開方法實現,透過私有方法進行封裝可以隱藏關鍵程式碼。如果想使用私有方法的功能時,可以在類的內部使用,或者在類的外部透過類提供的公共方法來間接使用。
這裡模擬尋迅雷下載檔案,其中檔案封裝了一個FileInfo類,主要包含檔名,檔案下載本地路徑以及檔案下載的遠端URL
"""檔案類"""class FileInfo(object): def __init__(self, file_url): self.__file_name = "" self.__file_url = file_url self.__file_location = "/Users/liuguanglei/Downloads" def get_file_url(self): return self.__file_url def get_file_name(self): return self.__file_name def get_file_location(self): return self.__file_location
而迅雷封裝了一個Thunder類,該類提供了三個方法,其中一個是公共的download方法給外部呼叫,而另兩個私有的__handle_download方法、__parse_file_name方法給download方法呼叫
"""迅雷下載類"""class Thunder(object): def __init__(self, file_info): self.__file_info = file_info def download(self): """ 下載檔案 """ print("開始下載檔案") # 呼叫私有的處理下載檔案的方法 self.__handle_download(self.__file_info) print("結束下載檔案") def __handle_download(self, file_info): """ 私有化處理下載檔案的方法 :param file_info: :return: """ print("開始處理檔案下載,下載的檔案資訊") print(f"開始根據{file_info.get_file_url()}從網路上查詢檔案") file_name = self.__parse_file_name(file_info.get_file_url()) print(f"開始複製檔案{file_name}到本地磁碟,預設的路徑是{file_info.get_file_location()}") print("檔案複製完畢") def __parse_file_name(self, file_url): """ 根據url獲取檔名 :param file_url: :return: """ if isinstance(file_url, str): file_url_partition = file_url.rpartition("/") file_name = file_url_partition[2] return file_name
測試迅雷下載
# 測試迅雷下載thunder = Thunder( FileInfo("https://www.python.org/ftp/python/3.9.1/python-3.9.1-macosx10.9.pkg"))thunder.download()
程式執行結果
類的繼承繼承概述現實生活中子女可以繼承父母的資產。而程式本身就是對現實世界的模擬,程式中也可以實現類的繼承。繼承是描述程式中多個類之間的關係,如果一個父類(也叫基類)的屬性和方法可以複用,那麼子類(派生類)就可以透過繼承父類的方式,呼叫父類的屬性和方法,同時子類還可以擁有自己獨立的屬性和方法。
Python中類的繼承語法格式
class 子類名(父類名): #方法列表
繼承的方法呼叫
在出現繼承後,當子類物件去呼叫方法時,會先查詢子類的方法,如果有就執行,如果沒有找到,就去父類中查詢,該類如果找不到再到上一級的類中查詢,直到找到object類,如果沒找到就報錯。子類不能訪問父類的私有屬性和方法,但是也可透過父類的公有方法間接訪問父類的私有方法
定義父類CellPhone 模擬老式電話,功能簡單,只能打電話,由公共方法call實現,其中該方法還呼叫了私有方法__get_cell_phone_number,用於根據名字查詢電話。
"""繼承的方法呼叫@author tony [email protected]@version 2021-02-07 16:05:26@since python3.9.1""""""定義父類CellPhone 模擬老式電話,功能簡單,只能打電話"""class CellPhone(object): # 初始化電話本 phone_dict = {"tony": "18601767221", "jack": "18601752217"} def call(self, name): """ 父類的方法 :param name: :return: """ cell_phone_number = self.__get_cell_phone_number(name) print(f"給{name}打電話,電話號碼是{cell_phone_number}") def __get_cell_phone_number(self, name): """ 根據name獲取電話號碼,此方法為私有方法 :param name: :return: """ cell_phone_number = self.phone_dict.get(name) return cell_phone_number
定義IPhone,該類作為CellPhone的父類,除了擁有打電話的功能以外,還可以拍照
"""定義IPhone 繼承自CellPhone"""class IPhone(CellPhone): def take_photo(self, name): """ 子類特有的拍照方法 :param name: :return: """ print(f"給{name}拍照") pass
當建立子類Iphone物件後就可以呼叫父類的公共方法以及子類特有的方法,而呼叫父類的call()方法時,會間接呼叫父類的__get_cell_phone_number私有方法。
# 建立子類物件iphone12 = IPhone()# 呼叫繼承自父類的打電話方法name = "tony"iphone12.call(name)# 呼叫子類特有的方法iphone12.take_photo(name)
程式執行結果
需要注意的是子類不能呼叫父類的私有方法,如果呼叫父類的私有方法將會出錯
# 建立子類物件iphone12 = IPhone()# 呼叫繼承自父類的打電話方法name = "tony"# 子類不能呼叫父類的私有方法iphone12.__get_phone(name)
程式執行結果
繼承的方法重寫在子類繼承父類後,子類擁有父類的方法,但是子類並不想使用父類的方法或者父類的方法不能滿足需要求時,子類需要重寫父類的方法。子類重寫父類的方法後優先呼叫子類同名的方法。
定義動物父類Animal,該類包含兩個方法:eat()和sleep()
"""繼承的方法重寫以及子類呼叫父類的方法@author tony [email protected]@version 2021-02-07 17:26:16@since python3.9.1""""""定義動物父類"""class Animal(object): def eat(self): print("動物園的小動物在吃東西") def sleep(self, name): print(f"動物園的{name}在睡覺")
定義Animal父類的兩個子類:Monkey和Panda,它們都重寫了父類的eat()方法,同時呼叫了父類的sleep()方法
class Monkey(Animal): def eat(self): """ Monkey子類重寫父類的eat方法 :return: """ print("猴子在吃香蕉") # 呼叫父類的方法格式:父類名.方法名(引數列表) Animal.sleep(self, "猴子們")class Panda(Animal): def eat(self): """ Panda子類重寫父類的eat()方法 :return: """ print("國寶在吃竹子") Animal.sleep(self, "熊貓們")
測試方法的重寫以及子類呼叫父類的方法
# 建立子類對想並呼叫eat()方法monkey = Monkey()monkey.eat()panda = Panda()panda.eat()
程式執行結果
繼承的屬性初始化在子類繼承父類後,如果子類提供了__init__方法,那麼在使用子類物件時,就會呼叫子類自己的__init__方法,不會再呼叫父類的__init__方法,這樣父類中的屬性不會再有繫結的機會,所以這時沒有父類的屬性,如果想要獲取父類的屬性,需要執行父類的__init__方法,呼叫格式是父類名.__init__(引數列表)
定義父類Product,該類有兩個私有屬性:__name和__price,同時包含了__init__方法和獲取兩個屬性的屬性值方法
"""繼承的屬性初始化@author tony [email protected]@version 2021-02-07 16:59:44@since python3.9.1""""""定義產品父類"""class Product(object): def __init__(self, name, price): self.__name = name self.__price = price def get_name(self): return self.__name def get_price(self): return self.__price
定義子類ElectronicProduct,該類繼承自Product父類,擁有一個私有屬性__rechargeable,同時包含了__init__方法和__str__方法
"""定義電子產品子類"""class ElectronicProduct(Product): def __init__(self, name, price, rechargeable): """ 如果子類包含__init__方法,則建立物件時預設不會呼叫父類的__init__方法,而是呼叫子類自己的__init__方法 如果想要獲取父類的屬性,需要呼叫父類的__init__方法,呼叫格式為父類名.__init__(引數列表) :param rechargeable: """ # 呼叫父類的__init__()方法初始化name,price屬性 Product.__init__(self, name, price) # 初始化子類的屬性 self.__rechargeable = rechargeable def __str__(self): rechargeable = '支援充電' if self.__rechargeable else '不支援充電' # 父類名.方法名(引數類表)呼叫父類的方法 s = f"電子產品資訊: 產品名稱{Product.get_name(self)},產品價格:{Product.get_price(self)},產品是否支援充電:{rechargeable}" return s pass
當建立子類物件時,子類物件想要獲取父類的屬性,需要在子類的__init__方法呼叫父類的__init__方法
# 建立子類物件iphone12 = ElectronicProduct("iphone12 pro", 7999, True)print(iphone12)
程式執行結果
多層繼承多層繼承指的是子類繼承父類,父類繼承爺爺類,例如之前的ElectronicProduct繼承Product父類,而Product父類繼承object爺爺類
這裡以訂單為例Order類作為父類,子類是AliOrder類,AliOrder 類有TaoBaoOrder和TmallOrder兩個子類。
Order類有四個屬性:order_id,order_address,order_amount,並且在__init__方法繫結給Order物件以及Order類的子類物件,提供了__str__方法顯示訂單資訊
"""定義定單父類"""class Order(object): def __init__(self, order_id, order_address, order_amount): self.__order_id = order_id self.__order_address = order_address self.__order_amount = order_amount def get_order_id(self): return self.__order_id def get_order_address(self): return self.__order_address def get_order_amount(self): return self.__order_amount def __str__(self): s = f"訂單資訊--> 訂單ID:{self.__order_id} 訂單地址:{self.__order_address} 訂單金額:{self.__order_amount} " return s pass
子類AliOrder繼承自Order
"""阿里系訂單類"""class AliOrder(Order): pass
子類TaoBaoOrder和TmallOrder繼承自父類AliOrder,並在TmallOrder類中增加了兩個屬性:username,telphone,在__init__方法中透過AliOrder.__init__(self, order_id, order_address, order_amount) 呼叫父類的__init__方法。而且提供了__str__方法顯示天貓訂單資訊。
"""淘寶訂單類"""class TaoBaoOrder(AliOrder): pass"""天貓訂單類"""class TmallOrder(AliOrder): def __init__(self, order_id, order_address, order_amount, username, telphone): AliOrder.__init__(self, order_id, order_address, order_amount) self.__username = username self.__telphone = telphone def __str__(self): s = f"天貓訂單資訊--> 訂單ID:{AliOrder.get_order_id(self)} 訂單地址:{AliOrder.get_order_address(self)} 訂單金額:{AliOrder.get_order_amount(self)} 下單人:{self.__username},下單手機號:{self.__telphone}" return s pass
多層繼承測試
當建立TmallOrder物件時,會先呼叫父類AliOrder的__init__方法完成__order_id, __order_address, __order_amount屬性的賦值,再執行子類的__init__方法完成__username, __telphone的屬性賦值
而建立TaoBaoOrder物件時,呼叫了Order爺爺類的__init__方法完成屬性的賦值,以及爺爺類Order的__str__方法顯示訂單資訊,這樣說明了繼承時的查詢機制:先找當前類,找不到再找父類,直到object根類,找不到就報錯。
tmall = TmallOrder("tm00001", "上海市", 999, "tony", "18601767221")print(tmall)taobao = TaoBaoOrder("tm00001", "上海市", 999)print(taobao)
程式執行結果
多繼承多繼承表示子類可以同時繼承多個父類Python多繼承的語法格式是
class 子類名(父類1,父類2)
多繼承時多個父類之間使用逗號(,)隔開。
當多個類之間發生了菱形繼承(也叫做鑽石繼承),例如Son同時繼承了Father和Uncle,而Father繼承了Grandfather,Uncle也繼承了Grandfather,此時就是鑽石繼承
多繼承時(這裡以鑽石繼承為例),如果繼承的多個類同時繼承了同一個父類,那麼這時會出現初始化問題,這個共同的父類會被初始化多次。
當子類Son呼叫__init__方法時,爺爺類Grandfather會被初始化多次,出現問題的原因是因為在子類的__init__()呼叫父類名.__init__(引數列表)方法導致。
"""鑽石繼承的初始化問題@author tony [email protected]@version 2021-02-07 19:03:02@since python3.9.1"""class Grandfather(object): def __init__(self, name, age, gender): self.__name = name self.__age = age self.__gender = gender print("Init Grandfather") passclass Father(Grandfather): def __init__(self, name, age, gender, marriage): Grandfather.__init__(self, name, age, gender) self.__marriage = marriage print("Init Father") passclass Uncle(Grandfather): def __init__(self, name, age, gender, address): Grandfather.__init__(self, name, age, gender) self.__address = address print("Init Uncle") passclass Son(Father, Uncle): def __init__(self, name, age, gender, marriage, address, salary): Father.__init__(self, name, age, gender, marriage) Uncle.__init__(self, name, age, gender, address) self.__salary = salary print("Init Son") pass# 例項化子類物件會發現Grandfather的__init__方法會呼叫多次son = Son("jack", 25, "男", "未婚", "上海市", 8000)
程式執行結果例項化子類物件會發現Grandfather的__init__方法會呼叫多次
那麼如何解決鑽石繼承的初始化問題?
使用super(類名,self) 物件呼叫父類的__init__方法解決鑽石繼承的初始化問題。其中第一個引數類名錶示當前類名,而第二個引數self是當前類的例項物件,在第二個引數所屬類的method relation ordered關係中查詢引數一的下一個類進行例項化,第一個引數和第二個引數是可以省略的,加上引數就是為了明白其原理。
"""解決鑽石繼承的初始化問題@author tony [email protected]@version 2021-02-07 20:17:50@since python3.9.1"""class Grandfather(object): def __init__(self, name, age, gender): self.__name = name self.__age = age self.__gender = gender print("Init Grandfather") def get_name(self): return self.__name def get_age(self): return self.__age def get_gender(self): return self.__gender passclass Father(Grandfather): def __init__(self, name, age, gender, marriage, address): # Grandfather.__init__(self, name, age, gender) super(Father, self).__init__(name, age, gender, address) self.__marriage = marriage print("Init Father") def get_marriage(self): return self.__marriage passclass Uncle(Grandfather): def __init__(self, name, age, gender, address): # Grandfather.__init__(self, name, age, gender) super(Uncle, self).__init__(name, age, gender) self.__address = address print("Init Uncle") def get_address(self): return self.__address passclass Son(Father, Uncle): def __init__(self, name, age, gender, marriage, address, salary): # Father.__init__(self, name, age, gender, marriage) # Uncle.__init__(self, name, age, gender, address) # 第一個引數類名錶示當前類名,而第二個引數self是當前類的例項物件,在第二個引數所屬類的method relation ordered關係中查詢引數一的下一個類進行例項化。 super(Son, self).__init__(name, age, gender, marriage, address) self.__salary = salary print("Init Son") def __str__(self): s = f"姓名:{Grandfather.get_name(self)} 年齡:{Grandfather.get_age(self)} 性別:{Grandfather.get_gender(self)},婚姻狀況:{Father.get_marriage(self)}家庭地址:{Uncle.get_address(self)},月收入:{self.__salary}" return s pass# 獲取Son類的method relation ordered關係print("Son類的method relation ordered關係", Son.__mro__)print("Father類的method relation ordered關係", Father.__mro__)# 建立Son的物件son = Son("jack", 25, "男", "未婚", "上海市", 8000)print(son)
程式執行結果
而透過類名.__mro__方法就可以得到一個元祖,該元祖中的元素是當前類在繼承關係上的一個順序,這個順序不是我們確定的,是由在確定某個類的繼承關係後,由Python直譯器來確定這個順序。而super物件是基於類的mro(method relation ordered)順序來對當前類進行初始化的,基於這個mro順序可以保證所有繼承關係上的類只會執行一次初始化。需要注意的是類多繼承時的書寫順序會影響mro的順序,例如Son先繼承Uncle,再繼承Father
class Son(Uncle, Father): def __init__(self, name, age, gender, marriage, address, salary): # Father.__init__(self, name, age, gender, marriage) # Uncle.__init__(self, name, age, gender, address) # 第一個引數類名錶示當前類名,而第二個引數self是當前類的例項物件,在第二個引數所屬類的method relation ordered關係中查詢引數一的下一個類進行例項化。 super(Son, self).__init__(name, age, gender, marriage, address) self.__salary = salary print("Init Son") def __str__(self): s = f"姓名:{Grandfather.get_name(self)} 年齡:{Grandfather.get_age(self)} 性別:{Grandfather.get_gender(self)},婚姻狀況:{Father.get_marriage(self)}家庭地址:{Uncle.get_address(self)},月收入:{self.__salary}" return s pass print("Son類的method relation ordered關係", Son.__mro__)
程式執行結果
不過現在還有個引數傳遞的問題,當嘗試建立Father類的物件時,發現引數傳遞問題
father = Father("jack", 25, "男", "未婚")print(father)
程式執行結果
透過使用可變引數*args解決菱形繼承的引數傳遞,而且Father,Uncle也可以單獨使用
"""解決鑽石繼承的初始化引數傳遞的問題@author tony [email protected]@version 2021-02-07 20:17:50@since python3.9.1"""class Grandfather(object): def __init__(self, name, age, gender): self.__name = name self.__age = age self.__gender = gender print("Init Grandfather") def get_name(self): return self.__name def get_age(self): return self.__age def get_gender(self): return self.__gender passclass Father(Grandfather): def __init__(self, marriage, *args): # Grandfather.__init__(self, name, age, gender) super(Father, self).__init__(*args) self.__marriage = marriage print("Init Father") def get_marriage(self): return self.__marriage def __str__(self): s = f"姓名:{Grandfather.get_name(self)} 年齡:{Grandfather.get_age(self)} 性別:{Grandfather.get_gender(self)},婚姻狀況:{Father.get_marriage(self)}" return s pass passclass Uncle(Grandfather): def __init__(self, address, *args): # Grandfather.__init__(self, name, age, gender) super(Uncle, self).__init__(*args) self.__address = address print("Init Uncle") def get_address(self): return self.__address def __str__(self): s = f"姓名:{Grandfather.get_name(self)} 年齡:{Grandfather.get_age(self)} 性別:{Grandfather.get_gender(self)},家庭地址:{self.__address}" return s pass passclass Son(Father, Uncle): def __init__(self, salary, marriage, address, name, age, gender): # Father.__init__(self, name, age, gender, marriage) # Uncle.__init__(self, name, age, gender, address) # 第一個引數類名錶示當前類名,而第二個引數self是當前類的例項物件,在第二個引數所屬類的method relation ordered關係中查詢引數一的下一個類進行例項化。 super(Son, self).__init__(marriage, address, name, age, gender) self.__salary = salary print("Init Son") pass def __str__(self): s = f"姓名:{Grandfather.get_name(self)} 年齡:{Grandfather.get_age(self)} 性別:{Grandfather.get_gender(self)},婚姻狀況:{Father.get_marriage(self)}家庭地址:{Uncle.get_address(self)},月收入:{self.__salary}" return s pass# 獲取Son類的method relation ordered關係print("Son類的method relation ordered關係", Son.__mro__)print("Father類的method relation ordered關係", Father.__mro__)# 建立Son的物件son = Son(23000, "未婚", "上海市", "jack", 25, "男")print(son)father = Father("已婚", "bruce", 45, "男")print("father物件資訊", father)uncle = Uncle("上海市", "tony", 45, "男")print("uncle物件資訊", father)
程式執行結果
當多繼承時,呼叫父類方法的查詢順序也是基於mro(method relation ordered)順序查詢父類的方法
當在子類Son的show_money方法中使用super().get_money(amount)呼叫父類的get_money()方法時,首先查詢父類Father中是否有該方法,如果有則直接呼叫,如果沒有則從Uncle父類中查詢,如果有該方法就直接呼叫。 如果想要呼叫指定的父類方法,可以透過父類名.方法名(引數列表)即可。
"""多繼承的方法呼叫機制@author tony [email protected]@version 2021-02-08 10:05:34@since python3.9.1"""class GrandFather(object): def get_money(self, amount): print(f"爺爺給{amount}壓歲錢") return amountclass Father(GrandFather): passclass Uncle(GrandFather): def get_money(self, amount): print(f"伯父給{amount}壓歲錢") return amountclass Son(Father, Uncle): def show_money(self, current_amount, amount): """ :param current_amount: :param amount: :return: """ print(f"我當前有{current_amount}壓歲錢") amount = super().get_money(amount) current_amount += amount print(f"長輩給壓歲錢之後,我當前有{current_amount}壓歲錢")print("Son類的method relation ordered關係", Son.__mro__)son = Son()current_amount = 100amount = 200son.show_money(current_amount, amount)
程式執行結果
mro順序的實現原理首先準備一個複雜的多繼承和多層繼承
"""mro順序的原理@author tony [email protected]@version 2021-02-08 10:22:10@since python3.9.1""""""A是object的子類"""class A(object): pass"""B、C、D是A的子類"""class B(A): passclass C(A): passclass D(A): passclass G(D): passclass J(G): passclass E(B): passclass F(B, C): passclass H(E, F): passclass I(G): passclass K(H, I, J): passprint("k的mro順序", K.__mro__)
類圖如下
程式執行結果
k的mro順序 (<class '__main__.K'>, <class '__main__.H'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.I'>, <class '__main__.J'>, <class '__main__.G'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
程式執行結果
多型多型指的是方法的多型,即當呼叫一個同名的方法名,得到的是不同的結果。在一般的面向物件的語言中,多型由繼承實現。但是Pyton天生具有多型性,Python的多型是鴨子型別(Duck Typing):一隻鳥如果長得像鴨子,叫聲像樣子,那麼它就認為是鴨子。因為Pyhton是弱型別的語言,沒有型別限制,但是必須具有相同的行為(即方法)。
"""python多型@author tony [email protected]@version 2021-02-08 10:53:35@since python3.9.1"""class Animal(object): def __init__(self, name): self.__name = name def get_name(self): return self.__name def eat(self): print(f"{self.__name}正在吃食物")class Panda(Animal): def eat(self): print(f"{super().get_name()}正在吃竹子")class Monkey(Animal): def eat(self): print(f"{super().get_name()}正在吃香蕉")class Zookeeper(object): def feedAnimal(self, animal): """ 動物飼養員飼養動物 :param animal: :return: """ animal.eat()# 建立一個飼養員tony = Zookeeper()# 飼養員飼養動物tony.feedAnimal(Panda("四川大熊貓"))tony.feedAnimal(Monkey("雲南金絲猴"))
程式執行結果