python進階篇-面向對象

1.對象的定義

1.1 什么是對象

面向過程:將程序流程化

對象:就是“容器“,是用來存儲數據和功能的,是數據和功能的集合體

面向對象和面向過程沒有優劣之分,它們只是使用的場景不同罷了。

1.2 為什么要有對象

有了對象之后,數據和功能之間有關系的會被放在同一個”容器里面“,它的解耦合程度會非常高,方便閱讀和維護。

1.3 怎么造對象

如果一個模塊里面存放了數據和功能,那么這個模塊就是容器,可以被稱之為對象。對象的本質就是存放數據和功能的”容器“。我們只要能做到這一點,我們就是在使用面向對象的思想在寫程序。但是使用文件整合的話,文件就太多了。

2. 類

2.1 類的概念

類的一方面是將不同的對象做區分,歸類的

為了解決不同的對象存儲相同數據的問題。所以不僅僅對象是”容器“,類也是”容器“。類用來存放同類對象的他們共有的數據和功能的。所以類也是對象。

將同類對象共有的數據提取到類里面,不是說私有的數據變為共有的了。本質上還是它們自己的。這樣做的目的,僅僅是為了歸類和節省空間。

在查找數據時,先找對象本身的數據,沒有才會到類里面去找。

88d4da0090f347e6875c47dc20ff3df5.png

2.2 類怎么定義

類名采用駝峰體命名法,首字母大寫

類的子代碼在定義階段就會直接運行。所以類的名稱空間在定義階段就會產生

aa5467c33c404ec1a2befc7634dff409.png

2.3 類的屬性訪問

Hero.__dict__會得到一個字典,這個字典里面存放的就是類名稱空間里面的名字

9c229bf6f83e484bb857b07f32c7ea32.png

可以使用字典取值的方式,來獲取類的屬性和方法?

cc13e90866664206b1ffd351e51f71d1.png

也可以通過類名.屬性名的方式來對類的屬性進行訪問,但是本質上就是通過字典取值。這樣只不過是python做的優化罷了。?

3.對象

3.1 對象的產生

類造出來之后,他本質上是為了將所有的同類對象提取出來。類里面存放的東西只是為了減少計算機資源的浪費,本質上還是屬于具體對象的。所以將數據和功能提取到類里面之后還沒有結束,還是需要建立對象和類之間的關聯。讓類順著這個關聯,還可以訪問到原來屬于它那部分的數據和功能。

只要基于類加括號就會創造一個對象,并且會將類和對象之間建好關聯。調好之后就會返回值就是一個英雄的對象。

類加括號的調用,并不會觸發類子代碼的運行。類的子代碼在定義階段就已經執行過了。調用的時候只會幫我們造好一個對象,然后建立好類與對象之間的關聯,并不會執行類的子代碼。調用一次就會幫我們創建一個對象。

對象里面也存在一個__dict__,里面存放的就是對象的屬性。現在三個字典里面都是空的。因為我們只是基于類創建了對象,并沒有往對象里面添加內容。但是并不意味著這些對象里面沒有任何屬性,因為類里面有。

e634a5deb73f447d98f2022903cab7a0.png

3ff7107f80a8496884b74e9fbed2c5b7.png

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)

97a4382c836b44fa94ba19c216061bf7.png

問題:這樣就出現了一個問題,給對象添加屬性的過程都是一樣的并且重復的,太過煩瑣了。

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)

8bb1a4771f584764906954ff5f81c5de.png

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 類的函數屬性

類的數據屬性是共享給對象使用的,只要類的數據屬性發生變化,對象是能夠立馬感知到的

對于類的函數屬性,類本身是可以使用的.通過類來調用,要嚴格按照函數的用法來,有幾個參數傳遞幾個參數.

be24a6ee902f468d89f3ef0590644812.png

3.6.2 對象調用類的函數屬性?

類里面定義的函數,主要是給對象去用的,而且是綁定給對象使用的,雖然所有的對象都是指向相同的功能,但是綁定到不同的對象,就會變成不同的綁定方法

73fc4bcf113348b597745100766f92d9.png

