19.面向物件:封裝、繫結方法與非繫結方法、繼承
阿新 • • 發佈:2021-01-11
-
引子
-
封裝
-
隱藏屬性
-
類內的裝飾器:特性(property)
-
繫結方法與非繫結方法
-
繼承
-
先抽象再繼承
-
繼承背景下的屬性查詢
-
封裝
封裝指的就是把資料與功能都整合到一起,聽起來是不是很熟悉,沒錯,我們之前所說的”整合“二字其實就是封裝的通俗說法。
除此之外,針對封裝到物件或者類中的屬性,我們還可以嚴格控制對它們的訪問,分兩步實現:隱藏與開放介面.
-
隱藏屬性
在python中用雙下劃線開頭的方式將屬性隱藏起來(設定成私有的),但其實這僅僅只是一種變形操作
# 類中所有雙下劃線開頭的名稱如__x都會在類定義時自動變形成:_類名__屬性名的形式: class People: # 類的資料屬性就應該是共享的,但是語法上是可以把類的資料屬性設定成私有的 ↓ __country = "China" # 變形為 _People__country = "china" def __init__(self,name,age): self.__name = name # self._People__name = name self.__age = age # self._people__age = age def __func(self): # _People__func print('xx') def tell_name(self): print(self.__name) # self._People__name print(People._People__country) # 只有在類內部才可以通過__開頭的形式訪問到. print(People._People__func) print(People.__dict__) obj1 = People("egon",18) print(obj1.__dict__) # __開頭的屬性的特點: # 1、並沒有真的藏起來,只是變形了 # 2、該變形只在類定義階段、掃描語法的時候執行,此後__開頭的屬性都不會變形 obj1.__gender = "male" print(obj1.__dict__) print(obj1.__gender ) # 3、該隱藏對外不對內 obj1.tell_name()
-
封裝的真諦在於明確地區分內外,封裝的屬性可以直接在內部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,需要我們為其開闢介面,讓外部能夠間接地用到我們隱藏起來的屬性,那這麼做的意義何在???
1:隱藏資料:將資料隱藏起來這不是目的。隱藏起來然後對外提供操作該資料的介面,然後我們可以在介面附加上對該資料操作的限制,以此完成對資料屬性操作的嚴格控制。
# 為何要隱藏屬性 # 1、隱藏資料屬性為了嚴格控制類外部訪問者對屬性的操作 class People: def __init__(self,name,age): self.__name = name # self._People__name = name self.__age = age def tell_info(self): print("<%s:%s>" %(self.__name,self.__age)) def set_info(self,name,age): if type(name) is not str: print("名字必須是字串") return if type(age) is not int: print("年齡必須是數字") return self.__name = name self.__age = age obj1 = People("egon",18) obj1.tell_info() # <egon:18> obj1.set_info("tom",28) obj1.tell_info() # <tom:28> obj1.set_info(123123123123,28) # 名字必須是字串 obj1.tell_info() # <egon:18>
2:隱藏方法:目的是隔離複雜度
# 取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、列印賬單、取錢 # 對使用者來說,只需要知道取款這個功能即可,其餘功能我們都可以隱藏起來,很明顯這麼做 # 隔離了複雜度,同時也提升了安全性 # 隔離複雜度的例子 class ATM: def __card(self): print('插卡') def __auth(self): print('使用者認證') def __input(self): print('輸入取款金額') def __print_bill(self): print('列印賬單') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
真正的封裝是,經過深入的思考,做出良好的抽象,給出“完整且最小”的介面,並使得內部細節可以對外透明
(注意:對外透明的意思是,外部呼叫者可以順利的得到自己想要的任何功能,完全意識不到內部細節的存在)
-
類內的裝飾器:
-
特性(property)
什麼是特性property
property是一種特殊的屬性,訪問它時會執行一段功能(函式)然後返回值
例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)
成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
體質指數(BMI)=體重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class People:
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)
p1 = People('egon',75,1.8)
# print(p1.bmi()) # 正常訪問p1.bmi()需要加括號
# 23.148148148148145
print(p1.bmi) # 用@property將bmi功能偽裝成了資料屬性
# 23.148148148148145
property的使用:
class People:
def __init__(self,name):
self.__name = name
# def get_name(self):
# return self.__name
@property # 1.檢視行為
def name(self): # 被property裝飾的name函式,其實是__name被藏起來了
return self.__name
@name.setter #
def name(self,x): # 2.修改行為
if type(x) is not str:
raise Exception("名字必須是字串型別")
self.__name = x
@name.deleter # 3.刪除行為
def name(self):
print("不允許刪除")
p1 = People('egon')
# print(p1.get_name) # 訪問
# 對應檢視name行為
print(p1.name) # p1.name通過@property訪問到的self.__name的屬性
# 對應修改name行為
p1.name = 123 # 丟擲異常
p1.name = "EGON" # 正常修改
print(p1.name)
# 對應刪除name行為
del p1.name
print(p1.name)
# ps: 配合property可以先將一個屬性藏起來,__name這個屬性被藏起來了,藏起來之後定義三個函式都叫name
# 這三個函式裡面分別寫上name的1、2、3的三種行為,跟操作一一對應
# 瞭解性知識點:這種方式也可以達到上面操作的效果
class People:
def __init__(self,name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self,x):
if type(x) is not str:
raise Exception("名字必須是字串型別")
self.__name = x
def del_name(self):
print("不允許刪除")
name = property(get_name,set_name,del_name)
p1 = People('egon')
-
繫結方法與非繫結方法
-
一:繫結方法
特點:繫結給誰就應該由誰來呼叫,誰來呼叫就會將自己當做第一個引數傳入
-
-
但凡在類中定義一個函式,預設就是繫結給物件的,應該由物件來呼叫,
會將物件當作第一個引數自動傳入
-
-
-
如果想要將函式繫結給類的話就需要用到繫結到類的方法:用classmethod裝飾器裝飾的方法。
類中定義的函式被classmethod裝飾過,就繫結給類,應該由類來呼叫,
類來呼叫會類本身當作第一個引數自動傳入
-
-
-
二:非繫結方法
特點:不與類和物件繫結,意味著誰都可以來呼叫,但無論誰來呼叫就是一個普通
函式,沒有自動傳參的效果
-
類中定義的函式被staticmethod裝飾過,就成一個非繫結的方法即一個普通函式,誰都可以呼叫,
但無論誰來呼叫就是一個普通函式,沒有自動傳參的效果
注意:與繫結到物件方法區分開,在類中直接定義的函式,沒有被任何裝飾器裝飾的,都是繫結到物件的方法,可不是普通函式,物件呼叫該方法會自動傳值,而staticmethod裝飾的方法,不管誰來呼叫,都沒有自動傳值一說
-
class People:
def __init__(self,name):
self.name = name
# 但凡在類中定義一個函式,預設就是繫結給物件的,應該由物件來呼叫,
# 會將物件當作第一個引數自動傳入
def tell(self):
print(self.name)
# 類中定義的函式被classmethod裝飾過,就繫結給類,應該由類來呼叫,
# 類來呼叫會類本身當作第一個引數自動傳入
@classmethod
def f1(cls): # cls = People
print(cls)
# 類中定義的函式被staticmethod裝飾過,就成一個非繫結的方法即一個普通函式,誰都可以呼叫,
# 但無論誰來呼叫就是一個普通函式,沒有自動傳參的效果
@staticmethod
def f2(x,y):
print(x,y)
p1 = People('egon')
p1.tell()
print(People.f1)
People.f1()
print(People.f2)
print(p1.f2)
People.f2(1,2)
p1.f2(3,4)
# 示例場景
'''
# settings.py 配置檔案模組
NAME = "xxx"
AGE = 103
GENDER = "male"
'''
import settings
class People:
def __init__(self,name,age,gender):
# self.id = self.create_id()
self.name = name
self.age = age
self.gender = gender
def tell_info(self): # 列印詳細資訊
print('<%s:%s:%s>' %(self.name,self.age,self.gender))
@classmethod # 繫結給類的方法
def from_conf(cls): # cls 自動傳入類
print(cls)
return cls(settings.NAME, settings.AGE, settings.GENDER)
# @staticmethod # 非繫結方法 誰都可以來調但沒有自動傳參效果
# def create_id():
# import uuid # 呼叫隨機產生id模組
# return uuid.uuid1() # uuid.uuid1 可以產生隨機編號
p1 = People("egon",18,"male") # 例項化得到一個物件
p2 = People.from_conf() # 通過配置檔案完成例項化 繫結給類的方法應該由類去調
print(p2.__dict__)
# 需求:每個使用者都要有一個自己的隨機id號
# print(p1.create_id())
# print(People.create_id())
# print(p1.__dict__) # 例項化後用戶就有自己的id了
-
繼承
-
什麼是繼承?
繼承是建立新類的一種方式,新建的類稱之為子類。
被繼承的類稱之為父類、基類、超類
繼承的特點:子類可以遺傳父類的屬性
-
類是用來解決物件之間冗餘問題的
-
而繼承則是解決類與類之間冗餘問題的
注意:新建的類可以繼承一個或多個父類(python支援多繼承)
class Parent1(object): # 定義父類
pass
class Parent2(object): # 定義父類
pass
class Sub1(Parent1): # 定義子類,單繼承,繼承基類Parent1
pass
class Sub2(Parent1,Parent2): # 定義子類,多繼承,既繼承基類parent1又繼承基類parent2
pass
# ps:__base__只檢視從左到右繼承的第一個子類,__bases__則是檢視所有繼承的父類
print(Sub1.__bases__) # 檢視基類
# (<class '__main__.Parent1'>,) # 單繼承
print(Sub2.__bases__) # 檢視基類
# (<class '__main__.Parent1'>, <class '__main__.Parent2'>) # 多繼承
# 但凡是繼承了object類的子類以及該子類的子子孫孫類都是新式類
# 反之就是經典類
# 在python2中有新式類與經典類之分,在python3中全都是新式類
# 在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
# 在python2中,顯式地宣告繼承object的類,以及該類的子類,都是新式類
# 在python3中,無論是否繼承object,都預設繼承object,即python3中所有類均為新式類
# print(Parent1.__bases__)
# print(Parent2.__bases__)
-
先抽象再繼承
繼承描述的是子類與父類之間的關係,是一種什麼是什麼的關係。要找出這種關係,必須先抽象再繼承
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.將奧巴馬和梅西這倆物件比較像的部分抽取成類;
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)
繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
-
繼承背景下的屬性查詢
# 示例1
class Bar: # 定義類
def f1(self):
print('Bar.f1')
def f2(self):
print('Bar.f2')
self.f1() # obj.f1()
class Foo(Bar):
def f1(self):
print("Foo.f1")
obj = Foo()
obj.f2() # 先從物件obj找沒有--》去物件的類Foo裡面找沒有--》去Bar裡面找有,會列印'Bar.f2'
# 緊接著找self.f1()調的是obj.f1(),按順序查詢列印結果'foo.f1'
# 示例2
class Bar:
def __f1(self): # 在定義階段掃描語法變形成_Bar__f1
print('Bar.f1')
def f2(self):
print('Bar.f2')
self.__f1() # 在類定義階段掃描語法變形成 self._Bar_f1
class Foo(Bar):
def __f1(self): # _Foo__f1
print("Foo.f1")
obj = Foo()
obj.f2() # 按順序查詢到'Bar.f2'並列印, 緊接著查詢self.__f1(),列印結果Bar.f1