1. 程式人生 > >Python面向物件程式設計詳解

Python面向物件程式設計詳解

類(Class)與物件(Object)

 

類(Class)是用來描述具有相同屬性(Attribute)方法(Method)物件的集合。物件(Object)是類(Class)的具體例項。比如學生都有名字和分數,他們有著共同的屬性。這時我們就可以設計一個學生類, 用於記錄學生的名字和分數,並自定義方法打印出他們的名字和方法。

 

  • 屬性(Attribute): 類裡面用於描述所有物件共同特徵的變數或資料。比如學生的名字和分數。

  • 方法(Method): 類裡面的函式,用來區別類外面的函式, 用來實現某些功能。比如打印出學生的名字和分數。

 

要建立一個類我們需要使用關鍵詞class. 這個學生類Student看上去應該是這樣的:

 

# 建立一個學生類
class Student:
    
    # 定義學生屬性,初始化方法
    def __init__(self, name, score):
        self.name = name
        self.score = score

    # 定義列印學生資訊的方法
    def show(self):
        print("Name: {}. Score: {}".format(self.name, self.score))

 

在這個案例中,我們只定義了一個抽象的類,電腦並沒有建立什麼儲存空間。只有當我們完成類的例項化(Instance)時,電腦才會建立一個具體的物件(Object),併為之分配儲存空間。所以物件(Object)是類(Class)的一個例項。

 

要建立一個具體的學生物件(Object),我們還需要輸入:

 

student1 = Student("John", 100)
student2 = Student("Lucy", 99)

 

在這個案例中,Student是類,student1和student2是我們建立的具體的學生物件。當我們輸入上述程式碼時,Python會自動呼叫預設的__init__

初始建構函式來生成具體的物件。關鍵字self是個非常重要的引數,代表建立的物件本身。

 

當你建立具體的物件後,你可以直接通過student1.name和student1.score來分別獲取學生的名字和分數,也可以通過student1.show()來直接列印學生的名字和分數。

 

類變數(class variables)與例項變數(instance variables)

 

假設我們需要在Student類裡增加一個計數器number,每當一個新的學生物件(Object)被建立時,這個計數器就自動加1。由於這個計數器不屬於某個具體學生,而屬於Student類的,所以被稱為類變數(class variables)。而姓名和分數屬於每個學生物件的,所以屬於例項變數(instance variables),也被稱為物件變數(object variables)。

 

這個新Student類看上去應該是這樣的:

# 建立一個學生類
class Student:

    # number屬於類變數,定義在方法外,不屬於具體例項
    number = 0

    # 定義學生屬性,初始化方法
    # name和score屬於例項變數,定義在方法裡
    def __init__(self, name, score):
        self.name = name
        self.score = score
        # 此處有錯誤
        number = number + 1

    # 定義列印學生資訊的方法
    def show(self):
        print("Name: {}. Score: {}".format(self.name, self.score))

 

類變數和例項變數的區別很大,訪問方式也也不一樣。

 

  • 類變數:類變數在整個例項化的物件中是公用的。類變數定義在類中且在函式體之外。訪問或呼叫類變數的正確方式是類名.變數名或者self.__class__.變數名。self.__class__自動返回每個物件的類名。

  • 例項變數:定義在方法中的變數,屬於某個具體的物件。訪問或呼叫例項變數的正確方式是物件名.變數名或者self.變數名.

 

注意到上述Student類有個錯誤沒? 我們試圖直接使用number = number + 1呼叫屬於類的變數number。正確的方式是使用Student.number或self.__class__.number訪問屬於類的變數。下面的程式碼才是正確的:

 

# 建立一個學生類
class Student:

    # number屬於類變數,不屬於某個具體的學生例項
    number = 0

    # 定義學生屬性,初始化方法
    # name和score屬於例項變數
    def __init__(self, name, score):
        self.name = name
        self.score = score
        Student.number = Student.number + 1

    # 定義列印學生資訊的方法
    def show(self):
        print("Name: {}. Score: {}".format(self.name, self.score))

# 例項化,建立物件
student1 = Student("John", 100)
student2 = Student("Lucy", 99)

print(Student.number)  # 列印2
print(student1.__class__.number) # 列印2

 

類方法(Class method)

 

正如同有些變數只屬於類,有些方法也只屬於類,不屬於具體的物件。你有沒有注意到屬於物件的方法裡面都有一個self引數, 比如__init__(self), show(self)? self是指物件本身。屬於類的方法不使用self引數, 而使用引數cls,代表類本身。另外習慣上對類方法我們會加上@classmethod的修飾符做說明。

 

同樣拿Student為例子,我們不用print函式打印出已建立學生物件的數量,而是自定義一個類方法來列印,我們可以這麼做:

 

class Student:

    # number屬於類變數,不屬於某個具體的學生例項
    number = 0

    # 定義學生屬性,初始化方法
    # name和score屬於例項變數
    def __init__(self, name, score):
        self.name = name
        self.score = score
        Student.number = Student.number + 1

    # 定義列印學生資訊的方法
    def show(self):
        print("Name: {}. Score: {}".format(self.name, self.score))

    # 定義類方法,列印學生的數量
    @classmethod
    def total(cls):
        print("Total: {0}".format(cls.number))


# 例項化,建立物件
student1 = Student("John", 100)
student2 = Student("Lucy", 99)