類的函數屬性綁定給對象使用之后,就不再是普通的函數,而是綁定方法.我們使用誰來調用綁定方法,綁定方法就會把水作為第一個參數傳遞進去.我們在實例化對象的時候,自動觸發的也是對象的init綁定方法,所以也不需要傳遞對象本身.?

3.6.3? 類函數的形參個數

正是因為有綁定方法的存在,所以我們在類里面定義函數的時候,就至少需要一個形參.即便你的函數里面不需要任何參數.因為我們在通過對象調用這個函數的時候,綁定方法會將對象傳遞進來,所以需要一個形參來接收綁定方法傳進來的對象.

3.6.4 類函數的self

從規范上面來說,第一個參數我們應該寫成self,這個self沒有任何特殊的地方,僅僅是一個變量名罷了

3.6.5 快捷鍵

快捷鍵:alt+j,按一次就會選中一個相同變量名

1f7181f56a6c45b6bacca16a9686cca3.png

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)

f9d592bc5a274005ae27ae9d0166075a.png

3.6.7 python一切皆對象

基本數據類型也是類

我們定義好一個字符串,一個列表,字典的時候,其實就是在造對象.我們通過造出來的對象.他們的方法,其實就是對象在調用它們的綁定方法

de141c240e1043bdae209374a22317a2.png

都是存在self的?

bc8945dfbfa0483a9f2bc74902a966a1.png

x= "aaa",其實背后是觸發了一個功能叫做x = str("aaa"),這其實就是類的實例化的過程,x就是通過str實例化出來的一個對象.只不過是python給我們優化了基本數據類型的實例化過程.直接使用"",就可以造出字符串對象.?

a0f36845d31d41faac2c2bb089999102.png

4.面向對象三大屬性

面向對象的三大屬性,封裝,繼承和多態

4.1 封裝

封裝是面向對象最核心的一個特性,封裝就是將一堆東西放到容器里面,然后封起來,也就是前面一直在說的整合?

4.1.1?隱藏屬性

特點:

  • 隱藏的本質,只是一種改名操作
  • 對外不對內
  • 這個改名操作,只會在類的定義階段檢查類的子代碼的時候執行一次,之后定義的__開頭的屬性都不會改名

我們封裝的時候可以將屬性隱藏起來,讓使用者沒有辦法直接使用,而不是讓使用者不能使用。

我們無論是隱藏數據屬性還是函數屬性,直接在變量名前面加__就可以實現。

class Test:__x = 10def __f1(self):print("f1")
print(Test.x)

dad7011500d14cb7be1f90c1176b088d.png

我們只要在屬性前面加上__,在類的定義階段,__開頭的屬性名字就會被加上前綴_?

d682eb67d8354a3db6dd6b6eac539a75.png?所以__x =》_Test__x,__f1=>_Test_f1.所以pyhton的這種隱藏機制,并沒有做到真正意義上的隱藏,其實就是一種改名操作,我們其實只要知道了類名和屬性名,就可以拼接出變形之后的名字,然后對她進行訪問

e5e73478b10e465dbb597c26a537a1db.png

對外不對內的含義就是,我們在類的外部通過__x是訪問不到隱藏屬性的,但是在類的內部通過__x和__f1是可以訪問到的。

class Test:__x = 10def __f1(self):print("f1")def f2(self):print(self.__x)print(self.__f1)obj = Test()
obj.f2()

8e1a704a94f54eae93d50d72f3e2d78b.png

為什么在類的外部不可以訪問隱藏屬性,在類的內部卻是可以訪問的呢??

在類的定義階段,檢查類子代碼語法的時候,對類內部__開頭的名字統一進行了改名操作。在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__)

6568ba496d3d43cdb48d877c96a6272e.png

基于對象的屬性隱藏?

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()

8de6845abedb40c68ddf177838eb5b22.png

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)

5d62a78d774648f89e4cd2e24289b1b7.png

  • 隱藏函數屬性

隱藏函數屬性可以隔離復雜度,讓使用者更加方便的使用設計者設計的類。

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()

2eb661758c49410d85d231083f438f8b.png

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)

6b6c3877c5d848168f46372a4c16f45d.png

裝飾器如此使用還是太麻煩了,所以還是得使用語法糖?

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)

cb7941db04254bd08b326a2135c6d835.png

?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__)

e2bc80800be6437bb89ae35b06bf5cb6.png

