Python編程思想(23):類與對象

李寧老師已經在「極客起源」 微信公衆號推出《Python編程思想》電子書,囊括了Python的核心技術,以及Python的主要函數庫的使用方法。讀者可以在「極客起源」 公衆號中輸入 160442 開始學習。

《Python編程思想》總目錄

《Python編程思想》專欄

類是面向對象的核心組成部分,類是一種自定義類型,並且可以將類作爲變量的類型、以及函數參數和返回值的類型。

1. 如何定義類

在面向對象的程序設計過程中有兩個重要概念:類(class)和對象( object),對象也被稱爲實例(instance),其中類是擁有類似特性的對象的抽象,可以把類理解成某種概念,對象纔是一個具體存在的實體。

從這個意義上看,日常所說的具體的人就是對象,而“人類”相當於類。

Python定義類的簡單語法如下:

class類名:
    執行語句...
    零個到多個類變量...
    零個到多個方法...

類名只要是一個合法的標識符即可,但這僅僅滿足的是 Python的語法要求。如果從程序的可讀性方面來看,Python的類名最好是由一個或多個有意義的單詞連綴而成的,每個單詞首字母大寫(大駝峯命名法),其他字母全部小寫,單詞與單詞之間不要使用任何分隔符。

從上面定義來看,Python的類定義有點像函數定義,都是以冒號(:)作爲類體的開始,以縮進的部分作爲類體的。區別只是函數定義使用def關鍵字,而類定義則使用 class關鍵字。

Python的類定義由類頭(指 class關鍵字和類名部分)和統一縮進的類體構成,在類體中最主要的兩類成員就是變量和方法。如果不爲類定義任何變量和方法,那麼這個類就相當於一個空類。如果空類不需要其他可執行語句,則可使用pass語句作爲佔位符。例如,如下類定義是允許的。

class MyClass
    pass

通常來說,空類沒有太大的實際意義。類中各成員之間的定義順序沒有任何影響。各成員之間可以相互調用。

成員變量屬於類本身,用於定義該類本身所包含的狀態數據。而實例變量則屬於該類的對象,用於定義對象所包含的狀態數據。方法則用於定義該類的對象的行爲或功能實現。

Python是一門動態語言,因此它的類所包含的類變量可以動態增加或刪除。程序在類體中爲新變量賦值就是增加類變量,程序也可在任何地方爲已有的類增加變量。程序可通過del語句刪除已有類的類變量。

與類變量類似,Python對象的實例變量也可以動態增加或刪除。只要對新實例變量賦值就是增加實例變量,因此程序可以在任何地方爲已有的對象增加實例變量。程序可通過del語句刪除已有對象的實例變量。

在類中定義的方法默認是實例方法,定義實例方法的方式與定義函數的方式基本相同,只是實例方法的第一個參數會被綁定到方法的調用者(該類的實例)上。因此實例方法至少應該定義一個參數,該參數通常會被命名爲self。

注意:

實例方法的笫1個參數並不一定要叫self,其實完全可以叫任何參數名,只是約定俗成地把該參數命名爲self,這樣具有最好的可讀性。

在實例方法中有一個特別的方法:init。這個方法被稱爲構造方法。構造方法用於構造該類的對象,Python通過調用構造方法返回該類的對象(創建Python對象時不需要使用new)。

構造方法是一個類創建對象是第1個要執行的方法,因此 ,Python還提供了一個功能。如果開發者沒有爲該類定義任何構造方法,那麼 Python會自動爲該類定義一個只包含一個self參數的構造方法。

下面程序將定義一個 Product類。

示例代碼:Product.py

class Product :
    '我們編寫的第1個Python類'
    # 下面定義了一個類變量
    type = 'Mobile'
    def __init__(self, name = 'iPhone12 Pro', price=8000):
        # 下面爲Product對象增加2個實例變量
        self.name = name
        self.price = price
    # 下面定義了一個buy方法
    def buy(self, price):
        print(f"您已經花了{price}元購買了一部{self.name}手機")

上面的Product類代碼定義了一個構造方法,該構造方法只是方法名比較特殊,該方法的第1個參數同樣是self,被綁定到構造方法初始化的對象。與函數類似的是, Python也允許爲類定義說明文檔,該文檔同樣被放在類聲明之後、類體之前,如上面程序中第1行的字符串所示。

在定義類之後,接下來即可使用該類了。 Python的類大致有如下作用。

  • 定義變量

  • 創建對象

  • 派生子類

下面先介紹如何創建類的實例(對象),以及如何使用對象。

2. 對象的創建和使用

創建對象的根本途徑是構造方法,調用某個類的構造方法即可創建這個類的對象, Python無須使用new調用構造方法。如下代碼示範了調用 Product類的構造方法(Product類的代碼見前面的內容)。

# 調用Product類的構造方法,返回一個product
# 將該Product對象賦值給product變量
product = Product()

