1.對象的定義
1.1 什么是對象
面向過程:將程序流程化
對象:就是“容器“,是用來存儲數據和功能的,是數據和功能的集合體。
面向對象和面向過程沒有優劣之分,它們只是使用的場景不同罷了。
1.2 為什么要有對象
有了對象之后,數據和功能之間有關系的會被放在同一個”容器里面“,它的解耦合程度會非常高,方便閱讀和維護。
1.3 怎么造對象
如果一個模塊里面存放了數據和功能,那么這個模塊就是容器,可以被稱之為對象。對象的本質就是存放數據和功能的”容器“。我們只要能做到這一點,我們就是在使用面向對象的思想在寫程序。但是使用文件整合的話,文件就太多了。
2. 類
2.1 類的概念
類的一方面是將不同的對象做區分,歸類的
為了解決不同的對象存儲相同數據的問題。所以不僅僅對象是”容器“,類也是”容器“。類用來存放同類對象的他們共有的數據和功能的。所以類也是對象。
將同類對象共有的數據提取到類里面,不是說私有的數據變為共有的了。本質上還是它們自己的。這樣做的目的,僅僅是為了歸類和節省空間。
在查找數據時,先找對象本身的數據,沒有才會到類里面去找。
2.2 類怎么定義
類名采用駝峰體命名法,首字母大寫
類的子代碼在定義階段就會直接運行。所以類的名稱空間在定義階段就會產生
2.3 類的屬性訪問
Hero.__dict__會得到一個字典,這個字典里面存放的就是類名稱空間里面的名字
可以使用字典取值的方式,來獲取類的屬性和方法?
也可以通過類名.屬性名的方式來對類的屬性進行訪問,但是本質上就是通過字典取值。這樣只不過是python做的優化罷了。?
3.對象
3.1 對象的產生
類造出來之后,他本質上是為了將所有的同類對象提取出來。類里面存放的東西只是為了減少計算機資源的浪費,本質上還是屬于具體對象的。所以將數據和功能提取到類里面之后還沒有結束,還是需要建立對象和類之間的關聯。讓類順著這個關聯,還可以訪問到原來屬于它那部分的數據和功能。
只要基于類加括號就會創造一個對象,并且會將類和對象之間建好關聯。調好之后就會返回值就是一個英雄的對象。
類加括號的調用,并不會觸發類子代碼的運行。類的子代碼在定義階段就已經執行過了。調用的時候只會幫我們造好一個對象,然后建立好類與對象之間的關聯,并不會執行類的子代碼。調用一次就會幫我們創建一個對象。
對象里面也存在一個__dict__,里面存放的就是對象的屬性。現在三個字典里面都是空的。因為我們只是基于類創建了對象,并沒有往對象里面添加內容。但是并不意味著這些對象里面沒有任何屬性,因為類里面有。
3.2 對象屬性的添加
?通過對象.屬性的方式來增加,修改,訪問屬性,本質上都是通過字典的方式來操作屬性的
hero1_obj.name = '魯班七號'
hero1_obj.speed = 450
hero1_obj.hp = 3000
hero1_obj.atk = 100
print(hero1_obj.__dict__)
print(hero1_obj.hero_work)hero2_obj.name = '后羿'
hero2_obj.speed = 460
hero2_obj.hp = 3100
hero2_obj.atk = 110
print(hero2_obj.__dict__)
print(hero2_obj.hero_work)hero3_obj.name = '虞姬'
hero3_obj.speed = 470
hero3_obj.hp = 3200
hero3_obj.atk = 120
print(hero3_obj.__dict__)
print(hero3_obj.hero_work)
問題:這樣就出現了一個問題,給對象添加屬性的過程都是一樣的并且重復的,太過煩瑣了。
3.3 __init__方法
只要我們在類里面定義了__init__方法,這個方法就會在類被調用的時候,自動執行。
python在調用類創建對象的時候,會自動調用類下面的__init__,并且將創建的對象自動傳遞進來(現在這個對象還是空的,沒有任何自己獨有的屬性)
調用類的過程
- 創建空對象
- 調用__init__方法,同時將空對象,以及調用類時候括號里面傳遞的參數,一同傳遞給__init__方法。
- 返回初始化之后的對象(注意這個對象并不是__init__返回的,返回對象是類的底層做的一些事情)
class Hero:hero_work = '射手'count = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def get_hero_info(self):print(f'英雄屬性:名字:{self.name} 移速:{self.speed} 'f'生命值:{self.hp} 攻擊力:{self.atk}')def set_hero_speed(self, speed_plus):self.speed += speed_plusdef buy_equipment(self, e_name):self.equipment.append(e_name)# 實例化
hero1_obj = Hero('魯班七號', 450, 3000, 100) # Hero.__init__(空對象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)
3.4 屬性查找順序
我們現在調用類產生對象,就會產生對象自己的名稱空間,里面放的也是對象的獨有屬性.
類里面放的是對象的共有屬性
對象查找屬性的時候,一定是先在自己的名稱空間里面找,里面沒有,才回到它所屬類的名稱空間里面找.
# _*_ coding utf-8 _*_
# george
# time: 2024/9/19下午5:28
# name: attribution.py
# comment:
class Hero:hero_work = '射手'count = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def get_hero_info(self):print(f'英雄屬性:名字:{self.name} 移速:{self.speed} 'f'生命值:{self.hp} 攻擊力:{self.atk}')def set_hero_speed(self, speed_plus):self.speed += speed_plusdef buy_equipment(self, e_name):self.equipment.append(e_name)# 實例化
hero1_obj = Hero('魯班七號', 450, 3000, 100) # Hero.__init__(空對象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)hero3_obj.hero_work = "法師"print(Hero.hero_work)
print(hero1_obj.hero_work)
print(hero2_obj.hero_work)
print(hero3_obj.hero_work)
3.5 數據屬性特點
我們只要修改了類里面的數據屬性,通過這個類實例化的所有對象,都是可以感知到這個變化的.
需求:每次實例化一個對象,count就+1
通過Hero.count來修改類的屬性之后,所有的對象都可以感知此變化
# _*_ coding utf-8 _*_
# george
# time: 2024/9/19下午5:28
# name: attribution.py
# comment:
class Hero:hero_work = '射手'count = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def get_hero_info(self):print(f'英雄屬性:名字:{self.name} 移速:{self.speed} 'f'生命值:{self.hp} 攻擊力:{self.atk}')def set_hero_speed(self, speed_plus):self.speed += speed_plusdef buy_equipment(self, e_name):self.equipment.append(e_name)# 實例化
hero1_obj = Hero('魯班七號', 450, 3000, 100) # Hero.__init__(空對象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)hero3_obj.hero_work = "法師"print(Hero.hero_work)
print(hero1_obj.hero_work)
print(hero2_obj.hero_work)
print(hero3_obj.hero_work)
3.6 綁定方法
3.6.1 類的函數屬性
類的數據屬性是共享給對象使用的,只要類的數據屬性發生變化,對象是能夠立馬感知到的
對于類的函數屬性,類本身是可以使用的.通過類來調用,要嚴格按照函數的用法來,有幾個參數傳遞幾個參數.
3.6.2 對象調用類的函數屬性?
類里面定義的函數,主要是給對象去用的,而且是綁定給對象使用的,雖然所有的對象都是指向相同的功能,但是綁定到不同的對象,就會變成不同的綁定方法
類的函數屬性綁定給對象使用之后,就不再是普通的函數,而是綁定方法.我們使用誰來調用綁定方法,綁定方法就會把水作為第一個參數傳遞進去.我們在實例化對象的時候,自動觸發的也是對象的init綁定方法,所以也不需要傳遞對象本身.?
3.6.3? 類函數的形參個數
正是因為有綁定方法的存在,所以我們在類里面定義函數的時候,就至少需要一個形參.即便你的函數里面不需要任何參數.因為我們在通過對象調用這個函數的時候,綁定方法會將對象傳遞進來,所以需要一個形參來接收綁定方法傳進來的對象.
3.6.4 類函數的self
從規范上面來說,第一個參數我們應該寫成self,這個self沒有任何特殊的地方,僅僅是一個變量名罷了
3.6.5 快捷鍵
快捷鍵:alt+j,按一次就會選中一個相同變量名
3.6.6 綁定方法的方便之處
這就是綁定方法厲害的地方,誰調用的綁定方法處理的就是誰的數據.
# _*_ coding utf-8 _*_
# george
# time: 2024/9/19下午5:28
# name: attribution.py
# comment:
class Hero:hero_work = '射手'count = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def get_hero_info(self):print(f'英雄屬性:名字:{self.name} 移速:{self.speed} 'f'生命值:{self.hp} 攻擊力:{self.atk}')def set_hero_speed(self, speed_plus):self.speed += speed_plusdef buy_equipment(self,e_mp):self.equipment.append(e_mp)# 實例化
hero1_obj = Hero('魯班七號', 450, 3000, 100) # Hero.__init__(空對象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)hero1_obj.buy_equipment("末世")
hero2_obj.buy_equipment("大棒")
hero3_obj.buy_equipment("復活甲")
print(hero1_obj.equipment)
print(hero2_obj.equipment)
print(hero3_obj.equipment)
3.6.7 python一切皆對象
基本數據類型也是類
我們定義好一個字符串,一個列表,字典的時候,其實就是在造對象.我們通過造出來的對象.他們的方法,其實就是對象在調用它們的綁定方法
都是存在self的?
x= "aaa",其實背后是觸發了一個功能叫做x = str("aaa"),這其實就是類的實例化的過程,x就是通過str實例化出來的一個對象.只不過是python給我們優化了基本數據類型的實例化過程.直接使用"",就可以造出字符串對象.?
4.面向對象三大屬性
面向對象的三大屬性,封裝,繼承和多態
4.1 封裝
封裝是面向對象最核心的一個特性,封裝就是將一堆東西放到容器里面,然后封起來,也就是前面一直在說的整合?
4.1.1?隱藏屬性
特點:
- 隱藏的本質,只是一種改名操作
- 對外不對內
- 這個改名操作,只會在類的定義階段檢查類的子代碼的時候執行一次,之后定義的__開頭的屬性都不會改名
我們封裝的時候可以將屬性隱藏起來,讓使用者沒有辦法直接使用,而不是讓使用者不能使用。
我們無論是隱藏數據屬性還是函數屬性,直接在變量名前面加__就可以實現。
class Test:__x = 10def __f1(self):print("f1")
print(Test.x)
我們只要在屬性前面加上__,在類的定義階段,__開頭的屬性名字就會被加上前綴_?
?所以__x =》_Test__x,__f1=>_Test_f1.所以pyhton的這種隱藏機制,并沒有做到真正意義上的隱藏,其實就是一種改名操作,我們其實只要知道了類名和屬性名,就可以拼接出變形之后的名字,然后對她進行訪問
對外不對內的含義就是,我們在類的外部通過__x是訪問不到隱藏屬性的,但是在類的內部通過__x和__f1是可以訪問到的。
class Test:__x = 10def __f1(self):print("f1")def f2(self):print(self.__x)print(self.__f1)obj = Test()
obj.f2()
為什么在類的外部不可以訪問隱藏屬性,在類的內部卻是可以訪問的呢??
在類的定義階段,檢查類子代碼語法的時候,對類內部__開頭的名字統一進行了改名操作。在f2里面的__x也被改名了。這也是類內內部可以被訪問的原因,雖然看起來還是__x,但是其實已經被改名字了。
但是改名字,只是在類的定義階段,檢查子代碼語法的時候發生的。我們在類的外面,無論是通過類來調用還是通過對象來調用。這都是類的調用階段。類的調用階段,類的子代碼早就運行完畢了。該改的名字也早就修改完畢了。所以還是通過__x肯定是訪問不到的。這也是在類的外部隱藏屬性不能被訪問的原因。
這個改名操作,只會在類的定義階段檢查類的子代碼的時候執行一次,之后定義的__開頭的屬性都不會改名
我們要在設計的時候考慮什么屬性不能直接對外界訪問,而不是在使用的時候做這件事情。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/9/20 5:49
# @Author : George
class Test:__x = 10def __f1(self):print("f1")def f2(self):print(self.__x)print(self.__f1)Test.__y = 20
print(Test.__dict__)
基于對象的屬性隱藏?
class Test:def __init__(self, name, age):self.__name = nameself.__age = agedef f1(self):print(self.__name, self.__age)test1 = Test('lisi', 30)
print(test1.__dict__)
test1.f1()
4.1.2? 為何隱藏屬性
屬性分為數據屬性和函數屬性
- 隱藏數據屬性
類的設計者將屬性設計出來之后,就是要給使用者用的,只是不想要使用者直接使用罷了。那就需要給這個屬性開兩個接口,讓使用者通過接口來使用這兩個屬性。
我們將屬性隱藏起來之后,使用者就沒辦法隨便修改這個屬性了。使用者想改的話,必須通過我設計的接口來進行修改。
隱藏數據屬性,并不是不讓外界使用。而是利用隱藏屬性,對外不對內的特點。讓類的設計者能夠嚴格控制,類的使用者對于這個屬性的各種操作
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/9/20 6:19
# @Author : George
class Test:def __init__(self, name, age):self.__name = nameself.__age = agedef get_name(self):return self.__namedef get_age(self):return self.__agedef set_age(self, new_age):if type(new_age) is not int:print("你個傻子,年齡必須是整數")returnif not 0 < new_age < 150:print("你個傻子,年齡必須在1-150歲之間")returnself.__age = new_agetest1 = Test('lisi', 30)
age = test1.get_age()
print(age)
test1.set_age(100)
age = test1.get_age()
print(age)
- 隱藏函數屬性
隱藏函數屬性可以隔離復雜度,讓使用者更加方便的使用設計者設計的類。
class Servant:def __run(self):print('跑起來')def __pay(self):print('給錢')def __take_bun(self):print('拿包子')def buy_bun(self): # 買包子self.__run()self.__pay()self.__take_bun()self.__run()ser = Servant()
ser.buy_bun()
4.1.3 類裝飾器property
隱藏屬性的目的是為了開接口給別人使用,然后我們在接口之上附加邏輯.對于一個數據屬性來說最多只有四種操作"增刪改查",對于已經存在的屬性而言只有"刪改查"三種操作.?作為設計者已經開好接口,設計好了類.但是對于使用者就有難度了.
因為age聽起來是一個數據屬性.平時刪改查age應該是,而不是調用函數接口來實現
obj = Test('lisi', 30)
del obj.age
obj.age = 100
print(obj.age)
obj = Test('lisi', 30)
age = obj.get_age()
obj.set_age(100)
obj.del_age()
如何實現讓使用者用起來是數據屬性,并且背后觸發的還是我們設置的接口呢?
property:Pyhton內置裝飾器(類實現的裝飾器)
(裝飾器的碼以及調作用是在不修改被裝飾對象源代用方式的前提下,給被裝飾對象添加新功能的可調用對象),裝飾器不一定非要是函數,只要是一個可調用對象就可以了.類可以加括號調用,所以說類也可以實現裝飾器的功能.
property的作用:用來把綁定給對象的方法偽裝成一個數據屬性
class Test:def __init__(self, name, age):self.__name = nameself.__age = agedef get_name(self):return self.__name@property # get_age = property(get_age)def get_age(self):return self.__agedef set_age(self, new_age):if type(new_age) is not int:print("你個傻子,年齡必須是整數")returnif not 0 < new_age < 150:print("你個傻子,年齡必須在1-150歲之間")returnself.__age = new_agedef del_age(self):del self.__ageobj = Test('lisi', 30)
print(obj.get_age)
裝飾器如此使用還是太麻煩了,所以還是得使用語法糖?
class Test:def __init__(self, name, age):self.__name = nameself.__age = agedef get_name(self):return self.__name# @property # get_age = property(get_age)def get_age(self):return self.__agedef set_age(self, new_age):if type(new_age) is not int:print("你個傻子,年齡必須是整數")returnif not 0 < new_age < 150:print("你個傻子,年齡必須在1-150歲之間")returnself.__age = new_agedef del_age(self):del self.__ageage = property(get_age,set_age,del_age)obj = Test('lisi', 30)
print(obj.age)
obj.age = 40
print(obj.age)
?使用語法糖
class Test:def __init__(self, name, age):self.__name = nameself.__age = agedef get_name(self):return self.__name# @property # get_age = property(get_age)@propertydef age(self):return self.__age@age.setterdef age(self, new_age):if type(new_age) is not int:print("你個傻子,年齡必須是整數")returnif not 0 < new_age < 150:print("你個傻子,年齡必須在1-150歲之間")returnself.__age = new_age@age.deleterdef age(self):del self.__age# age = property(get_age,set_age,del_age)obj = Test('lisi', 30)
print(obj.age)
obj.age = 100
print(obj.age)
?4.2 繼承
4.2.1 繼承介紹
- 繼承是一種創建新類的方式,通過繼承創建的類被稱之為子類,被繼承的類被稱為父類(基類)
- python支持多繼承
- 查看一個類的父類是誰:類.__bases__?
class Parent1(object):x = 10passclass Parent2(object):passclass Child1(Parent1): # 單繼承passclass Child2(Parent1, Parent2): # 多繼承passprint(Child1.__bases__)
print(Child2.__bases__)
在python2里面存在新式類和經典類的區分。
新式類:繼承了object類的子類(object類是python的內置類),以及繼承了這個類的子子孫孫類。
經典類:沒有繼承了object類的子類(object類是python的內置類),以及繼承了這個類的子子孫孫類。
所以pyhton2里面默認都是經典類,只有繼承了object才會變為新式類。
在python3里面默認繼承的就是object,python3里面所有的類都是新式類。
?如果想讓自己的代碼可以兼容python2的話,就可以讓類繼承object.這樣我們的代碼在python2中運行,它依然還是新式類。
4.2.2 繼承的特性
繼承的特性是遺傳
子類會遺傳父類的屬性:子類繼承父類之后,父類有的屬性,子類都可以訪問的到。兒子自己有就用自己的,兒子沒有的就找老爸要。
繼承就是用來解決代碼冗余問題的:可以將多個類的共同的部分提取到一個父類里面,這樣共同的屬性,只是需要存儲一份即可。
- 類是為了解決對象有共同屬性的問題。
- 繼承是為了解決類有共同屬性的問題
object是python的內置類,類存儲的是數據和功能,這些數據和功能都是python是認為比較有用的。python將其封裝到了object類里面,讓所有的新式類全部都來繼承它。
在python里面所有的新式類,只要往上找,最后一定是繼承了object,所以這些新式類,一定都有object提供的數據和功能。比如:__dict__
4.2.3 多繼承的優缺點
優點:
- 一個子類可以遺傳多個父類的屬性
缺點:
- 多繼承違背了人的思維習慣
- 多繼承會讓代碼的可讀性變差
如果必須要使用多繼承時,可以使用MixIns機制(它是一種編程規范,本質上還是多繼承),使用它可以使用多繼承的優點,也可以避免代碼可讀性變差的問題。MixIns才是pyhton多繼承的正確打開方式。
4.2.4 繼承的實現
抽象:就是抽取它們共同的部分
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/9/21 3:12
# @Author : George
class Human:star = 'earth'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass Chinese(Human):# star = 'earth'nation = 'China'def speak_chinese(self):print(f'{self.name}在說普通話')class American(Human):star = 'earthxxx'nation = 'America'def speak_english(self):print(f'{self.name}在說英語')dy_obj = Chinese('董永', 18, '男')
print(dy_obj.__dict__)
print(dy_obj.nation)
print(dy_obj.star)
dy_obj.speak_chinese()iron_man_obj = American('iron_man', 19, '男')
print(iron_man_obj.__dict__)
print(iron_man_obj.nation)
print(iron_man_obj.star)
iron_man_obj.speak_english()
?4.2.5 派生
- 子類派生父類沒有的屬性
- 子類存在和父類同名的屬性,優先以子類自己的屬性為主
- 子類造一個新的功能,這個新的功能并不是完全覆蓋父類的功能,而是在父類功能的基礎上進行的擴展。
現在的中國人屬性上面要添加一個賬戶余額,重寫父類的__init__
4.2.6 單繼承屬性查找
對象->類->父類->.....object,都找不到直接報錯?
- 通過對象訪問屬性的時候,會先在對象里面找,對象里面沒有才會到對象的類里面找。對象的類也沒有就去父類里面找
- 對象在調用綁定方法的時候,哪個對象調用的綁定方法,就會將哪個對象傳遞進去。所以self.f1()的這個self是obj
class Test1:def f1(self):print('Test1.f1')def f2(self):print('Test1.f2')self.f1()class Test2(Test1):def f1(self):print('Test2.f1')obj = Test2()
obj.f2()
如果想要打印Test1.f2,Test1.f1.可以基于類的隱藏屬性來實現?
class Test1:def __f1(self): # _Test1__f1print('Test1.f1')def f2(self):print('Test1.f2')self.__f1() # _Test1__f1class Test2(Test1):def __f1(self): # _Test2__f1print('Test2.f1')obj = Test2()
obj.f2()
4.2.7 多繼承屬性查找
4.2.7.1 菱形問題(鉆石問題)
對象訪問f1,先在對象里面找,對象里面沒有在類D里面查找,類D里面也沒有,在父類里面找。但是D的父類有兩個(B,C)?。應該先找哪一個??
pyhton繼承的實現原理:查找屬性的時候,會根據一個MRO(Method Resolution Order,方法解析順序)列表作為依據,它是通過C3算法來實現的。每定義一個類,python都會基于C3算法,算出一個MRO列表。MRO列表就是一個簡單的所有基類的線性順序列表。
我們查找屬性的順序,就是基于這個MRO列表的順序來查找的。我們通過對象查找屬性,對象是沒有MRO列表的,只有它的類才有。
class A:def f1(self):print('A.f1')class B(A):def f1(self):print('B.f1')class C(A):def f1(self):print('C.f1')class D(B, C):def f2(self):print('D.f2')print(D.mro())obj = D()
obj.f1()
D的MRO列表
4.2.7.2 非菱形查找?
- 對于菱形繼承來說,新式類和經典類的屬性查找順序是不一樣的。?
- 但是對于非菱形繼承來說,新式類的經典類的屬性查找順序都是一樣的
- python2里面也沒有提供mro這個屬性
- 在代碼里面要么全是新式類,要么全是經典類,不能一部分是新式類,一部分是經典類
屬性查找順序:
F->C->A->D->B->E->object?
class A:def f2(self):print('A.f1')class B:def f1(self):print('B.f1')class C(A):def f2(self):print('C.f1')class D(B):def f1(self):print('D.f1')class E:def f1(self):print('E.f1')class F(C, D, E):def f2(self):print('F.f2')print(F.mro())obj = F()
obj.f1()
F類的MRO列表
4.2.7.3 菱形查找
對于非菱形繼承,新式類和經典類的屬性查找是一樣的,但是對于菱形繼承.屬性的查找方式就不一樣了.?
- 經典類:深度優先查找,F->C->A->Z->D->B->E,找第一條分支的時候,就要找到共同的父類
- 新式類:廣度優先查找,F->C->A->D->B->E->Z,找完最后一條分支之后,才找最后的父類
class Z:def f1(self):print('Z.f1')class A(Z):def f2(self):print('A.f1')class B(Z):def f2(self):print('B.f1')class C(A):def f2(self):print('C.f1')class D(B):def f2(self):print('D.f1')class E(Z):def f2(self):print('E.f1')class F(C, D, E):def f2(self):print('F.f2')print(F.mro())obj = F()
obj.f1()
4.2.8 MixIns機制
多繼承可以最大程度的幫組我們重用代碼,但是多繼承會使得代碼的可讀性變差,并且它還違背了人的思維習慣.在人的思維習慣里面,繼承應該表達的是 什么"是"什么的關系
在使用多繼承的時候需要注意以下問題:
- 多繼承的結構不要太過復雜
- 要滿足什么"是"什么的關系
使用MixIns機制可以一定程度提高我們代碼的可讀性,并且可以讓多繼承滿足什么"是"什么的關系
原理:
它沒有改變多繼承的本質,只是通過命名規范來達到提升代碼可讀性的效果.它規定我們可以在需要的父類后面加上一個后綴名MixIn,讓別人閱讀代碼的時候知道這個父類只是用于混入功能的,而不是作為真正的父類.
使用MixIns注意:
用了MixIns之后,從語義層面上我們就可以區分出"真正"的父類..但是本質上面還是兩個父類.我們在
- 使用MixIn的時候,它必須表示某一種功能,而不是某一種事物.我們來表達"是"這種關系的類,才能是一種事物.
- 對于Mixin類的命名,一般以mixin,able,ible作為后綴
- mixin類的責任必須單一(不要將所有的功能都往mixin里面放置)
- mixin類里面功能的實現不能依賴于子類(mixin類里面不應該調用子類的功能)
- 我們在使用mixin表達多繼承的時候,表達"是"這種關系的類一定要放在最后,并且只應該繼承一個.但是可以繼承多個mixin類
- 一個類繼承的mixin類越多,代碼的可讀性就越差.并且繼承的層級太多,閱讀代碼越容易被繞暈
class Fowl: # 家禽類passclass SwimMixIn:def swimming(self):passclass Chicken(Fowl): # 雞passclass Duck(SwimMixIn, Fowl): # 鴨passclass Goose(SwimMixIn, Fowl): # 鵝pass
4.2.9 super
對于派生的第三種情況,子類重用父類的方法,但是不是單純的重用,而是在父類方法的基礎上進行拓展。
4.2.9.1 super嚴格依賴繼承
- 這是不依賴繼承關系,直接使用父類的類名調用__init__方法。
- super是嚴格依賴繼承關系,實現的重用父類的代碼?
4.2.9.2 super實現的原理?
?在這里調用super會得到一個特殊的對象,這個對象會參照self所屬類的mro列表。從super當前類的所處位置的下一個類開始查找屬性.
class Human:star = 'earth'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass Chinese(Human):# star = 'earth'nation = 'China'def __init__(self, name, age, gender,account):# Human.__init__(self, name, age, gender)super(Chinese,self).__init__(name, age,gender)self.account = accountdef speak_chinese(self):print(f'{self.name}在說普通話')class American(Human):star = 'earthxxx'nation = 'America'def speak_english(self):print(f'{self.name}在說英語')dy_obj = Chinese('董永', 18, '男',1000)
print(Chinese.mro())
- self所屬類的mro列表,就是Chinese.mro()
- super當前所處類是Chinese,下一個位置就是Human類開始查找屬性(查找init)
4.2.9.3 super 自動傳self?
使用super調用方法的時候,super也會自動傳self.
4.2.9.4 super python2-3間的區別
super只能用在新式類里面,在新式類里面還有python2和python3的區別
- 在python2里面必須要將super(chinese,self)兩個參數加上
- 在python3里面可以直接super().__init__
- 如果是為了兼容python2代碼可以直接將兩個參數加上
- 為了讓python2和python3之間的代碼兼容可以讓父類都繼承object變為新式類,同時讓super(chinese,self)完整形式來創建對象
經典類使用super會報錯,因為經典類不存在mro列表
class Human:star = 'earth'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass Chinese(Human):# star = 'earth'nation = 'China'def __init__(self, name, age, gender,account):# Human.__init__(self, name, age, gender)super(Chinese,self).__init__(name, age,gender)self.account = account# def speak_chinese(self):# print(f'{self.name}在說普通話')class American(Human):star = 'earthxxx'nation = 'America'# def speak_english(self):# print(f'{self.name}在說英語')dy_obj = Chinese('董永', 18, '男',1000)
print(Chinese.mro())
print(dy_obj.__dict__)
新式類python2里面的效果:
class Human(object):star = 'earth'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass Chinese(Human):# star = 'earth'nation = 'China'def __init__(self, name, age, gender,account):# Human.__init__(self, name, age, gender)super(Chinese,self).__init__(name, age,gender)self.account = account# def speak_chinese(self):# print(f'{self.name}在說普通話')class American(Human):star = 'earthxxx'nation = 'America'# def speak_english(self):# print(f'{self.name}在說英語')dy_obj = Chinese('董永', 18, '男',1000)
print(Chinese.mro())
print(dy_obj.__dict__)
4.2.9.5 super找屬性不是看的父類
super查找屬性,參考的是對象所屬類的mro列表,從super當前所在類的下一個類開始查找屬性。絕對不是我們看到的父類。?
class A:def f1(self):print('A.f1')super(A,self).f1()class B:def f1(self):print('B.f1')class C(A, B):passobj = C()
obj.f1()
print(C.mro())
4.3 多態
4.3.1 多態概念
多態是一種編程思想,在程序里面表達多態使用的就是繼承,多態只是在繼承的背景下演化出來的一個概念.
多態性:可以在不考慮對象具體類型的情況下,而直接使用對象.
多態性的好處:
我們只需要學習他們統一父類里面的方法就可以了,只要父類里面有,我們通過子類對象肯定也可以訪問.
統一了使用標準,以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用.這個好處本質上是父類帶給我們的.父類的作用就是統一所有子類的方法,父類統一子類的方法之后,我們才有了統一接口的可能性.
class Car:def run(self):print('開始跑', end=' ')class Benz(Car):def run(self):super().run()print('加98號汽油')class Lx(Car):def run(self):super().run()print('充電')class Auto(Car):def run(self):super().run()print('加92號汽油')car1 = Benz()
car2 = Lx()
car3 = Auto()
car1.run()
car2.run()
car3.run()def drive_car(car):car.run()drive_car(car1)
drive_car(car2)
drive_car(car3)
在python里面我們用到的所有數據類型都是類,我們所有的數據都是一個個對象.
len()就可以稱之為一個接口,它可以len()各個類型的數據.為什么它可以達到這個統一的使用方法?
len("abc")
len([1,2.3])
len({"a":1,"b":2})
print("abc".__len__())
print([1, 2, 3].__len__())
print({"a": 1, "b": 2}.__len__())
它類似于前面的drive_car功能,我們可以傳不同類型的對象.它們下面都有一個__len__()方法.這就是多態性的體現,不同類型的都有共同的方法名.這樣我們就可以定義一個統一的接口my_len().只要傳進來的obj對象統一了標準,都有__len__方法,那么我們就可以使用這個接口
def my_len(obj):return obj.__len__()print(my_len("abc"))
print(my_len([1, 2.3]))
print(my_len({"a": 1, "b": 2}))
但是在python里面多態不是這么使用的,python推崇的不是繼承的方式表達多態.?
4.3.2?鴨子類型
父類的作用就是用來統一標準的,讓子類都有run這個功能。如果一開始不繼承父類,我在一開始定義這三個類方法命名的時候,按照統一的方法規范來命名,都有run方法。這樣我們即便是不使用繼承,也能夠達到統一標準的目的。
python很多地方只是從規范上面規定,并不會限制我們。我們即便沒有繼承父類,只要我們定義類的時候按照統一的標準來。也能夠達到統一規范的效果。
繼承是一種耦合思想的表現,我們不用繼承就達到了一種解耦合的思想
所以在python里面實現多態,可以不依賴于繼承,只要定義的時候,讓他們長的像就可以了,這就是python推崇的鴨子類型。
linux一切皆文件,就是多態性的體現。實現統一的標準。
# 鴨子類型
# linux:一切皆文件class Disk:def read(self):print('Disk.read')def write(self):print('Disk.write')class Memory:def read(self):print('Memory.read')def write(self):print('Memory.write')class Txt:def read(self):print('Txt.read')def write(self):print('Txt.write')print('Txt.write')
4.3.3 抽象基類
如果非要使用父類來達到規范子類的效果,可以引入抽象基類的概念。
我們在繼承的背景下,來實現多態性時,父類的方法更多的就不是來實現功能的,而是主要用來規范子類標準的。
可以通過抽象基類,讓父類強制子類遵循父類的標準。
- 導入模塊abc(abstructmethod)
-
父類繼承metaclass=abc.ABCMeta,父類就變為了抽象基類
-
run()加上裝飾器,@abc.abstractmethod
import abcclass Car(metaclass=abc.ABCMeta):@abc.abstractmethoddef run(self):passclass Benz(Car):def run(self):passclass Lx(Car):def run(self):passclass Auto(Car):def run(self):passcar1 = Benz()
car2 = Lx()
car3 = Auto()
car1.run()
car2.run()
car3.run()
加上這個裝飾器之后,子類如果不寫父類規定的run方法。不用子類沒有問題,一用子類就會報錯。
import abc
class Car(metaclass=abc.ABCMeta):@abc.abstractmethoddef run(self):pass
class Benz(Car):pass
class Lx(Car):pass
class Auto(Car):pass
car1 = Benz()
car2 = Lx()
car3 = Auto()
car1.run()
car2.run()
car3.run()
目的:
讓父類成為一個規范標準的作用,所有繼承我這個抽象基類的子類。必須定義抽象基類實現的規定的方法。
注意:
抽象基類不能直接用于實例化,它的作用只是用來規范標準的。
?5.類方法與靜態方法
5.1 類方法
前面學的綁定方法是綁定給對象使用的,還有一種方法是專門綁定給類使用的。綁定給對象的方法會自動將對象傳遞進去,綁定給類的方法,會自動將類傳遞進去。
類方法的作用就是自動將類傳遞進去,通過類能做的事情其實就是實例化對象。通過它我們只是有了一種新的造對象的方式。
還有一種情況就是使用將類作為參數進行傳遞時,也是可以使用類方法的
- 在類方法前面加上一個裝飾器,@classmethod
- 給函數里面傳遞類,cls,參數使用class是規范要求
import settingsclass GetIp:def __init__(self, ip, port):self.ip = ipself.port = portdef get_ip(self):print(self.ip, self.port)@classmethoddef f1(cls):obj = cls(settings.IP, settings.PORT)return objobj = GetIp.f1()
print(obj.__dict__)
5.2 靜態方法
?類里面的函數有兩種用途,一種是綁定給對象使用的,一種是綁定給類使用的。如果函數子代碼需要使用到對象,這個函數就需要綁定給對象。如果函數子代碼需要使用到類,這個函數就需要綁定給類。
還有一種方法不需要用到類,也不要用到對象。它就是一個獨立的功能。就是靜態方法,對象和類都是可以調用它的,只是沒有自動傳參的功能。
在函數上面加上 @staticmethod
6.反射機制
6.1 何為反射機制
?python是一門強類型動態解釋型語言,反射機制則是動態語言的關鍵。
動態語言,在程序里面定義變量的時候,不需要指定數據類型。只有執行代碼賦值的時候,才識別它的數據類型。
靜態語言,在定義變量的時候,就要指定它的數據類型。
只要是動態語言就一定會有反射機制
定義:在程序運行過程中,動態獲取對象信息,以及動態調用對象方法的功能。
程序只要開始運行,就是計算機來識別代碼,和程序員就沒有關系了。這時候程序獲取到一個對象,程序如何知道有哪些屬性??如果程序不知道對象存在哪些屬性,就沒有辦法調用。所以需要讓程序在運行過程中,自己能動態獲取到對象屬性和方法
我們 可以通過__dict__來判斷對象有沒有屬性,但是有局限性,不是所有的對象都是存在__dict__的。
6.2 反射的實現
python提供的內置函數dir(),可以查看一個對象下面存在哪些屬性?
class Human:def __init__(self, name, age):self.name = nameself.age = agedef info(self):print('name:', self.name, 'age:', self.age)obj = Human('張大仙', 73)print(dir(obj))
列表里面的都是字符串類型的屬性,所以需要通過字符串反射到真正的屬性上面,從而得到屬性的值
?提供了四個函數通過字符串來操作屬性
這里的obj不僅可以是對象,也可以是類,當然后面放置的就要是類的屬性了。
反射就是在我們不知道對象是什么的情況下,讓我們的程序程序能動態的分析出來我們的對象里面有什么 ,反射是一種動態分析的能力。
6.3 反射的案例
輸入的是什么就執行什么方法,但是要判斷對象是否存在這個方法,并且直接輸入的內容是字符串。
通過getattr判斷輸入的內容是否是對象存在的屬性,如果存在就通過字符串獲取該屬性值。如果不存在,直接調用默認的self.warning函數。
class Ftp:def put(self):print('正在上傳數據。。。')def get(self):print('正在下載數據。。。')def interact(self):opt = input('>>>')getattr(self, opt, self.warning)()def warning(self):print('功能不存在!')obj = Ftp()
obj.interact()
7.內置方法
以__開頭__結尾的方法,會在滿足條件的時候自動執行。
作用:為了定制化我們的對象或是類
__init__:在實例化對象的時候自動執行的,作用就是給對象定制獨有的屬性。
__str__:調用print方法的時候自動執行
打印列表的時候,打印的是列表的內容。打印對象的時候,打印的是對象的內存地址。因為列表是python的內置數據類型,python為了讓列表打印的時候,容易觀看特意做了定制化的處理。自定的類沒有考慮到這個,所以打印出來的是內存地址。
__del__:在刪除對象的時候,會先執行它。
作用:計算機的資源有兩種,一種是應用程序的資源,一種是操作系統的資源。我們的對象現在有一個屬性f,引用到了系統的資源。當我的對象被刪除之后,python回收的只是應用程序的資源,操作系統資源python是不管的。所以要在對象刪除之前告訴操作系統,對象已經不存在了,回收操作系統資源。
class Human:def __init__(self, name, age):self.name = nameself.age = agedef __str__(self):return f'<{self.name}: {self.age}>'def __del__(self):self.f.close()obj = Human('張大仙', 73)
print(obj.__str__())
print(obj) # <張大仙:73>
8.元類
8.1 元類定義
在python中一切皆是對象,類也是對象。元類就是用來實例化產生類的類。
元類 --> 實例化 --> 類(Human)?--> 實例化 --> 對象(obj)
- 查看類的元類和查看對象的類是一樣的,可以通過type方法
- 通過class關鍵字定義的所有的類,以及內置的類,都是由內置元類type實例化產生的
class Human:def __init__(self, name, age):self.name = nameself.age = agedef info(self):print('name:', self.name, 'age:', self.age)# print(Human.__dict__)
# # 基于類創建了一個對象
obj = Human('張大仙', 73)print(type(obj))
print(type(Human))
print(type(str))
8.2 class分析
class關鍵字是如何一步步將類造出來的:
- 1.類名,class_name = 'Human'
- 2.基類,繼承自哪個父類,可以繼承多個父類 class_bases = (object,)
- 3.類的子代碼,類的子代碼本身就是一串字符串。類的子代碼會在定義階段執行,執行類的子代碼就會產生類的名稱空間。對應的就是一個類的字典 class_dict.類的子代碼運行過程中,會將產生的名字都丟到class_dict中
# # 3、執行類子代碼,產生名稱空間
class_dic = {}
class_body = '''
def __init__(self, name, age):self.name = nameself.age = age
def info(self):print('name:', self.name, 'age:', self.age)
'''
exec(class_body, {}, class_dic)
print(class_dic)
class關鍵字產生的名稱空間,比exec執行類的子代碼產生的名稱空間,多了更多的內置屬性。我們創建的名字空間,只存在類子代碼里面的名字?
- 4.調用元類?,元類是可以指定的,我們可以自定義元類。如果沒有自定義元類,使用的就是內置元類type.調用type將前面散步創建的參數傳遞進去。
Human不是類,type的返回值才是類。他的類名則是字符串格式的叫做class_name = 'Human'。前面使用class定義的Human,也只是變量名而已。變量名指向的值才是我們的類。
# # 4、調用元類
Human = type(class_name, class_bases, class_dic)
print(Human)
?class底層做的事情就是以上四步:獲取類名,獲取基類,獲取名稱空間,調用元類。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/10/16 0:39
# @Author : George
# # 1、類名
class_name = 'Human'
#
# # 2、基類
class_bases = (object,)
#
# # 3、執行類子代碼,產生名稱空間
class_dic = {}
class_body = '''
def __init__(self, name, age):self.name = nameself.age = age
def info(self):print('name:', self.name, 'age:', self.age)
'''
exec(class_body, {}, class_dic)
# print(Human.__dict__)
# print(class_dic)
#
#
# # 4、調用元類
Human = type(class_name, class_bases, class_dic)
obj = Human('張大仙', 73)
obj.info()
通過調用元類最后得到一個類,這個類就是元類實例化的對象。清楚了這個步驟之后,我們以后就可以針對性的對這些步驟做出定制化的操作.主要是第四步。在調用元類進行實例化類的時候,可以進行定制化的操作,比如將名稱空間里面的屬性隱藏掉,要求類名不能有下劃線。這些都是要求我們自定義元類。
8.3 自定義元類
- 自定義元類的話,必須使用關鍵字 metaclass,指定使用的自定義元類
- 只有繼承type的類才是元類,自定義元類的話,必須讓其繼承type
現在通過class造Human的時候也是四步,拿類名,拿基類,執行類子代碼拿名稱空間,調用元類,就是自定義的Mytype( Human = Mytype(class_name,class_bases,class_dict)),進行實例化,得到Human這個類
class Human(metaclass=Mytype):def __init__(self, name, age):self.name = nameself.age = agedef info(self):print("name:", self.name, "age", self.age)
現在Human是通過Mytype實例化得到的,Mytype實例化的步驟如下:
- # 1. 產生空對象Human
- # 2. 調用Mytype的__init__方法,同時將空對象,以及調用類時候括號里面傳遞的參數,一同傳遞給__init__方法.初始化對象Human
- # 3. 返回初始化好的對象Human
# 自定義元類
class Mytype(type):def __init__(self,class_name,class_bases,class_dict):print("Mytype.init")# Human = Mytype(class_name,class_bases,class_dict)
# 1. 產生空對象Human
# 2. 調用Mytype的__init__方法,同時將空對象,以及調用類時候括號里面傳遞的參數,一同傳遞給__init__方法.初始化對象Human
# 3. 返回初始化好的對象Humanclass Human(metaclass=Mytype):def __init__(self, name, age):self.name = nameself.age = agedef info(self):print("name:", self.name, "age", self.age)
8.4 元類的init?
進行定制化操作,也就是類的實例化的第二步,調用Mytype的__init__方法.
- 類名不能加下劃線
- 創建類的時候,必須添加文檔注釋
# 自定義元類
class Mytype(type):def __init__(self,class_name,class_bases,class_dict):if "_" in class_name:raise NameError("類名不能存在下劃線!")if not class_dict.get("__doc__"):raise SyntaxError("創建類的時候必須添加文檔注釋!")# Human = Mytype(class_name,class_bases,class_dict)
# 1. 產生空對象Human
# 2. 調用Mytype的__init__方法,同時將空對象,以及調用類時候括號里面傳遞的參數,一同傳遞給__init__方法.初始化對象Human
# 3. 返回初始化好的對象Humanclass Human(metaclass=Mytype):"""這是文檔注釋"""def __init__(self, name, age):self.name = nameself.age = agedef info(self):print("name:", self.name, "age", self.age)
8.5 元類的__new__
類的實例化第一步,產生空對象Human,進行定制化操作.
__new__是在__init__之前被調用的,也就是說,調用Mytype的__new__方法,產生一個空對象Human
# 自定義元類
class Mytype(type):def __init__(self, class_name, class_bases, class_dict):if "_" in class_name:raise NameError("類名不能存在下劃線!")if not class_dict.get("__doc__"):raise SyntaxError("創建類的時候必須添加文檔注釋!")def __new__(cls, *args, **kwargs):print(cls) # Mytype這個類本身print(args) # args,就是class機制第四步傳遞進來的參數,類名/基類/名稱空間print(kwargs)return super().__new__(cls, *args, **kwargs) # 在動底層代碼沒有自動傳參# Human = Mytype(class_name,class_bases,class_dict)
# 1. 產生空對象Human
# 2. 調用Mytype的__init__方法,同時將空對象,以及調用類時候括號里面傳遞的參數,一同傳遞給__init__方法.初始化對象Human
# 3. 返回初始化好的對象Humanclass Human(metaclass=Mytype):"""這是文檔注釋"""def __init__(self, name, age):self.name = nameself.age = agedef info(self):print("name:", self.name, "age", self.age)
8.6 元類的__call__
調用Mytype等價于調用__call__?,在__call__的內部就是做的這幾步
- 調用Mytype的__new__方法,產生一個空對象Human
- 調用Mytype的__init__方法,將及調用類時候括號里面傳遞的參數,一同傳遞給__init__方法.初始化對象Human
- 返回初始化好的對象Human
__call__()會在對象被調用的時候進行觸發.所以如果想把一個對象,做成一個可以加括號調用的對象,就可以在對象的類里面加一個__call__方法.
class Test:def __init__(self,name,age):self.name = nameself.age = agedef __call__(self, *args, **kwargs):print("Test.__call__")return "abc"obj = Test("張大仙",23)
res = obj() # => Test.__call__
print(res) # => abc
現在Human可以加括號調用,也就是說它的元類Mytype里面一定有一個__call__方法.
Human = Mytype(class_name,class_bases,class_dict),調用了內置元類type的__call__方法.
也就是說
- 對象()? ? ? ? --> 類內部的__call__
- 類()? ? ? ? ? ? --> 自定義元類里面的__call__
- 自定義元類()? --> 內置元類的__call__
Mytype里面的__init__和__new__是為了控制Human的產生,Human造好之后.
我們現在需要控制的是Human對象的產生(Human("張大仙",23) => 觸發了Mytype的__call__方法
- 調用Human的__new__方法
- 調用Human的__init__方法
- 返回初始化好的對象?
class Mytype(type):def __call__(self, *args, **kwargs):human_obj = self.__new__(self)self.__init__(human_obj, *args, **kwargs)# print(human_obj.__dict__) # => {'name': '張大仙', 'age': 23}# 定制化對象的屬性,所有的屬性名字前面加Hdict = {}for key in human_obj.__dict__:dict["H_"+key] = human_obj.__dict__[key]human_obj.__dict__ = dictreturn human_objclass Human(metaclass=Mytype):def __init__(self, name, age):self.name = nameself.age = agedef info(self):print("name:", self.name, "age", self.age)def __new__(cls, *args, **kwargs):# 造Human的空對象obj = super().__new__(cls)# 定制化操作return objobj = Human("張大仙", 23)
print(obj.__dict__)
8.7 元類的屬性查找
- 通過元類查找屬性,不會涉及到元類的查找
- 通過查找屬性的時候,會先順著父類的路線查找,和對象的查找屬性是一樣的.如果所有的父類都沒有,最后會去元類找
- 父類使用了自定義元類,所有繼承它的子類,都會使用這個自定義元類
9.單例模式
是為了保證某一個類,只能有一個實例對象存在.如果我們在寫代碼的過程中,使用同一個類產生了很多對象,而這些對象只是為了執行某一個或是某幾個功能.這種頻繁創建和銷毀對象的過程就會特別浪費內存資源.
單例模式:只允許內存中有一個實例,它減少了內存的消耗
單例模式的實現
9.1 模塊導入
python模塊就是一個天然的單例模式
# _*_ coding utf-8 _*_
# george
# time: 2025/2/17下午5:12
# name: setting.py
# comment:
class Human:def __init__(self,name,age):self.name = nameself.age = ageobj = Human("張大仙",73)
_*_ coding utf-8 _*_
# george
# time: 2025/2/17下午5:12
# name: 單例模式.py
# comment:from setting import obj
9.2 類裝飾器的實現
需要知道的是,裝飾器的作用就是@outer => recharge = outer(recharge)
現在recharge指向的是outer.wrapper的內存地址。現在就是wrapper調用兩次。
import timedef outer(func):print(111)def wrapper(*args, **kwargs):start = time.time()reponse = func(*args, **kwargs)end = time.time()print(end - start)return reponsereturn wrapperdef recharge(num):for i in range(num, 101):time.sleep(0.05)print(f"\r當前電量為:{'|' * i} {i}%", end="")print("電量已充滿")return 100recharge = outer(recharge)
recharge(10)
recharge(20)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2025/2/17 20:05
# @Author : Georgedef single_mode(cls):obj = Nonedef wrapper(*args, **kwargs):nonlocal objif not obj:obj = cls(*args, **kwargs)return objreturn wrapper@single_mode # Human = single_mode(Human)
class Human:def __init__(self, name, age):self.name = nameself.age = ageobj1 = Human("zs", 11)
obj2 = Human("li", 22)
# <__main__.Human object at 0x000001EA06B0F6E0> <__main__.Human object at 0x000001EA06B0F6E0>
print(obj1, obj2)
9.3 類綁定方法的實現
class Human:obj = Nonedef __init__(self,name,age):self.name = nameself.age = age@classmethoddef get_obj(cls,*args,**kwargs):if not cls.obj:cls.obj = cls(*args,**kwargs)return cls.objobj1 = Human.get_obj("張大仙",23)
obj2 = Human.get_obj("張大仙",24)
print(obj1,obj2)
9.4 __new__方式實現
class Human:obj = Nonedef __init__(self,name,age):self.name = nameself.age = agedef __new__(cls, *args, **kwargs):if not cls.obj:cls.obj = super().__new__(cls)return cls.objobj1 = Human("張大仙",23)
obj2 = Human("張大仙",24)
print(obj1,obj2)
9.5 元類
class Mytype(type):obj = Nonedef __call__(self, *args, **kwargs):if not self.obj:self.obj = super().__call__(*args, **kwargs)return self.objclass Human(metaclass=Mytype):obj = Nonedef __init__(self, name, age):self.name = nameself.age = ageobj1 = Human("張大仙", 23)
obj2 = Human("張大仙", 24)
print(obj1, obj2)
class Mytype(type):obj = Nonedef __call__(self, *args, **kwargs):if not self.obj:# self.obj = super().__call__(*args, **kwargs)self.obj = self.__new__(self)self.__init__(self.obj, *args, **kwargs)return self.objclass Human(metaclass=Mytype):obj = Nonedef __init__(self, name, age):self.name = nameself.age = ageobj1 = Human("張大仙", 23)
obj2 = Human("張大仙", 24)
print(obj1, obj2)
9.6 并發
Pyhton里面的None就是一個單例模式,代碼里面所有的None都是同一個對象.這是我們在用None進行判斷的時候使用的都是is None.因為都是同一個對象.
10.枚舉與拉鏈
- enumerate(li)返回的是一個迭代器
- enumerate(li,1) : 讓index從1開始
- zip()返回的也是一個迭代器
- zip里面可以傳遞多個值,如果值的長度不一致,就會以最短的為標準
li = ["a","b","c","d"]for index,value in enumerate(li,1):print(index,value)
li = ["a","b","c","d"]
li2 = [1,2,3,4]for i in zip(li,li2):print(i)
11.eval和exec
__import__:以字符串的形式導入模塊
time = __import__('time')
print(time.time())
eval和exec都是將字符串作為代碼執行的
eval主要用于執行表達式,也就是說它的執行代碼需要有一個返回結果。
exec主要用于執行代碼塊的
a = 1
res = eval("a+1+2")
print(res)
res = exec("a+1+2")
print(res)
11.1 globals() locals()
- globals()顯示的就是全局名稱空間里面的名字
- locals()顯示的就是局部名稱空間里面的名字
print(globals())
def func():a = 1print(locals())
func()
11.2?eval
- eval()后面兩個參數就是用來傳遞參數的,如果不傳遞默認傳輸的就是globals()和locals()
- 如果局部名稱空間里面傳遞了參數,就不會使用全局空間里面的參數了
- 執行的表達式必須有返回值
a = 3
g = {"a":a}
l = {"a":4}res = eval("a+1+2",g,l)
print(res)
print(g)
print(l)
a = 3
g = {"a":a}
l = {"a":4}res = eval("rr=a+1+2",g,l)
?
11.3?exec
- exec的表達式沒有返回值,他只是會將執行表達式里面的名字存儲到第三個參數里面
- 現在在全局打印a會報錯,因為全局不存在。但是不傳遞g,l的就不會報錯。因為默認傳遞的就是globals()和locals().locals()在全局里面就是globals()
g = {"b":3}
l = {"b":4}
exec("a=1+2+b",g,l)
print(l)
def func():b = 3g = {"b": b}l = {}exec("a=1+2+b")print(l)print(a)
func()
globals()產生的字典和全局名稱空間是同一個東西。?
locals()產生的字典并沒有和當前的局部名稱空間綁定,它只是一個拷貝版本的字典。
11.4 系統攻擊?
evel(0和exec()經常會被黑客利用,成為執行系統級別命令的入口,進而達成攻擊.所以以后使用這兩個函數的時候,一定要非常謹慎.
- 用戶輸入什么類型,不需要做出轉換,就可以獲取什么類型
- 如果確實需要他們來執行input輸入的內容,就可以控制它的全局名稱空間,將全局名稱空間定義為空字典.它會默認將內置名稱空間添加進去,我們只需要將內置名稱空間重置為None即可.這樣的話eval里面就訪問不到任何內置函數了
?12.異常處理
異常總體上面分為兩種,語法上面的錯誤和邏輯上面的錯誤.語法上面的錯誤沒法做任何處理,語法錯誤是不允許存在的.
邏輯上面的錯誤也是分為兩類:
- 錯誤的發生條件可以預知
- 錯誤發生的條件不可以預知
try:
? ? ? ? 子代碼塊
except 異常類型 as e:
? ? ? ? 子代碼塊
- 如果是子代碼塊3報錯,子代碼塊4不會執行.如果異常被異常類型1捕獲,剩下的異常類型都不會執行.包括Exception.
- Exception可以捕獲所有的異常.
- 對于同樣的異常處理可以將異常放在一起
- try不能單獨和else連用,必須搭配except才可以
- try可以和finally連用,只捕獲不處理異常,但是報錯之前,一定會將finally下面的代碼全部執行完畢.后續的代碼不會執行
try:子代碼塊1子代碼塊2子代碼塊3子代碼塊4
except 異常類型1 as e:子代碼塊try:子代碼塊
except 異常類型2 as e:子代碼塊try:子代碼塊
except 異常類型3 as e:子代碼塊
except Exception as e: # Exception可以捕獲所有的異常子代碼塊else:子代碼塊: 會在被檢測代碼(try下面的子代碼塊), 沒有異常的時候執行
finally:子代碼塊: 不管前面有沒有發生異常都會執行
try/expect會減弱代碼的可讀性,可以用判斷解決的問題,就不要用try