在python2里面存在新式類和經典類的區分。

新式類:繼承了object類的子類(object類是python的內置類),以及繼承了這個類的子子孫孫類。

經典類:沒有繼承了object類的子類(object類是python的內置類),以及繼承了這個類的子子孫孫類。

所以pyhton2里面默認都是經典類,只有繼承了object才會變為新式類。

958d3bf929914482839930fb0ec3b148.png

在python3里面默認繼承的就是object,python3里面所有的類都是新式類。

cfc2458567554588b47d38114b3ea385.png

?如果想讓自己的代碼可以兼容python2的話,就可以讓類繼承object.這樣我們的代碼在python2中運行,它依然還是新式類。

4.2.2 繼承的特性

繼承的特性是遺傳

子類會遺傳父類的屬性:子類繼承父類之后,父類有的屬性,子類都可以訪問的到。兒子自己有就用自己的,兒子沒有的就找老爸要。

繼承就是用來解決代碼冗余問題的:可以將多個類的共同的部分提取到一個父類里面,這樣共同的屬性,只是需要存儲一份即可。

  • 類是為了解決對象有共同屬性的問題。
  • 繼承是為了解決類有共同屬性的問題

object是python的內置類,類存儲的是數據和功能,這些數據和功能都是python是認為比較有用的。python將其封裝到了object類里面,讓所有的新式類全部都來繼承它。

在python里面所有的新式類,只要往上找,最后一定是繼承了object,所以這些新式類,一定都有object提供的數據和功能。比如:__dict__

4.2.3 多繼承的優缺點

優點:

  • 一個子類可以遺傳多個父類的屬性

缺點:

  • 多繼承違背了人的思維習慣
  • 多繼承會讓代碼的可讀性變差

如果必須要使用多繼承時,可以使用MixIns機制(它是一種編程規范,本質上還是多繼承),使用它可以使用多繼承的優點,也可以避免代碼可讀性變差的問題。MixIns才是pyhton多繼承的正確打開方式。

4.2.4 繼承的實現

抽象:就是抽取它們共同的部分

da9fba1723fc4623ad000d27946275cd.png

#!/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__

1a69fd18136446f2aec567acfca15607.png

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()

00775979352d45879b60f82b9029bed8.png

如果想要打印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()

956a7829c2364e3f80227f239ba830dc.png

4.2.7 多繼承屬性查找

4.2.7.1 菱形問題(鉆石問題)

210611c3b9f54576b9ee82bfbbdb9c2b.png

對象訪問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列表

82905e03f2ea48a1b2e38d6ad215f2fa.png

4.2.7.2 非菱形查找?

  • 對于菱形繼承來說,新式類和經典類的屬性查找順序是不一樣的。?
  • 但是對于非菱形繼承來說,新式類的經典類的屬性查找順序都是一樣的
  • python2里面也沒有提供mro這個屬性
  • 在代碼里面要么全是新式類,要么全是經典類,不能一部分是新式類,一部分是經典類

f287e489f7774ec9ac2c3adfbfde1939.png

屬性查找順序:

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列表

b063f6641fa54e3e90a50f7fd3400e69.png

4.2.7.3 菱形查找

對于非菱形繼承,新式類和經典類的屬性查找是一樣的,但是對于菱形繼承.屬性的查找方式就不一樣了.?

580fd7501dde435ca32e81fe57a58df1.png

  • 經典類:深度優先查找,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()

a64c5e94e46f4296b80823bc5df5437f.png

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__方法。

ba110ae4e140464db11331e8e9a270db.png

  • super是嚴格依賴繼承關系,實現的重用父類的代碼?

0196430b01b14c53a19c0964a1541983.png

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())

4d308539166c452f835dc1471efec70c.png

  • 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__)

e863514016864cb5ad3586b46230e5fa.png

新式類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__)

025993fc359642e9aa93ac5d3b6e8c49.png

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())

f42db5076cae43dfb6017ca41d5f61ed.png

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)

1db4ed3b9f0b435da0aa1ee9d8c1e90e.png

在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}))

c0f42940187d4b00a79bd7f7619c7f6e.png

但是在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()

e5e298092c7a45eea6df991938e9a393.png

目的:

