程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

小白學python系列————【Day44】面向對象三大特性之封裝與多態;面向對象之反射

編輯:Python

今日內容概要

  • 派生方法的實戰演練(重要)
  • 面向對象三大特性之封裝
  • property偽裝屬性
  • 面向對象三大特性之多態
  • 面向對象之反射(重要)
  • 反射實戰(重要)

派生方法的實戰演練(重要)

1.前戲

import datetime
import json
d = {

't1':datetime.datetime.today(),
't2':datetime.date.today()
}
res = json.dumps(d)
print(res)
''' TypeError: Object of type 'datetime' is not JSON serializable '''

(1)此處代碼會報錯,無法正常序列化,原因是json序列化python數據類型是有限制的,並不適用於所有類型。
(2)利用json.JSONEncoder查看json序列化可以適用數據類型

(3)上述json存儲的為時間類型數據,所以不會被序列化。即將要被序列化的數據,裡裡外外都必須要保證是上述類型才行。

2.引出
針對上述不能被序列化的數據類型,給出下列解決辦法。
(1)解決方式一:
手動將不符合數據類型轉成符合要求的。

import datetime
import json
d = {

't1':str(datetime.datetime.today()),
't2':str(datetime.date.today())
}
res = json.dumps(d)
print(res)
''' {"t1": "2022-07-28 15:48:31.269905", "t2": "2022-07-28"} '''

(2)解決方式二:
利用派生方法。
查看dumps方法後得出:

class JSONEncoder:
pass
dumps(obj,cls=None):
if cls == None:
cls = JSONEncoder
return cls(...) # JSONEncoder()

(1)查看JSONEncoder源碼發現序列化報錯是有default方法觸發的

raise TypeError(f'Object of type {
o.__class__.__name__} '
f'is not JSON serializable')

(2)我們如果想要避免報錯 那麼肯定需要對default方法做修改(派生)

import datetime
import json
d = {

't1':datetime.datetime.today(),
't2':datetime.date.today()
}
class MyJsonEncode(json.JSONEncoder):
def default(self, o):
'''o就是json即將要序列化的數據'''
if isinstance(o, datetime.datetime):
return o.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(o, datetime.date):
return o.strftime('%Y-%m-%d')
# 如果是可以序列化的類型 那麼不做任何處理 直接讓它序列化即可
return super().default(o)
res = json.dumps(d, cls=MyJsonEncode)
print(res)
json.dumps(d, cls=MyJsonEncode)

面向對象三大特性之封裝

1.封裝的概念
封裝其實就是將數據或者功能隱藏起來;隱藏的目的不是讓用戶無法使用,而是給這些隱藏的數據開設特定的接口,讓用戶使用接口才可以去使用,我們在接口中添加一些額外的操作。

2.封裝隱藏特性
(1)在類定義階段使用雙下劃線開頭的名字都是隱藏屬性。
後續類和對象都無法直接獲取
(2)在python中不會真正的限制任何代碼。
隱藏的屬性如果真的需要訪問,也可以經過變形處理。不過這樣就失去了隱藏的意義!!!

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)
print(stu1._Student__name)
''' jason '''
# 修改名字方法:
stu1.set_info('kevin',20)
print(stu1._Student__name)
''' kevin'''

4.代碼封裝之君子協定
我們在編寫python很多時候都是大家墨守成規的東西,不需要真正的限制(君子協定):

class A:
_school = '清華大學'
def _choice_course(self):
pass

python偽裝屬性

1.偽裝的含義
可以簡單地理解為將方法偽裝成數據。
數據只需要點名字:obj.name
方法至少還要加括號:obj.func()
偽裝之後可以將func方法偽裝成數據:obj.func

2. @property主要功能作用
具體案例代碼展示:
(1)# 沒有加裝飾器@property之前————p1.BMI() #加括號調用

class Person:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
def BMI(self):
return self.weight / (self.height ** 2)
# 沒有加裝飾器@property之前
p1 = Person('jason',57,1.73)
res = p1.BMI() # 加括號調用
print(res)
''' 19.045073340238563'''
"""BMI雖然需要計算獲得 但是更像是人的數據"""

(2)# 添加裝飾器@property之後————p1.BMI #直接調用

class Person:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
@property
def BMI(self):
return self.weight / (self.height ** 2)
# 添加裝飾器@property之後
p1 = Person('jason', 78, 1.83)
print(p1.BMI) # 23.291229956104985
print(p1.name) # jason 

3.知識點的加深之偽裝的徹底(了解)
需求:增加了用戶修改和刪除裝飾器