在創建對象之後,接下來即可使用該對象了。 Python的對象大致有如下作用。

  • 操作對象的實例變量(包括訪問實例變量的值、添加實例變量、刪除實例變量);

  • 調用對象的方法;

對象訪問方法或變量的語法如下:

對象.變量|方法(參數)

在這種方式中,對象是主調者,用於訪問該對象的變量或方法。下面代碼通過Product對象來調用 Product的實例和方法(Product類的源代碼見前面的內容)。

# 輸出product的name、price實例變量
print(product.name, product.price)
# 訪問product的name實例變量,直接爲該實例變量賦值
product.name = 'iPhone12 pro max'
product.price = 12000
# 調用product的buy()方法,聲明buy()方法時定義了2個形參
# 但第1個形參(self)是自動綁定的,因此調用該方法只需爲第2個形參指定一個值
product.buy(12000)
# 再次輸出p的name、age實例變量
print(product.name, product.price)

上面程序開始訪問了product對象的name和price兩個實例變量。這兩個變量是何時定義的呢?留意在Product的構造方法中有如下2行代碼

self. name= name
self. price = price

這兩行代碼用傳入的name和price參數(這兩個參數都有默認值)對self的name、price變量賦值。由於 Python的第1個self參數是自動綁定的(在構造方法中自動綁定到該構造方法初始化的對象),而這2行代碼就是對self的name、price兩個變量賦值,也就是對該構造方法初始化的對象(product對象)的name和age變量賦值,即爲product對象增加了name和price兩個實例變量。

上面代碼中通過Product對象調用了buy方法,在調用該方法時必須爲方法的形參賦值。但buy方法的第1個形參self是自動綁定的,它會被綁定到方法的調用者(product)上,因此程序只需要爲buy方法傳入1個浮點數作爲參數值,這個浮點數將被傳給price參數。

大部分時候,定義一個類就是爲了重複創建該類的對象,同一個類的多個對象具有相同的特徵,而類則定義了多個對象的共同特徵。從某個角度來看,類定義的是多個對象的特徵,因此類不是個具體存在的實體,對象纔是一個具體存在的實體。

3. 動態增加實例變量

由於 Python是動態語言,因此程序完全可以爲product對象動態增加實例變量。只要爲它的新變量賦值即可,動態刪除實例變量需要使用del語句。

爲Product對象增加一個size實例變量

# 爲product對象增加一個size實例變量
product.size = '6.7英寸'
print(product.size)
# 刪除product對象的name實例變量
del product.name
# 再次訪問pproduct的name實例變量
# print(product.name)  # AttributeError

上面程序先爲product對象動態增加了一個size實例變量。只要對roduct對象的 price實例變量賦值,就是新增一個實例變量。

接下來程序中調用del刪除了product對象的name實例變量,當程序再次訪問print( p name)時就會導致 Attribute error錯誤,並提示:" Product' object has no attribute name

Python是動態語言,當然也允許爲對象動態增加方法。比如上面程序中在定義 Product類時只定義了一個buy方法,但程序完全可以爲product對象動態增加方法。

但需要說明的是,爲product對象動態增加的方法,Python不會自動將調用者自動綁定到第一個參數(即使將第1個參數命名爲self也沒用)。例如如下代碼。

# 先定義一個函數
def info(self):
    print("---info函數---", self)
# 使用info對Product的info方法賦值(動態綁定方法)
product.info = info
# Python不會自動將調用者綁定到第一個參數,
# 因此程序需要手動將調用者綁定爲第一個參數
product.info(product)

# 使用lambda表達式爲product對象的bar方法賦值(動態綁定方法)
product.info = lambda self: print('--lambda表達式--', self)
product.info(product)

上面兩行粗體字代碼分別使用函數、 lambda表達式爲p對象動態增加了方法,但對於動態增加的方法,Python不會自動將方法調用者綁定到它們的第1個參數,因此程序必須手動爲第1個參數傳入參數值,如上面程序中所示。如果希望動態增加的方法也能自動綁定到第1個參數,則可藉助於 types模塊下的 Method Type進行包裝。例如如下代碼。

def intro_func(self, price):
    print(f"您已經花了{price}元購買了一部手機")
# 導入MethodType
from types import MethodType
# 使用MethodType對intro_func進行包裝,將該函數的第一個參數綁定爲p
product.intro = MethodType(intro_func, product)
# 第一個參數已經綁定了,無需傳入
product.intro("生活在別處")

正如從上面代碼所看到的,通過 Method Type包裝 intro_func函數之後(包裝時指定了將該函數的第1個參數綁定爲product),爲product對象動態增加的 info方法的第1個參數已經綁定,因此,程序通過product調用 intro方法時無須傳入第一個參數的值。只需要像定義類時已經定義了 info方法一樣。

-----------------支持作者請轉發本文,也可以加李寧老師微信:unitymarvel,或掃描下面二維碼加微信--------

歡迎關注  極客起源  微信公衆號,更多精彩視頻和文章等着你哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章