1. 程式人生 > 其它 >python面向物件(3)

python面向物件(3)

python面向物件(3)

繼承下的派生實際應用

import datetime
import json

class MyJsonEncoder(json.JSONEncoder):
    def default(self, o):
        # 形參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, cls=MyJsonEncoder)
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方法
"""


面向物件三大特徵之封裝

# 封裝的含義
將類中的某些名字'隱藏'起來 不讓外界直接呼叫
隱藏的目的是為了提供專門的通道去訪問 在通道內可以新增額外的功能

# 封裝的概念
在python中用雙下劃線,開頭的方式將屬性隱藏起來(設定成私有的)
但其實這只是一種變形操作,而且僅僅在類定義階段會發生變形
類中所有雙下劃線開頭的如__x都會在類定義的時候自動形成:_類名__x的形式

# 程式碼示例:
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(Student.__dict__)  
"""
如何封裝名字
    在變數名的前面加上兩個下劃線__
封裝的功能只在類定義階段才能生效!!!
    在類中封裝其實也不是絕對的 僅僅是做了語法上的變形而已
        __變數名       >>>     _類名__變數名
我們雖然指定了封裝的內部變形語法 但是也不能直接去訪問
    看到了就表示這個屬性需要通過特定的通道(介面)去訪問
"""

通過介面呼叫

# 封裝的真諦在於明確地區分內外,封裝的屬性可以直接在內部使用,而不能在外部直接使用,然而定義
# 屬性的目的終歸是要用,外部想要用類隱藏的屬性,需要我們為其開闢介面

 1,封裝資料
    將資料隱藏起來不是目的,隱藏起來然後提供操作該資料的介面,然後我們可以在
   '''介面上附加對返資料操作的限制,以此完成對資料屬性的嚴格控制
      類比於電腦上的各種配件,就是被封裝的資料,我們可以通過操作來改變電腦硬體的配置,
       例如我們要加一塊記憶體條,只需要知道介面,頻率等引數,而不用知道記憶體條是什麼材質的,
       也不用知道記憶體條內部顆粒的構成'''
class Teacher:
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
        self.set_info(name,age)
    def tell_info(self):
        print('name:%s,age:%s' %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError("姓名必須是字串型別")
        if not isinstance(age,int):
            raise TypeError("年齡必須是整數型別")
        self.__name = name
        self.__age = age
t = Teacher('jason',18)
t.tell_info()
t.set_info('kevin',28)
t.tell_info()
2,封裝方法:目的是隔離複雜度
   就像我們玩電腦一樣,我們不需要知道電腦的工作原理,只需要知道他是怎麼用的就可以了
    
"""
將資料隱藏起來就限制了類外部對資料的直接操作,然後類內應該提供相應的介面來允許類外部間接地操作資料,
    介面之上可以附加額外的邏輯來對資料的操作進行嚴格地控制
    
目的的是為了隔離複雜度,例如ATM程式的取款功能,該功能有很多其他功能組成
    比如插卡、身份認證、輸入金額、列印小票、取錢等,而對使用者來說,只需要開發取款這個功能介面即可,其餘功能我們都可以隱藏起來
""" 

property

# property 的定義
Property 是 Python 類的一個內建的 裝飾器. @property 的作用是 將一個方法, 變為屬性來呼叫

# 訪問類的私有屬性
class Foo:
    def __init__(self, score):
        self.__score = score

    @property  # 將方法裝飾為屬性
    def show_score(self):
        return self.__score


    s = Foo(66)
    # 在寫法上更加優雅, 不用多寫 "方法()" 沒用的括號
    print(s.show_score)

property擴充套件

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)

property示例

'''封裝一個Book的類, 主要用來描述書籍的名稱, 價格和評論.  要求能用 property 裝飾器實現對 Book 的例項屬性 set 和 get 方法進行定義. 即讓例項物件可以用屬性的方式來操作方法/屬性'''

class Book:
    def __init__(self, name, price, comment=None):
        self.__name = name
        self.__price = price
        self.__comment = comment

    # 傳統方法對price屬性進行 設定和訪問
    def get_price(self):
        return self.__price

    def set_price(self, value):
        self.__price = value

    # price = property(get_price, set_price)

    # @property 裝飾器對 comment 屬性進行 設定和訪問
    @property
    def my_comment(self):
        # 預設是 getter, 需要return 或 yeild
        return self.__comment

    @my_comment.setter
    def my_comment(self, value):
        # 檢驗
        if not isinstance(value, float):
            raise ValueError("must be a decimal number")

        if value < 0 or value > 1000:
            raise ValueError("0 ~ 1000")

        self.__comment = value

    @my_comment.deleter
    def my_comment(self):
        print("delete this atrr...")
        

面向物件三大特性之多型

# 什麼是多型
	一種事物的多種形態
  	eg:
      水  固態 液態 氣態
 		  動物	貓、狗、豬

# 多型性
	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('哼哼哼')
"""
上述場景下 雖然體現了事物的多型性 但是並沒有完整的體現出來
因為現在不同的形態去叫 需要呼叫不同的方法 不夠一致
    
    只要你是動物 那麼你想要說話 就應該呼叫一個相同的方法 這樣便於管理