Student.total()  # 列印 Total: 2

 

類的私有屬性(private attribute)和私有方法(private method)

 

類裡面的私有屬性私有方法以雙下劃線__開頭。私有屬性或方法不能在類的外部被使用或直接訪問。我們同樣看看學生類這個例子,把分數score變為私有屬性,看看會發生什麼。

 

# 建立一個學生類
class Student:

    # 定義學生屬性,初始化方法
    # name和score屬於例項變數, 其中__score屬於私有變數
    def __init__(self, name, score):
        self.name = name
        self.__score = score

    # 定義列印學生資訊的方法
    def show(self):
        print("Name: {}. Score: {}".format(self.name, self.__score))

# 例項化,建立物件
student1 = Student("John", 100)

student1.show()  # 列印 Name: John, Score: 100
student1.__score  # 打印出錯,該屬性不能從外部訪問。

 

如果你將score變成__score, 你將不能直接通過student1.__score獲取該學生的分數。show()可以正常顯示分數,是因為它是類裡面的函式,可以訪問私有變數。

 

私有方法是同樣的道理。當我們把show()變成,__show()你將不能再通過student1.__show()打印出學生的名字和分數。值得注意的是私有方法必需含有self這個引數,且把它作為第一個引數。

 

在面向物件的程式設計中,通常情況下很少讓外部類直接訪問類內部的屬性和方法,而是向外部類提供一些按鈕,對其內部的成員進行訪問,以保證程式的安全性,這就是封裝。

 

@property的用法與神奇之處

 

在上述案例中使用者不能用student1.__score方式訪問學生分數,然而使用者也就知道了__score是個私有變數。我們有沒有一種方法讓使用者通過student1.score來訪問學生分數而繼續保持__score私有變數的屬性呢?這時我們就可以藉助python的@property裝飾器了。我們可以先定義一個方法score(), 然後利用@property把這個函式偽裝成屬性。見下面例子:

 

 

# 建立一個學生類
class Student:

    # 定義學生屬性,初始化方法
    # name和score屬於例項變數, 其中score屬於私有變數
    def __init__(self, name, score):
        self.name = name
        self.__score = score

    # 利用property裝飾器把函式偽裝成屬性
    @property
    def score(self):
        print("Name: {}. Score: {}".format(self.name, self.__score))

# 例項化,建立物件

student1 = Student("John", 100)

student1.score  # 列印 Name: John. Score: 100

 

注意: 一旦給函式加上一個裝飾器@property,呼叫函式的時候不用加括號就可以直接呼叫函數了 

 

類的繼承(Inheritance)

 

面向物件的程式設計帶來的最大好處之一就是程式碼的重用,實現這種重用的方法之一是通過繼承(Inheritance)。你可以先定義一個基類(Base class)或父類(Parent class),再按通過class 子類名(父類名)來建立子類(Child class)。這樣子類就可以從父類那裡獲得其已有的屬性與方法,這種現象叫做類的繼承。

 

我們再看另一個例子,老師和學生同屬學校成員,都有姓名和年齡的屬性,然而老師有工資這個專有屬性,學生有分數這個專有屬性。這時我們就可以定義1一個學校成員父類,2個子類。

 

# 建立父類學校成員SchoolMember
class SchoolMember:

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

    def tell(self):
        # 列印個人資訊
        print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")


# 建立子類老師 Teacher
class Teacher(SchoolMember):

    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age) # 利用父類進行初始化
        self.salary = salary

    # 方法重寫
    def tell(self):
        SchoolMember.tell(self)
        print('Salary: {}'.format(self.salary))


# 建立子類學生Student
class Student(SchoolMember):

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

    def tell(self):
        SchoolMember.tell(self)
        print('score: {}'.format(self.score))


teacher1 = Teacher("John", 44, "$60000")
student1 = Student("Mary", 12, 99)

teacher1.tell()  # 列印 Name:"John" Age:"44" Salary: $60000
student1.tell()  # Name:"Mary" Age:"12" score: 99

 

上述程式碼中,你注意到以下幾點了嗎?

 

  • 在建立子類的過程中,你需要手動呼叫父類的建構函式__init__來完成子類的構造。

  • 在子類中呼叫父類的方法時,需要加上父類的類名字首,且需要帶上self引數變數。比如SchoolMember.tell(self), 這個可以通過使用super關鍵詞簡化程式碼。

  • 如果子類呼叫了某個方法(如tell())或屬性,Python會先在子類中找,如果找到了會直接呼叫。如果找不到才會去父類找。這為方法重寫帶來了便利。

 

實際Python程式設計過程中,一個子類可以繼承多個父類,原理是一樣的。第一步總是要手動呼叫__init__建構函式。

 

super()關鍵字呼叫父類方法

 

在子類當中可以通過使用super關鍵字來直接呼叫父類的中相應的方法,簡化程式碼。在下面例子中,學生子類呼叫了父類的tell()方法。super().tell()等同於SchoolMember.tell(self)。當你使用Python super()關鍵字呼叫父類方法時時,注意去掉括號裡self這個引數。

 

# 建立子類學生Student
class Student(SchoolMember):

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

    def tell(self):
        super().tell() # 等同於 SchoolMember.tell(self)
        print('score: {}'.format(self.score))