class Foo:
def __init__(self, val):
self.__NAME = val # 將屬性隱藏起來
@property
def name(self):
return self.__NAME
@name.setter
def name(self, value):
if not isinstance(value, str): # 在設定值之前進行類型檢查
raise TypeError('%s must be str' % value)
self.__NAME = value # 通過類型檢查後,將值value存放到真實的位置self.__NAME
@name.deleter
def name(self):
raise PermissionError('Can not delete')
obj = Foo('jason')
print(obj.name) # jason
obj.name = 'kevin'
print(obj.name) # kevin
del obj.name
# PermissionError: Can not delete

面向對象三大特性之多態

1.多態的字面含義
多態:一種事物的多種形態
水:液態 氣態 固態
動物:人 狗 貓 豬

2.具體實例代碼展示
(1)案例一:
前戲之動物叫聲單獨方法調用:

class Animal(object):
def spark(self):
pass
class Cat(Animal):
def miao(self):
print('喵喵喵')
class Dog(Animal):
def wang(self):
print('汪汪汪')
#
c1 = Cat()
d1 = Dog()
c1.miao() # 喵喵喵
d1.wang() # 汪汪汪
""" 一種事物有多種形態,但是相同的功能應該有相同的名字; 這樣的話以後我們無論拿到具體的動物,都不需要知道到底是哪種動物,>直接調用相同的功能即可。 """

引出之不同動物叫聲用一個方法(spark)調用:

""" eg: 無論是雞鴨狗豬,只要叫就調用固定的屬於叫的功能!!! """
class Animal(object):
def spark(self):
pass
class Cat(Animal):
def spark(self):
print('喵喵喵')
class Dog(Animal):
def spark(self):
print('汪汪汪')
c1.spark() # 喵喵喵
d1.spark() # 汪汪汪

(2)案例二:
不論是元組,列表還是字典,統計長度都是len方法調用。

l1 = [11, 22, 33, 44]
d1 = {
'name': 'jason', 'pwd': 123, 'hobby': 'raed'}
t1 = (11, 22, 33, 44)
print(len(l1))
print(len(d1))
print(len(t1))

3.多態性的概念
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
def run(self):
pass
obj = Person()

4.鴨子類型
大白話解釋:只要你長得像鴨子,走路像鴨子,說話像鴨子,那麼你就是鴨子。

class Teacher:
def run(self):pass
def eat(self):pass
class Student:
def run(self):pass
def eat(self):pass

5.多態不同系統中解釋(鴨子類型推導)
(1)linux系統:一切皆文件
只要你能讀數據 能寫數據 那麼你就是文件
內存
硬盤

class Txt: # Txt類有兩個與文件類型同名的方法,即read和write
def read(self):
pass
def write(self):
pass
class Disk: # Disk類也有兩個與文件類型同名的方法:read和write
def read(self):
pass
def write(self):
pass
class Memory: # Memory類也有兩個與文件類型同名的方法:read和write
def read(self):
pass
def write(self):
pass

(2)python:一切皆對象
只要你有數據 有功能 那麼你就是對象
文件名 文件對象
模塊名 模塊對象

面向對象之反射

1.反射的含義
通過字符串來操作對象的數據或方法

2.反射四個方法:
hasattr():判斷對象是否含有某個字符串對應的屬性
getattr():獲取對象字符串對應的屬性
setattr():根據字符串給對象設置屬性
delattr():根據字符串給對象刪除屬性

3. 反射實例代碼展示

class Student:
school = '清華大學'
def choice_course(self):
print('選課')
stu = Student()
# 需求:判斷用戶提供的名字在不在對象可以使用的范圍內
# 方式1:利用異常處理(過於繁瑣)
# try:
# if stu.school:
# print(f"True{stu.school}")
# except Exception:
# print("沒有屬性")
""" 變量名school 與字符串school 區別大不大 stu.school stu.'school' 兩者雖然只差了引號 但是本質是完全不一樣的 """
# 方式2:獲取用戶輸入的名字 然後判斷該名字對象有沒有
# while True:
# target_name = input('請輸入您想要核查的名字>>>:').strip()
# '''上面的異常更加不好實現 需要用反射'''
# # print(hasattr(stu, target_name))
# # print(getattr(stu, target_name))
# if hasattr(stu, target_name):
# # print(getattr(stu, target_name))
# res = getattr(stu, target_name)
# if callable(res):
# print('拿到的名字是一個函數', res())
# else:
# print('拿到的名字是一個數據', res)
# else:
# print('不好意思 您想要查找的名字 對象沒有')
print(stu.__dict__)
stu.name = 'jason'
stu.age = 18
print(stu.__dict__)
setattr(stu, 'gender', 'male')
setattr(stu, 'hobby', 'read')
print(stu.__dict__)
del stu.name
print(stu.__dict__)
delattr(stu, 'age')
print(stu.__dict__)

4.反射用法
需求裡面只要看到…字符串或者…對象那麼肯定需要使用反射!!!

5.反射實戰案例


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. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved