從零學Python:17課-物件導向程式設計(進階)

千鋒Python唐小強發表於2020-07-28

上一節課我們講解了Python物件導向程式設計的基礎入門知識,這一節課我們繼續來討論物件導向程式設計相關的內容。

另外上節課很多夥伴要了影片學習教程,提醒大家哈,認真的看完,不清楚的地方,可以再說!

可見性和屬性裝飾器

在很多物件導向程式語言中,物件的屬性通常會被設定為私有(private)或受保護(protected)的成員,簡單的說就是不允許直接訪問這些屬性;物件的方法通常都是公開的(public),因為公開的方法是物件能夠接受的訊息,也是物件暴露給外界的呼叫介面,這就是所謂的訪問可見性。在Python中,可以透過給物件屬性名新增字首下劃線的方式來說明屬性的訪問可見性,例如,可以用__name表示一個私有屬性,_name表示一個受保護屬性,程式碼如下所示。



class 
Student:


    def __init__ (self, name, age):
       self.__name = name
       self.__age = age

    def study (self, course_name):
       print( f' {self.__name}正在學習 {course_name}.')


stu = Student( '王大錘', 20)
stu.study( 'Python程式設計')
print(stu.__name)

上面程式碼的最後一行會引發AttributeError(屬性錯誤)異常,異常訊息為:'Student' object has no attribute '__name'。由此可見,以__開頭的屬性__name是私有的,在類的外面無法直接訪問,但是類裡面的study方法中可以透過self.__name訪問該屬性。

需要提醒大家的是,Python並沒有從語法上嚴格保證私有屬性的私密性,它只是給私有的屬性和方法換了一個名字來阻撓對它們的訪問,事實上如果你知道更換名字的規則仍然可以訪問到它們,我們可以對上面的程式碼稍作修改就可以訪問到私有的。



class 
Student:


    def __init__ (self, name, age):
       self.__name = name
       self.__age = age

    def study (self, course_name):
       print( f' {self.__name}正在學習 {course_name}.')


stu = Student( '王大錘', 20)
stu.study( 'Python程式設計')
print(stu._Student__name, stu._Student__age)

Python中做出這樣的設定是基於一句名言:“ We are all consenting adults here”(大家都是成年人)。Python語言的設計者認為程式設計師要為自己的行為負責,而不是由Python語言本身來嚴格限制訪問可見性,而大多數的程式設計師都認為 開放比封閉要好,把物件的屬性私有化並不是必須的東西。

Python中可以透過property裝飾器為“私有”屬性提供讀取和修改的方法,裝飾器通常會放在類、函式或方法的宣告之前,透過一個@符號表示將裝飾器應用於類、函式或方法。裝飾器的概念我們會在稍後的課程中以專題的形式為大家講解,這裡我們只需要瞭解property裝飾器的用法就可以了。



class 
Student:


    def __init__(self, name, age):
       self.__name = name
       self.__age = age

   # 屬性訪問器(getter方法) - 獲取__name屬性
    @property
   def name(self):
        return self.__name

   # 屬性修改器(setter方法) - 修改__name屬性
    @name.setter
   def name(self, name):
       # 如果name引數不為空就賦值給物件的__name屬性
       # 否則將__name屬性賦值為 '無名氏',有兩種寫法
       # self.__name = name if name else '無名氏'
       self.__name = name or '無名氏'

    @property
   def age(self):
        return self.__age


stu = Student( '王大錘', 20)
print(stu.name, stu.age)    # 王大錘 20
stu.name = ''
print(stu.name)    # 無名氏
# stu.age = 30     # AttributeError: can 't set attribute

在實際專案開發中,我們並不經常使用私有屬性,屬性裝飾器的使用也比較少,所以上面的知識點大家簡單瞭解一下就可以了。

動態屬性

Python是一門動態語言,維基百科對動態語言的解釋是:“在執行時可以改變其結構的語言,例如新的函式、物件、甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。動態語言非常靈活,目前流行的Python和JavaScript都是動態語言,除此之外如PHP、Ruby等也都屬於動態語言,而C、C++等語言則不屬於動態語言”。

在Python中,我們可以動態為物件新增屬性,這是Python作為動態型別語言的一項特權,程式碼如下所示。需要提醒大家的是,物件的方法其實本質上也是物件的屬性,如果給物件傳送一個無法接收的訊息,引發的異常仍然是AttributeError。



class 
Student:


    def __init__ (self, name, age):
       self.name = name
       self.age = age


stu = Student( '王大錘', 20)
# 為Student物件動態新增sex屬性
stu.sex = '男'

如果不希望在使用物件時動態的為物件新增屬性,可以使用Python的__slots__魔法。對於Student類來說,可以在類中指定__slots__ = ('name', 'age'),這樣Student類的物件只能有name和age屬性,如果想動態新增其他屬性將會引發異常,程式碼如下所示。



class 
Student:

    __slots__ = ( 'name', 'age')

   def __init__(self, name, age):
       self.name = name
       self.age = age


stu = Student( '王大錘', 20)
# AttributeError: 'Student' object has no attribute 'sex'
stu.sex = '男'

靜態方法和類方法

之前我們在類中定義的方法都是物件方法,換句話說這些方法都是物件可以接收的訊息。除了物件方法之外,類中還可以有靜態方法和類方法,這兩類方法是發給類的訊息,二者並沒有實質性的區別。在物件導向的世界裡,一切皆為物件,我們定義的每一個類其實也是一個物件,而靜態方法和類方法就是傳送給類物件的訊息。那麼,什麼樣的訊息會直接傳送給類物件呢?

