1. 程式人生 > 其它 >2022.4.8封裝、多型、反射等

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序列化儲存到檔案裡