面向對象三大特性之封裝、多態

目錄

  • 繼承下的派生實際應用

  • 面向對象三大特性之封裝

  • 面向對象三大特性之多態

  • 反射

 

 內容

1.繼承下的派生實際應用

import datetime
import json

class MyJsonEncoder(json.JSONEncoder):
    def default(self, o):
        # 形參o就是即將要被序列化的數據對象
        # print('重寫了', o)
        '''將o處理成json能夠序列化的類型即可'''
        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方法繼續執行 防止有其他額外操作)
d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
res = json.dumps(d1)
print(res)

問題:json能否序列化d1字典里的values?

列印結果為:

TypeError: Object of type 'datetime' is not JSON serializable

得出結論:json不能序列化python所有的數據類型 只能是一些基本數據

那麼如何將values轉成字元串?

方法一:手動將不能序列化的類型轉成字元串 str

{'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())}

方法二:研究json源碼並重寫序列化方法

研究源碼發現報錯的方法叫default

 raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__)

我們可以寫一個類繼承JSONEncoder然後重寫default方法

正確寫法:

d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
res = json.dumps(d1, cls=MyJsonEncoder)
print(res)

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) # 逆來順受

如何封裝名字:__變數名

封裝的功能只在類定義階段才能生效!!!

我們雖然指定了封裝的內部變形語法 但是也不能直接去訪問 需要通過特定的通道(介面)去訪問

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()
stu1.set_info('jasonNB',28)
stu1.check_info()
stu1.set_info('','haha')

將數據隱藏起來就限制了類外部對數據的直接操作,然後類內應該提供相應的介面來允許類外部間接地操作數據,介面之上可以附加額外的邏輯來對數據的操作進行嚴格地控制

目的的是為了隔離複雜度,例如ATM程式的取款功能,該功能有很多其他功能組成
比如插卡、身份認證、輸入金額、列印小票、取錢等,而對使用者來說,只需要開發取款這個功能介面即可,其餘功能我們都可以隱藏起來

property

property就是將方法偽裝成數據

有時候很多數據需要經過計算才可以獲得  但是這些數據給我們的感覺應該屬於數據而不是功能

體質指數(BMI)=體重(kg)÷身高^2(m)

BMI指數>>>:應該屬於人的數據而不是人的功能

class Person(object):
    def __init__(self, name, height, weight):
        self.__name = name
        self.height = height
        self.weight = weight
    @property
    def BMI(self):
        # print('%s的BMI指數是:%s' % (self.name, self.weight / (self.height ** 2)))
        return '%s的BMI指數是:%s' % (self.__name, self.weight / (self.height ** 2))

p1 = Person('jason', 1.83, 77)
# p1.BMI() # 22.9
print(p1.BMI)
p2 = Person('eason',1.90,85)
# p2.BMI()  # 23.5
print(p2.BMI)
p3 = Person('xd',1.85,100)
# p3.BMI()  # 29.2
print(p3.BMI)
p4 = Person('xd',1.5,34)
# p4.BMI()  # 15.1
print(p4.BMI)

面向對象三大特性之多態

多態:一種事物的多種形態

多態性:

class Animal(object):
    def speak(self):
        pass

class Cat(Animal):
      def speak(self):
          print('喵喵喵')

class Dog(Animal):
      def speak(self):
          print('汪汪汪')

class Pig(Animal):
      def speak(self):
          print('哼哼哼')

上述場景下 雖然體現了事物的多態性 但是並沒有完整的體現出來

因為現在不同的形態去叫 需要調用不同的方法 不夠一致

只要你是動物 那麼你想要說話 就應該調用一個相同的方法 這樣便於管理

c1 = Cat()
d1 = Dog()
p1 = Pig()
c1.speak()
d1.speak()
p1.speak()

多態性的好處在於增強了程式的靈活性和可擴展性,比如通過繼承Animal類創建了一個新的類,實例化得到的對象obj,可以使用相同的方式使用obj.speak()

面向對象的多態性也需要python程式設計師自己去遵守

雖然python推崇的是自由 但是也提供了強制性的措施來實現多態性

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,無法實例化

由多態性衍生出一個鴨子類型理論:

class Memory(object):
    def read(self):
        pass
    def write(self):
        pass
class Disk(object):
    def read(self):
        pass
    def write(self):
        pass

得到記憶體或者硬碟對象之後 只要想讀取數據就調用read 想寫入數據就調用write 不需要考慮具體的對象是誰

反射

專業解釋:指程式可以訪問、檢測和修改本身狀態或者行為的一種能力

大白話:其實就是通過字元串來操作對象的數據和功能

反射需要掌握的四個方法

hasattr():判斷對象是否含有字元串對應的數據或者功能

getattr():根據字元串獲取對應的變數名或者函數名

setattr():根據字元串給對象設置鍵值對(名稱空間中的名字)

delattr():根據字元串刪除對象對應的鍵值對(名稱空間中的名字)

反射實際應用:

class Student(object):
    school = '清華大學'
    def get(self):
        pass

編寫一個小程式 判斷Student名稱空間中是否含有用戶指定的名字 如果有則取出展示

guess_name = input('請輸入你想要查找的名字>>>:').strip()
  # 不使用反射不太容易實現
print(hasattr(Student, 'school'))  # True
print(hasattr(Student, 'get'))  # True
print(hasattr(Student, 'post'))  # False

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('類中有一個功能名字是%s'%guess_name,target_name)
     else:
         print('類中有一個數據名字是%s'%guess_name,target_name)
else:
     print('類中沒有該名字')

setattr(Student,'level','貴族學校')
print(Student.__dict__)

以後只要在業務中看到關鍵字:對象字元串(用戶輸入、自定義、指定) 那麼肯定用反射

反射實際案例

利用反射獲取配置文件中的配置資訊:

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