舉一個例子,定義一個三角形類,透過傳入三條邊的長度來構造三角形,並提供計算周長和麵積的方法。計算周長和麵積肯定是三角形物件的方法,這一點毫無疑問。但是在建立三角形物件時,傳入的三條邊長未必能構造出三角形,為此我們可以先寫一個方法來驗證給定的三條邊長是否可以構成三角形,這種方法很顯然就不是物件方法,因為在呼叫這個方法時三角形物件還沒有建立出來。我們可以把這類方法設計為靜態方法或類方法,也就是說這類方法不是傳送給三角形物件的訊息,而是傳送給三角形類的訊息,程式碼如下所示。



class 
Triangle
(object):

    """三角形類"""

    def __init__ (self, a, b, c):
        """初始化方法"""
       self.a = a
       self.b = b
       self.c = c

   @staticmethod
    def is_valid (a, b, c):
        """判斷三條邊長能否構成三角形(靜態方法)"""
        return a + b > c and b + c > a and a + c > b

    # @classmethod
   # def is_valid(cls, a, b, c):
   #     """判斷三條邊長能否構成三角形(類方法)"""
   #     return a + b > c and b + c > a and a + c > b

   def perimeter(self):
       """計算周長"""
       return self.a + self.b + self.c

   def area(self):
       """計算面積"""
       p = self.perimeter() / 2
       return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5

上面的程式碼使用staticmethod裝飾器宣告瞭is_valid方法是Triangle類的靜態方法,如果要宣告類方法,可以使用classmethod裝飾器。可以直接使用類名.方法名的方式來呼叫靜態方法和類方法,二者的區別在於,類方法的第一個引數是類物件本身,而靜態方法則沒有這個引數。簡單的總結一下, 物件方法、類方法、靜態方法都可以透過類名.方法名的方式來呼叫,區別在於方法的第一個引數到底是普通物件還是類物件,還是沒有接受訊息的物件。靜態方法通常也可以直接寫成一個獨立的函式,因為它並沒有跟特定的物件繫結。

從零學Python:17課-物件導向程式設計(進階)

繼承和多型

物件導向的程式語言支援在已有類的基礎上建立新類,從而減少重複程式碼的編寫。提供繼承資訊的類叫做父類(超類、基類),得到繼承資訊的類叫做子類(派生類、衍生類)。例如,我們定義一個學生類和一個老師類,我們會發現他們有大量的重複程式碼,而這些重複程式碼都是老師和學生作為人的公共屬性和行為,所以在這種情況下,我們應該先定義人類,再透過繼承,從人類派生出老師類和學生類,程式碼如下所示。



class 
Person:

    """人類"""

    def __init__ (self, name, age):
       self.name = name
       self.age = age

    def eat (self):
       print( f' {self.name}正在吃飯.')

    def sleep (self):
       print( f' {self.name}正在睡覺.')


class Student (Person):
    """學生類"""

    def __init__ (self, name, age):
        # super(Student, self).__init__(name, age)
       super().__init__(name, age)

   def study(self, course_name):
       print(f'{self.name}正在學習{course_name}.')


class Teacher(Person):
   """老師類"""

   def __init__(self, name, age, title):
       # super(Teacher, self).__init__(name, age)
       super().__init__(name, age)
       self.title = title

   def teach(self, course_name):
       print(f'{self.name}{self.title}正在講授{course_name}.')



stu1 = Student('白元芳', 21)
stu2 = Student('狄仁傑', 22)
teacher = Teacher('武則天', 35, '副教授')
stu1.eat()
stu2.sleep()
teacher.teach('Python程式設計')
stu1.study('Python程式設計')

繼承的語法是在定義類的時候,在類名後的圓括號中指定當前類的父類。Python語言允許多重繼承,也就是說一個類可以有一個或多個父類,關於多重繼承的問題我們在後面會有更為詳細的討論。在子類的初始化方法中,我們可以透過super().__init__()來呼叫父類初始化方法,super函式是Python內建函式中專門為獲取當前物件的父類物件而設計的。從上面的程式碼可以看出,子類除了可以透過繼承得到父類提供的屬性和方法外,還可以定義自己特有的屬性和方法,所以子類比父類擁有的更多的能力。在實際開發中,我們經常會用子類物件去替換掉一個父類物件,這是物件導向程式設計中一個常見的行為,也叫做“里氏替換原則”(Liskov Substitution Principle)。

子類繼承父類的方法後,還可以對方法進行重寫(重新實現該方法),不同的子類可以對父類的同一個方法給出不同的實現版本,這樣的方法在程式執行時就會表現出多型行為(呼叫相同的方法,做了不同的事情)。多型是物件導向程式設計中最精髓的部分,當然也是對初學者來說最難以理解和靈活運用的部分,我們會在下一節課中用專門的例子來講解多型這個知識點。

簡單的總結

Python是動態語言,Python中的物件可以動態的新增屬性。在物件導向的世界中, 一切皆為物件,我們定義的類也是物件,所以 類也可以接收訊息,對應的方法是類方法或靜態方法。透過繼承,我們 可以從已有的類建立新類,實現對已有類程式碼的複用。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2707473/,如需轉載,請註明出處,否則將追究法律責任。

相關文章