"""

多型性的好處在於增強了程式的靈活性和可擴充套件性,比如通過繼承Animal類建立了一個新的類,例項化得到的物件obj,可以使用相同的方式使用obj.speak()


雖然python推崇的是自由 但是也提供了強制性的措施來實現多型性
    不推薦使用

多型的鴨子型別

# python推崇的是鴨子型別
# linux 一切皆是檔案(對於檔案操作一個讀,一個寫)
# 通過多型統一的思想格式把cpu 記憶體 txt檔案做的像檔案,對其進行操作
class Cpu:
    def read(self):
        print('cpu read')
    def write(self):
        print('cpu write')

class Mem: # 記憶體
    def read(self):
        print('mem read')
    def write(self):
        print('mem write')

class Txt:
    def read(self):
        print('txt read')
    def write(self):
        print('txt write')

# 統一使用
obj1 = Cpu()
obj2 = Mem()
obj3 = Txt()

obj1.read()
obj1.write()
obj2.read()
obj2.write()
obj3.read()
obj3.write()

'''得到記憶體或者硬碟物件之後 只要想讀取資料就呼叫read 想寫入資料就呼叫write 不需要考慮具體的物件是誰'''

面向物件之反射

# 什麼是反射
專業解釋:指程式可以訪問、檢測和修改本身狀態或者行為的一種能力
    
  大白話:其實就是通過字串來操作物件的資料和功能
    
# 反射需要掌握的四個方法
  hasattr():判斷物件是否含有字串對應的資料或者功能
        
  getattr():根據字串獲取對應的變數名或者函式名
    
  setattr():根據字串給物件設定鍵值對(名稱空間中的名字)
    
  delattr():根據字串刪除物件對應的鍵值對(名稱空間中的名字)
    
# 哪些物件可以使用反射

python中一切都是物件,所以都可以使用反射來進行自省

類可以對類的共有屬性,方法(繫結方法,非繫結方法)進行反射

當前模組也可以進行反射 如:判定匯入的模組是否有某個方法,有的化就進行呼叫。

反射的實際應用

class Student(object):
    school = '清華大學'
    def get(self):
        pass
  # 編寫一個小程式 判斷Student名稱空間中是否含有使用者指定的名字 如果有則取出展示
  # print(Student.__dict__)
  '''字串的school 跟 變數名school差距大不大?  本質區別'''
 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>

反射的好處

#反射的好處
實現可插拔機制 
就是可先判定某個物件(模組物件,類物件,物件等)是否有某個屬性(是否插入),有則呼叫處理
沒有(拔出)則走另一條邏輯
這樣可以實現團隊開發中 實現預定好介面 就算呼叫的介面沒有具體完成,呼叫方也可以完成自己的邏輯

動態模組匯入

#動態模組匯入:a.import(模組名)函式 b. 使用importlib模組,使用importlib的import_module('模組名')

#動態匯入模組使用場景:
1. 動態引用模組的變數,可以利用反射,切換其引用的模組,並使用模組中的屬性。
2. 使用反射判斷是否有對應屬性,有則幹嘛,沒有則幹嘛。 兩種場景都是利用物件的反射來處理。核心就是利用字串來驅動不同的事件,比如匯入模組,呼叫函式等。

這是一種程式設計方法,設計模式的提現,凝聚了高內聚、鬆耦合的程式設計思想,不能簡單的用執行字串來代替。反射和exec()和eval()不同。

反射的案例

# 利用反射獲取配置檔案中的配置資訊
"""一切皆物件 檔案也是物件"""
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()