1. 程式人生 > 實用技巧 >類的裝飾器,繫結方法與非繫結方法,繼承,繼承背景下的屬性查詢

類的裝飾器,繫結方法與非繫結方法,繼承,繼承背景下的屬性查詢

一、類的裝飾器

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)
...
>>> obj=People('lili',75,1.85)
>>> obj.bmi #觸發方法bmi的執行,將obj自動傳給self,執行後返回值作為本次引用的結果
21.913805697589478

使用property有效地保證了屬性訪問的一致性。另外property還提供設定和刪除屬性的功能,如下

>>> 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')
...
>>> f=Foo('lili')
>>> f.name
lili
>>> f.name='LiLi' #觸發name.setter裝飾器對應的函式name(f,’Egon')
>>> f.name=123 #觸發name.setter對應的的函式name(f,123),丟擲異常TypeError
>>> del f.name #觸發name.deleter對應的函式name(f),丟擲異常PermissionError

二、繫結方法與非繫結方法

類中定義的函式分為兩大類:繫結方法和非繫結方法

其中繫結方法又分為繫結到物件的物件方法和繫結到類的類方法。

在類中正常定義的函式預設是繫結到物件的,而為某個函式加上裝飾器@classmethod後,該函式就繫結到了類。

繫結方法的特點:繫結給誰就應該由誰來呼叫,誰來呼叫就會將自己當做第一個引數傳入

為類中某個函式加上裝飾器@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)

三、繼承

繼承是建立新類的一種方式

新建的類稱之為子類, 被繼承的類稱之為父類、基類、超類

繼承的特點是:子類可以遺傳父類的屬性

類是用解決物件之間冗餘問題的, 而繼承則是來解決類與類之間冗餘問題的

在python中支援多繼承

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承
    pass

class SubClass2(ParentClass1,ParentClass2): #多繼承
    pass

通過類的內建屬性__bases__可以檢視類繼承的所有父類

>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

在Python2中有經典類與新式類之分,沒有顯式地繼承object類的類,以及該類的子類,都是經典類,顯式地繼承object的類,以及該類的子類,都是新式類。而在Python3中,即使沒有顯式地繼承object,也會預設繼承該類,在python3中全都是新式類。如下

>>> ParentClass1.__bases__
(<class ‘object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

繼承與抽象(先抽象再繼承)

繼承描述的是子類與父類之間的關係,是一種什麼是什麼的關係。要找出這種關係,必須先抽象再繼承

抽象即抽取類似或者說比較像的部分。

繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。

四、繼承背景下的屬性查詢

有了繼承關係,物件在查詢屬性時,先從物件自己的--dict--中找,如果沒有則去子類中找,然後再去父類中找……

>>> class Foo:
...     def f1(self):
...         print('Foo.f1')
...     def f2(self):
...         print('Foo.f2')
...         self.f1()
... 
>>> class Bar(Foo):
...     def f1(self):
...         print('Foo.f1')
... 
>>> b=Bar()
>>> b.f2()
Foo.f2
Foo.f1

b.f2()會在父類Foo中找到f2,先列印Foo.f2,然後執行到self.f1(),即b.f1(),仍會按照:物件本身->類Bar->父類Foo的順序依次找下去,在類Bar中找到f1,因而列印結果為Foo.f1

父類如果不想讓子類覆蓋自己的方法,可以採用雙下劃線開頭的方式將方法設定為私有的

>>> class Foo:
...     def __f1(self): # 變形為_Foo__fa
...         print('Foo.f1') 
...     def f2(self):
...         print('Foo.f2')
...         self.__f1() # 變形為self._Foo__fa,因而只會呼叫自己所在的類中的方法
... 
>>> class Bar(Foo):
...     def __f1(self): # 變形為_Bar__f1
...         print('Foo.f1')
... 
>>> 
>>> b=Bar()
>>> b.f2() #在父類中找到f2方法,進而呼叫b._Foo__f1()方法,同樣是在父類中找到該方法
Foo.f2
Foo.f1