Python封裝,多型,反射
派生的實際應用
當前需要序列化一個字典,如下程式碼:
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所有的資料型別 只能是一些基本資料型別 '''
解決方法:
方法1 - 手動將不能序列化的型別先轉字串
d1={'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())}
res = json.dumps(d1)
print(res) # {"t1": "2022-04-08 20:45:06.122407", "t2": "2022-04-08"}
方法2 - 研究json原始碼並重寫序列化方法
''' 研究原始碼發現報錯的方法叫default raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__) 我們可以寫一個類繼承JSONEncoder然後重寫default方法 ''' 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,cls=MyJsonEncoder) print(res) # {"t1": "2022-04-08 20:51:41", "t2": "2022-04-08"}
面向物件三大特性之封裝
封裝的含義
將類中的某些名字'隱藏'起來,不讓外界直接呼叫。
隱藏的目的是為了提供專門的通道去訪問,在通道內可以新增額外的功能。
如何封裝名字?
1. 在變數名的前面加上兩個下劃線__,封裝的功能只在類定義階段才能生效!!!
2.在類中封裝其實也不是絕對的,僅僅是做了語法上的變形而已,'__變數名'變成了'_類名__變數名'。
3.雖然指定了封裝的內部變形語法,但是也不能直接去訪問看到了就表示這個屬性需要通過特定的通道(介面)去訪問。
封裝名字演示:
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) # 報錯,'Student' object has no attribute '__label' 因為隱藏了__label print(Student.__dict__) # '_Student__label': '隱藏項!!' print(Student._Student__label) # 隱藏項!! 可以通過_類名__變數名 取到值,但是也不能直接去訪問 print(stu1._Student__label) # 隱藏項!! 表示這個屬性需要通過特定的通道(介面)去訪問
將資料隱藏起來就限制了外幣對資料的直接操作,讓後類內應該提供相應的介面來允許類外部間接地操作資料,介面之上可以附加額外的邏輯來對資料的操作進行嚴格的控制,目的是為了隔離複雜度,例如ATM程式的取款功能,該功能有很多其他功能組成, 比如插卡、身份認證、輸入金額、列印小票、取錢等,而對使用者來說,只需要開發取款這個功能介面即可,其餘功能我們都可以隱藏起來。
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('','11')
stu1.check_info()
property偽裝
property就是將方法偽裝成資料。
很多時候有些資料需要通過計算才能獲取,但這些資料給我們的感覺應該是屬於資料而不是功能,這個時候我們就可以用到property來把功能偽裝成資料。
# 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.74, 80)
# p1.BMI() 報錯 'str' object is not callable 已偽裝成資料,不能通過()呼叫。
print(p1.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() # 哼哼哼
上述場景下雖然體現了事物的多型性 ,但是並沒有完整的體現出來。因為現在不同的形態去叫,需要呼叫不同的方法,不夠一致。只要你是動物,那麼你想要說話,就應該呼叫一個相同的方法 這樣便於管理。
面向物件裡的多型性:len方法
s1 = 'hello world'
l1 = [1, 2, 3, 4]
d1 = {'name': 'jason', 'pwd': 123}
print(len(s1))
print(len(l1))
print(len(d1))
多型性的好處在於增強了程式的靈活性和可擴充套件性,比如通過繼承Animal類建立了一個新的類,例項化得到的物件obj,可以使用相同的方式使用obj.speak()。狗,貓,豬都可以呼叫speak()發出自己的叫聲。
強制多型性
雖然python推崇的是自由,但是也提供了強制性的措施來實現多型性,使用了abc模組,不推薦使用性多型。
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
hasattr():判斷物件是否含有字串對應的資料或者功能
print(asattr(Student, 'school')) # True
print(hasattr(Student, 'get')) # True
print(hasattr(Student, 'post')) # False
getattr():根據字串獲取對應的變數名或者函式名
print(getattr(Student, 'school')) # 清華大學
print(getattr(Student, 'get')) # <function Student.get at 0x0000022EFDCDCC80>
setattr():根據字串給物件設定鍵值對(名稱空間中的名字)
setattr(Student,'隸屬','211和985')
print(Student.__dict__) # '隸屬': '211和985'
obj1=Student()
setattr(obj1,'地裡位置','北京市海淀區清華園')
print(obj1.__dict__) # {'地裡位置': '北京市海淀區清華園'}
delattr():根據字串刪除物件對應的鍵值對(名稱空間中的名字)
delattr(obj1,'地裡位置')
print(obj1.__dict__) # {}
反射實際案例
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()