讓父類成為一個規范標準的作用,所有繼承我這個抽象基類的子類。必須定義抽象基類實現的規定的方法。

注意:

抽象基類不能直接用于實例化,它的作用只是用來規范標準的。

e1b2edf742d4490f814c0ff6ec0cd8b5.png

?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__)

44348e60e0494c92875ea36f2fc2dac3.png

99d9602353a14bfe8b983e03ed25ff2e.png

5.2 靜態方法

?類里面的函數有兩種用途,一種是綁定給對象使用的,一種是綁定給類使用的。如果函數子代碼需要使用到對象,這個函數就需要綁定給對象。如果函數子代碼需要使用到類,這個函數就需要綁定給類。

還有一種方法不需要用到類,也不要用到對象。它就是一個獨立的功能。就是靜態方法,對象和類都是可以調用它的,只是沒有自動傳參的功能。

在函數上面加上 @staticmethod

e89214ea8b7f42c1ac3ce5b373d1ddda.png

6.反射機制

6.1 何為反射機制

?python是一門強類型動態解釋型語言,反射機制則是動態語言的關鍵。

動態語言,在程序里面定義變量的時候,不需要指定數據類型。只有執行代碼賦值的時候,才識別它的數據類型。

靜態語言,在定義變量的時候,就要指定它的數據類型。

只要是動態語言就一定會有反射機制

定義:在程序運行過程中,動態獲取對象信息,以及動態調用對象方法的功能。

程序只要開始運行,就是計算機來識別代碼,和程序員就沒有關系了。這時候程序獲取到一個對象,程序如何知道有哪些屬性??如果程序不知道對象存在哪些屬性,就沒有辦法調用。所以需要讓程序在運行過程中,自己能動態獲取到對象屬性和方法

我們 可以通過__dict__來判斷對象有沒有屬性,但是有局限性,不是所有的對象都是存在__dict__的。

d4da80da10d74b90b4b57daefcb11ae7.png

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))

列表里面的都是字符串類型的屬性,所以需要通過字符串反射到真正的屬性上面,從而得到屬性的值

86197e1e632143069833ecb8a230c081.png

?提供了四個函數通過字符串來操作屬性

1bd089b0f4c444108288249a2f4da216.png

這里的obj不僅可以是對象,也可以是類,當然后面放置的就要是類的屬性了。

cccd20cd19c6457dbcbb4839dba47610.png

04567fc19f4b41129f86a78c163a0d04.png

反射就是在我們不知道對象是什么的情況下,讓我們的程序程序能動態的分析出來我們的對象里面有什么 ,反射是一種動態分析的能力。

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>

bfb826fb651a4602b5166178fbc22de3.png

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))

682ea9d12af144689753bc8acce5fa4e.png

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)

9a9e5db0c30149ffa9c0e2633436c39b.png

class關鍵字產生的名稱空間,比exec執行類的子代碼產生的名稱空間,多了更多的內置屬性。我們創建的名字空間,只存在類子代碼里面的名字0e5f7d217ad54146bd6c7d6981972517.png?

  • 4.調用元類?,元類是可以指定的,我們可以自定義元類。如果沒有自定義元類,使用的就是內置元類type.調用type將前面散步創建的參數傳遞進去。

Human不是類,type的返回值才是類。他的類名則是字符串格式的叫做class_name = 'Human'。前面使用class定義的Human,也只是變量名而已。變量名指向的值才是我們的類。

# # 4、調用元類
Human = type(class_name, class_bases, class_dic)
print(Human)

d03ce5648245431380ee6b91aadf1a99.png?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()

fa90717029984b5d94b71b1ad6aab405.png

通過調用元類最后得到一個類,這個類就是元類實例化的對象。清楚了這個步驟之后,我們以后就可以針對性的對這些步驟做出定制化的操作.主要是第四步。在調用元類進行實例化類的時候,可以進行定制化的操作,比如將名稱空間里面的屬性隱藏掉,要求類名不能有下劃線。這些都是要求我們自定義元類。

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/71360.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/71360.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/71360.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

網絡安全“掛圖作戰“及其場景

