2022.4.8封裝、多型、反射等
2022.4.8封裝、多型、反射等
- 繼承下派生實際應用
- 面向物件三大特性之封裝
- 面向物件三大特徵之多型
- 反射
一、繼承下派生實際應用
以datetime模組和json模組為例
1.序列化字典 d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()} res = json.dumps(d1) # 傳入字典發現報錯,見下方 print(res) ''' TypeError: Object of type 'datetime' is not JSON serializable json不能序列化python所有的資料型別 只能是一些基本資料型別 json.JSONEncoder ''' 1.手動將不能序列化的型別先轉字串 {'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())} 2.研究json原始碼並重寫序列化方法 研究原始碼發現報錯的方法叫default raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__) 我們可以寫一個類繼承JSONEncoder然後重寫default方法 2.嘗試使用程式碼,定義一個類繼承json.JSONEncoder class MyJsonEncoder(json.JSONEncoder): def default(self,o): # 形參o就是即將要被序列化的資料物件 print('重寫了',o) if isinstance(o,datetime.datetime): return o.strftime('%Y-%m-%d %X') elif isinstance(o,datetime.date): return o.strftime('%Y-%m-%d') return super().default(o) # 不滿足上述兩種情況則呼叫父類default(讓父類的default方法繼續執行,防止有其他額外操作) res = json.dumps(d1,cls=MyJsonEncoder) print(res) # 重寫了 2022-04-08 18:48:43.589847 # 重寫了 2022-04-08 # {"t1": "2022-04-08 18:48:43", "t2": "2022-04-08"}
二、面向物件三大特徵之封裝
1、封裝的含義
封裝就是將類中的某些名字'隱藏'起來,不讓外界直接呼叫,隱藏的目的是為了提供專門的通道去訪問,在通道內可以新增額外的功能。
2、程式碼實操
(1)如何封裝名字
在變數名的前面加上兩個下劃線__
(2)封裝名字的語法
封裝的功能只在類定義階段才能生效!!!
在類中封裝其實也不是絕對的 僅僅是做了語法上的變形而已
__變數名 >>> _類名__變數名
class Student(object): school = '清華大學' __label = '逆來順受' # 由於python崇尚自由 所以並沒有真正的隱藏 而是自動轉換成了特定的語法 def __init__(self, name, age): self.name = name self.age = age def choose_course(self): print('%s正在選課'%self.name) stu1 = Student('jason', 18) print(stu1.school) # 清華大學 print(stu1.name) # jason print(stu1.age) # 18 print(stu1.__label) # 報錯,看不到結果 print(Student.__dict__) # {...,'_Student__label': '逆來順受',...} print(Student._Student__label) # 逆來順受 print(stu1._Student__label) # 逆來順受
(3)介面(通道)
我們雖然指定了封裝的內部變形語法 但是也不能直接去訪問
看到了就表示這個屬性需要通過特定的通道(介面)去訪問
class Student(object): __school = '清華大學' def __init__(self, name, age): self.__name = name self.__age = age # 專門開設一個訪問學生資料的通道(介面) def check_info(self): print(""" 學生姓名:%s 學生年齡:%s """ % (self.__name, self.__age)) # 專門開設一個修改學生資料的通道(介面) def set_info(self,name,age): if len(name) == 0: print('使用者名稱不能為空') return if not isinstance(age,int): print('年齡必須是數字') return self.__name = name self.__age = age stu1 = Student('jason', 18) stu1.check_info() # json 18 stu1.set_info('jasonNB',28) stu1.check_info() # json 18, jasonNB 28 stu1.set_info('','haha') """ 將資料隱藏起來就限制了類外部對資料的直接操作,然後類內應該提供相應的介面來允許類外部間接地操作資料, 介面之上可以附加額外的邏輯來對資料的操作進行嚴格地控制 目的的是為了隔離複雜度,例如ATM程式的取款功能,該功能有很多其他功能組成 比如插卡、身份認證、輸入金額、列印小票、取錢等,而對使用者來說,只需要開發取款這個功能介面即可,其餘功能我們都可以隱藏起來 """
3、property
剛才在資料前面加雙下方法可以將資料隱藏,那麼如何將方法隱藏起來呢,property方法可以解決這個問題。
瞭解這個方法之前我們先舉個例子:
BMI指數:
體質指數(BMI)=體重(kg)÷身高^2(m)
有時候很多資料需要經過計算才可以獲得
但是這些資料給我們的感覺應該屬於資料而不是功能
BMI指數>>>:應該屬於人的資料而不是人的功能
接下來定義一個人類,封裝BMI指數方法:
class Person(object):
def __init__(self, name, height, weight):
self.__name = name
self.height = height
self.weight = weight
@property # 封裝方法關鍵字
def BMI(self):
return '%s的BMI指數是:%s' % (self.__name, self.weight / (self.height ** 2)) # 返回計算之後的格式化輸出
p1 = Person('jason', 1.83, 77)
p1.BMI() # TypeError: 'str' object is not callable,不能作為函式呼叫
print(p1.BMI) # jason的BMI指數是:22.99262444384723
因此,封裝後的方法可以使用點的方式直接實現,而不需要加括號執行
三、面向物件三大特性之多型
1、什麼是多型?
一種事物的多種狀態
eg:
水:固態,液態,氣態
動物:貓,狗,豬
2、多型性
程式碼模擬,結合使用繼承方法
class Animal(object):
def speak(self):
pass
class Cat(Animal):
def miao(self):
print('喵喵喵')
class Dog(Animal):
def wang(self):
print('汪汪汪')
class Pig(Animal):
def heng(self):
print('哼哼哼')
上述程式碼通過單繼承Animal,雖然呈現了動物的多型性,但是並沒有完整的體現,因為從程式碼角度需要呼叫不同的方法才能實現不同動物的叫聲,所以可以想到優化成只要說話就呼叫同一個方法,如下:
class Animal(object):
def speak(self):
pass
class Cat(Animal):
def miao(self):
print('喵喵喵')
class Dog(Animal):
def wang(self):
print('汪汪汪')
class Pig(Animal):
def heng(self):
print('哼哼哼')
c1 = Cat() # 生成物件
d1 = Dog() # 生成物件
P1 = Pig() # 生成物件
c1.speak() # 呼叫speak方法
d1.speak() # 呼叫speak方法
p1.speak() # 呼叫speak方法
因此,總體來說,多型性並不是什麼方法,而是一個理論,就是在編寫類物件的時候,假如子類有父類相同功能的情況下,可以將功能的名字統一,便於使用,就不用呼叫多個方法那麼麻煩了
"""
多型性的好處在於增強了程式的靈活性和可擴充套件性,比如通過繼承Animal類建立了一個新的類,例項化得到的物件obj,可以使用相同的方式使用obj.speak()
面向物件的多型性也需要python程式設計師自己去遵守
雖然python推崇的是自由 但是也提供了強制性的措施來實現多型性
不推薦使用
"""
3、強制子類定義父類方法
有一種方法可以限制子類必須定義相關父類的方法的操作,如下:
import abc
指定metaclass屬性將類設定為抽象類,抽象類本身只是用來約束子類的,不能被例項化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # 該裝飾器限制子類必須定義有一個名為talk的方法
def talk(self): # 抽象方法中無需實現具體的功能
pass
class Person(Animal): # 但凡繼承Animal的子類都必須遵循Animal規定的標準
def talk(self):
pass
p1=Person() # 若子類中沒有一個名為talk的方法則會丟擲異常TypeError,無法例項化
4、鴨子理論
說到這裡,由多型衍生出一個鴨子理論:
只要你裡看著像鴨子,走路像鴨子,說話像鴨子,那麼你就是鴨子!
鴨子型別實戰案例:
"""
在linux系統中有一句話>>>:一切皆檔案!!!
記憶體可以存取資料
硬碟可以存取資料
...
那麼多有人都是檔案
"""
class Memory(object):
def read(self):
pass
def write(self):
pass
class Disk(object):
def read(self):
pass
def write(self):
pass
# 得到記憶體或者硬碟物件之後 只要想讀取資料就呼叫read 想寫入資料就呼叫write 不需要考慮具體的物件是誰,都是用來存取檔案資料
四、面向物件之反射
1、什麼是反射?
專業解釋:指程式設計師可以訪問、檢測和修改本身狀態或者行為的一種能力
大白話:其實就是通過字串來操作物件的資料和功能
2、反射的四個方法
引入:attr,及attribute屬性的簡寫
hasattr(obj,...):判斷物件是否含有字串對應的資料或者功能
getattr(obj,...):根據字串獲取對應的變數名或者函式名
setattr(obj,...):根據字串給物件設定鍵值對(名稱空間中的名字)
delattr(obj,...):根據字串刪除物件對應的鍵值對(名稱空間中的名字)
3、反射實際應用
需求:編寫一個小程式 判斷Student名稱空間中是否含有使用者指定的名字 如果有則取出展示
class Student(object):
school = '清華大學'
def det(self):
pass
# 不使用反射不太容易實現,因為獲取使用者輸入一般都是字串,因此可以使用反射的兩種方法
1.hasattr判斷Student是否有指定名字
print(hasattr(Student, 'school')) # True
print(hasattr(Student, 'get')) # True
print(hasattr(Student, 'post')) # False
2.getattr獲取名字對應的值
print(getattr(Student, 'school')) # 清華大學
print(getattr(Student, 'get')) # <function Student.get at 0x10527a8c8>
# 程式碼實現
guess_name = input('請輸入你想要查詢的名字>>>:').strip()
if hasattr(Student,guess_name):
target_name = getattr(Student,guess_name)
if callable(target_name):
print(f'存在功能名{target_name}')
else:
print(f'存在資料名{target_name}')
else:
print('該方法不在該類中')
"""
什麼時候使用反射 可以記固定的口訣
以後只要在業務中看到關鍵字
物件 和 字串(使用者輸入、自定義、指定) 那麼肯定用反射
"""
4、反射實際案例
利用反射獲取配置檔案中的配置資訊(一切皆物件,檔案也是物件)
import settings
dir(settings) # 獲取物件中所有可以使用的名字
getattr(settings,'NAME') # 配置檔案中一般為常量,大寫
class FtpServer:
def serve_forever(self):
while True:
inp=input('input your cmd>>: ').strip()
cmd,file=inp.split()
if hasattr(self,cmd): # 根據使用者輸入的cmd,判斷物件self有無對應的方法屬性
func=getattr(self,cmd) # 根據字串cmd,獲取物件self對應的方法屬性
func(file)
def get(self,file):
print('Downloading %s...' %file)
def put(self,file):
print('Uploading %s...' %file)
obj = FtpServer()
obj.serve_forever()
預習
拔高:面向物件大作業>>>:選課系統(暫時可不看 學有餘力者可提前思考)
角色:學校、學員、課程、講師
要求:
1. 建立北京、上海
2 所學校2. 建立linux , python , go
3個課程 , linux\py 在北京開, go 在上海開3. 課程包含,週期,價格,通過學校建立課程
4. 通過學校建立班級, 班級關聯課程、講師
5. 建立學員時,選擇學校,關聯班級5. 建立講師角色時要關聯學校,
6. 提供三個角色介面
6.1 學員檢視, 可以註冊, 交學費, 選擇班級,
6.2 講師檢視, 講師可管理自己的班級, 上課時選擇班級, 檢視班級學員列表 , 修改所管理的學員的成績
6.3 管理檢視,建立講師, 建立班級,建立課程7. 上面的操作產生的資料都通過pickle序列化儲存到檔案裡