文章目錄 一、網絡安全掛圖作戰來源與定義1、網絡安全掛圖作戰的來源2、網絡安全掛圖作戰的定義 二、掛圖作戰關鍵技術三、掛圖作戰與傳統態勢感知的差異四、掛圖作戰主要場景五、未來趨勢結語 一、網絡安全掛圖作戰來源與定義 1、網絡安全掛圖作戰的來源 網絡安全掛圖作戰的…

【嵌入式Linux應用開發基礎】read函數與write函數

目錄 一、read 函數 1.1. 函數原型 1.2. 參數說明 1.3. 返回值 1.4. 示例代碼 二、write 函數 2.1. 函數原型 2.2. 參數說明 2.3. 返回值 2.4. 示例代碼 三、關鍵注意事項 3.1 部分讀寫 3.2 錯誤處理 3.3 阻塞與非阻塞模式 3.4 數據持久化 3.5 線程安全 四、嵌…

嵌入式八股文(四)計算機網絡篇

第一章 基礎概念 1. 服務 指網絡中各層為緊鄰的上層提供的功能調用,是垂直的。包括面向連接服務、無連接服務、可靠服務、不可靠服務。 2. 協議 是計算機?絡相互通信的對等層實體之間交換信息時必須遵守的規則或約定的集合。?絡協議的三個基本要素:語法、…

LabVIEW 天然氣水合物電聲聯合探測

天然氣水合物被認為是潛在的清潔能源&#xff0c;其儲量豐富&#xff0c;預計將在未來能源格局中扮演重要角色。由于其獨特的物理化學特性&#xff0c;天然氣水合物的探測面臨諸多挑戰&#xff0c;涉及溫度、壓力、電學信號、聲學信號等多個參數。傳統的人工操作方式不僅效率低…

JAVA代碼走查重構常用prompt

代碼重構prompt&#xff1a; ## 主題&#xff1a; 代碼重構 ## 角色扮演: 你是軟件開發大師Martin Fowler&#xff0c;精通代碼重構、面向對象編程、Clean Code和設計模式&#xff0c;且熟練掌握《重構&#xff0c;改善既有代碼的設計》這本書中的重構思想和各種重構方法。 ## …

[數據結構]紅黑樹,詳細圖解插入

目錄 一、紅黑樹的概念 二、紅黑樹的性質 三、紅黑樹節點的定義 四、紅黑樹的插入&#xff08;步驟&#xff09; 1.為什么新插入的節點必須給紅色&#xff1f; 2、插入紅色節點后&#xff0c;判定紅黑樹性質是否被破壞 五、插入出現連續紅節點情況分析圖解&#xff08;看…

STM32 HAL庫USART串口DMA IDLE中斷編程:避坑指南

HAL_UART_Receive接收最容易丟數據了,STM32 HAL庫UART查詢方式實例 可以考慮用中斷來實現,但是HAL_UART_Receive_IT還不能直接用,容易數據丟失,實際工作中不會這樣用,STM32 HAL庫USART串口中斷編程&#xff1a;演示數據丟失, 需要在此基礎優化一下. STM32F103 HAL庫USART串口…

sql注入中information_schema被過濾的問題

目錄 一、information_schema庫的作用 二、獲得表名 2.1 sys.schema_auto_increment_columns 2.2 schema_table_statistics 三、獲得列名 join … using … order by盲注 子查詢 在進行sql注入時&#xff0c;我們經常會使用information_schema來進行爆數據庫名、表名、…

Jenkins 給任務分配 節點(Node)、設置工作空間目錄

Jenkins 給任務分配 節點(Node)、設置工作空間目錄 創建 Freestyle project 類型 任務 任務配置 Node 打開任務-> Configure-> General 勾選 Restrict where this project can be run Label Expression 填寫一個 Node 的 Label&#xff0c;輸入有效的 Label名字&#x…

Electron:使用electron-react-boilerplate創建一個react + electron的項目

使用 electron-react-boilerplate git clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name cd your-project-name npm install npm start 安裝不成功 在根目錄加上 .npmrc文件 內容為 electron_…

數控機床設備分布式健康監測與智能維護系統MTAgent

數控機床設備分布式健康監測與智能維護系統MTAgent-v1.1融合了目前各種先進的信號處理以及信息分析算法以算法工具箱的方式&#xff0c;采用了一種開發的、模塊化的結構實現信號各種分析處理&#xff0c;采用Python編程語言&#xff0c;滿足不同平臺需求(包括Windows、Linux)。…

FPGA VIVADO:axi-lite 從機和主機

FPGA VIVADO:axi-lite 從機和主機 TOC在這里插入代碼片 前言 協議就不詳細講解了&#xff0c;直接看手冊即可。下面主要如何寫代碼和關鍵的時序。 此外下面的代碼可以直接用于實際工程 一、AXI-LITE 主機 數據轉axi lite接口&#xff1a; 讀/寫數據FIFO緩存 仲裁&#xff1a…

1. 對比 LVS 負載均衡群集的 NAT 模式和 DR 模式,比較其各自的優勢 。2. 基于 openEuler 構建 LVS-DR 群集。

DR 模式 * 負載各節點服務器通過本地網絡連接&#xff0c;不需要建立專用的IP隧道 原理&#xff1a;首先負載均衡器接收到客戶的請求數據包時&#xff0c;根據調度算法決定將請求發送給哪個后端的真實服務器&#xff08;RS&#xff09;。然后負載均衡器就把客戶端發送的請求數…

ollama server啟動服務后如何停止

要停止 Ollama 服務器服務&#xff0c;取決于如何啟動該服務的。以下是幾種常見的啟動方法和相應的停止服務的步驟&#xff1a; 1. 直接在命令行中啟動 如果是在命令行中直接啟動 Ollama 服務器的&#xff0c;例如使用以下命令&#xff1a; ollama serve 可以通過以下方式停…

【設計模式】03-理解常見設計模式-行為型模式(專欄完結)

前言 前面我們介紹完創建型模式和創建型模式&#xff0c;這篇介紹最后的行為型模式&#xff0c;也是【設計模式】專欄的最后一篇。 一、概述 行為型模式主要用于處理對象之間的交互和職責分配&#xff0c;以實現更靈活的行為和更好的協作。 二、常見的行為型模式 1、觀察者模…

mapbox基礎,使用geojson加載line線圖層,實現純色填充、圖片填充、虛線和漸變效果

????? 主頁: gis分享者 ????? 感謝各位大佬 點贊?? 收藏? 留言?? 加關注?! ????? 收錄于專欄:mapbox 從入門到精通 文章目錄 一、??前言1.1 ??mapboxgl.Map 地圖對象1.2 ??mapboxgl.Map style屬性1.3 ??line線圖層樣式二、??使用geojson加載…

深入淺出:CUDA是什么,如何利用它進行高效并行計算

在當今這個數據驅動的時代&#xff0c;計算能力的需求日益增加&#xff0c;特別是在深度學習、科學計算和圖像處理等領域。為了滿足這些需求&#xff0c;NVIDIA推出了CUDA&#xff08;Compute Unified Device Architecture&#xff09;&#xff0c;這是一種并行計算平臺和編程模…

LNMP+Zabbix安裝部署(Zabbix6.0 Lnmp+Zabbix Installation and Deployment)

LNMPZabbix安裝部署&#xff08;Zabbix6.0&#xff09; 簡介 LNMP&#xff08;Linux Nginx MySQL PHP&#xff09;是一種流行的Web服務器架構&#xff0c;廣泛用于搭建高性能的網站和應用程序。Zabbix 是一個開源的監控軟件&#xff0c;可以用來監控網絡、服務器和應用程序…

Docker 部署 Dify:輕松集成 Ollama 和 DeepSeek

1 Ollama的安裝及使用 1.1 什么是Ollama&#xff1f; Ollama 是一個用于本地部署和運行大型語言模型的框架。 Ollama 的作用包括&#xff1a; 本地模型運行&#xff1a;Ollama 允許在本地機器上運行大型語言模型&#xff08;如 LLaMA、DeepSeek 等&#xff09;&#xff0c;無…

C++筆記之標準庫中用于處理迭代器的`std::advance`和`std::distance`

C++筆記之標準庫中用于處理迭代器的std::advance和std::distance code review! 文章目錄 C++筆記之標準庫中用于處理迭代器的`std::advance`和`std::distance`一.`std::advance`函數原型參數說明使用場景示例代碼示例 1:移動 `std::vector` 的隨機訪問迭代器示例 2:移動 `st…