使用 Python 學習面對對象的編程

使用 Python 類使你的代碼變得更加模塊化。html

在我上一篇文章中,我解釋瞭如何經過使用函數、建立模塊或者二者一塊兒來使 Python 代碼更加模塊化。函數對於避免重複屢次使用的代碼很是有用,而模塊能夠確保你在不一樣的項目中複用代碼。可是模塊化還有另外一種方法:類。java

若是你已經聽過面對對象編程object-oriented programming(OOP)這個術語,那麼你可能會對類的用途有一些概念。程序員傾向於將類視爲一個虛擬對象,有時與物理世界中的某些東西直接相關,有時則做爲某種編程概念的表現形式。不管哪一種表示,當你想要在程序中爲你或程序的其餘部分建立「對象」時,你均可以建立一個類來交互。python

沒有類的模板

假設你正在編寫一個以幻想世界爲背景的遊戲,而且你須要這個應用程序可以涌現出各類壞蛋來給玩家的生活帶來一些刺激。瞭解了不少關於函數的知識後,你可能會認爲這聽起來像是函數的一個教科書案例:須要常常重複的代碼,可是在調用時能夠考慮變量而只編寫一次。linux

下面一個純粹基於函數的敵人生成器實現的例子:git

#!/usr/bin/env python3

import random

def enemy(ancestry,gear):
    enemy=ancestry
    weapon=gear
    hp=random.randrange(0,20)
    ac=random.randrange(0,20)
    return [enemy,weapon,hp,ac]

def fight(tgt):
    print("You take a swing at the " + tgt[0] + ".")
    hit=random.randrange(0,20)
    if hit > tgt[3]:
        print("You hit the " + tgt[0] + " for " + str(hit) + " damage!")
        tgt[2] = tgt[2] - hit
    else:
        print("You missed.")


foe=enemy("troll","great axe")
print("You meet a " + foe[0] + " wielding a " + foe[1])
print("Type the a key and then RETURN to attack.")

while True:
    action=input()

    if action.lower() == "a":
        fight(foe)

    if foe[2] < 1:
        print("You killed your foe!")
    else:
        print("The " + foe[0] + " has " + str(foe[2]) + " HP remaining")
複製代碼

enemy 函數創造了一個具備多個屬性的敵人,例如譜系、武器、生命值和防護等級。它返回每一個屬性的列表,表示敵人所有特徵。程序員

從某種意義上說,這段代碼建立了一個對象,即便它尚未使用類。程序員將這個 enemy 稱爲對象,由於該函數的結果(本例中是一個包含字符串和整數的列表)表示遊戲中一個單獨但複雜的東西。也就是說,列表中字符串和整數不是任意的:它們一塊兒描述了一個虛擬對象。github

在編寫描述符集合時,你可使用變量,以便隨時使用它們來生成敵人。這有點像模板。shell

在示例代碼中,當須要對象的屬性時,會檢索相應的列表項。例如,要獲取敵人的譜系,代碼會查詢 foe[0],對於生命值,會查詢 foe[2],以此類推。編程

這種方法沒有什麼不妥,代碼按預期運行。你能夠添加更多不一樣類型的敵人,建立一個敵人類型列表,並在敵人建立期間從列表中隨機選擇,等等,它工做得很好。實際上,Lua 很是有效地利用這個原理來近似了一個面對對象模型。ruby

然而,有時候對象不只僅是屬性列表。

使用對象

在 Python 中,一切都是對象。你在 Python 中建立的任何東西都是某個預約義模板的實例。甚至基本的字符串和整數都是 Python type 類的衍生物。你能夠在這個交互式 Python shell 中見證:

>>> foo=3
>>> type(foo)
<class 'int'>
>>> foo="bar"
>>> type(foo)
<class 'str'>
複製代碼

當一個對象由一個類定義時,它不只僅是一個屬性的集合,Python 類具備各自的函數。從邏輯上講,這很方便,由於只涉及某個對象類的操做包含在該對象的類中。

在示例代碼中,fight 的代碼是主應用程序的功能。這對於一個簡單的遊戲來講是可行的,但對於一個複雜的遊戲來講,世界中不只僅有玩家和敵人,還可能有城鎮居民、牲畜、建築物、森林等等,它們都不須要使用戰鬥功能。將戰鬥代碼放在敵人的類中意味着你的代碼更有條理,在一個複雜的應用程序中,這是一個重要的優點。

此外,每一個類都有特權訪問本身的本地變量。例如,敵人的生命值,除了某些功能以外,是不會改變的數據。遊戲中的隨機蝴蝶不該該意外地將敵人的生命值下降到 0。理想狀況下,即便沒有類,也不會發生這種狀況。可是在具備大量活動部件的複雜應用程序中,確保不須要相互交互的部件永遠不會發生這種狀況,這是一個很是有用的技巧。

Python 類也受垃圾收集的影響。當再也不使用類的實例時,它將被移出內存。你可能永遠不知道這種狀況會何時發生,可是你每每知道何時它不會發生,由於你的應用程序佔用了更多的內存,並且運行速度比較慢。將數據集隔離到類中能夠幫助 Python 跟蹤哪些數據正在使用,哪些不在須要了。

優雅的 Python

下面是一個一樣簡單的戰鬥遊戲,使用了 Enemy 類:

#!/usr/bin/env python3

import random

class Enemy():
    def __init__(self,ancestry,gear):
        self.enemy=ancestry
        self.weapon=gear
        self.hp=random.randrange(10,20)
        self.ac=random.randrange(12,20)
        self.alive=True

    def fight(self,tgt):
        print("You take a swing at the " + self.enemy + ".")
        hit=random.randrange(0,20)

        if self.alive and hit > self.ac:
            print("You hit the " + self.enemy + " for " + str(hit) + " damage!")
            self.hp = self.hp - hit
            print("The " + self.enemy + " has " + str(self.hp) + " HP remaining")
        else:
            print("You missed.")

        if self.hp < 1:
            self.alive=False

# 遊戲開始
foe=Enemy("troll","great axe")
print("You meet a " + foe.enemy + " wielding a " + foe.weapon)

# 主函數循環
while True:
   
    print("Type the a key and then RETURN to attack.")
        
    action=input()

    if action.lower() == "a":
        foe.fight(foe)
                
    if foe.alive == False:
        print("You have won...this time.")
        exit()
複製代碼

這個版本的遊戲將敵人做爲一個包含相同屬性(譜系、武器、生命值和防護)的對象來處理,並添加一個新的屬性來衡量敵人時候已被擊敗,以及一個戰鬥功能。

類的第一個函數是一個特殊的函數,在 Python 中稱爲 init 或初始化的函數。這相似於其餘語言中的構造器,它建立了類的一個實例,你能夠經過它的屬性和調用類時使用的任何變量來識別它(示例代碼中的 foe)。

Self 和類實例

類的函數接受一種你在類以外看不到的新形式的輸入:self。若是不包含 self,那麼當你調用類函數時,Python 沒法知道要使用的類的哪一個實例。這就像在一間充滿獸人的房間裏說:「我要和獸人戰鬥」,向一個獸人發起。沒有人知道你指的是誰,全部獸人就都上來了。

Image of an Orc, CC-BY-SA by Buch on opengameart.org
CC-BY-SA by Buch on opengameart.org

CC-BY-SA by Buch on opengameart.org

類中建立的每一個屬性都以 self 符號做爲前綴,該符號將變量標識爲類的屬性。一旦派生出類的實例,就用表示該實例的變量替換掉 self 前綴。使用這個技巧,你能夠在一間盡是獸人的房間裏說:「我要和譜系是 orc 的獸人戰鬥」,這樣來挑戰一個獸人。當 orc 聽到 「gorblar.orc」 時,它就知道你指的是誰(他本身),因此你獲得是一場公平的戰鬥而不是鬥毆。在 Python 中:

gorblar=Enemy("orc","sword")
print("The " + gorblar.enemy + " has " + str(gorblar.hp) + " remaining.")
複製代碼

經過檢索類屬性(gorblar.enemygorblar.hp 或你須要的任何對象的任何值)而不是查詢 foe[0](在函數示例中)或 gorblar[0] 來尋找敵人。

本地變量

若是類中的變量沒有以 self 關鍵字做爲前綴,那麼它就是一個局部變量,就像在函數中同樣。例如,不管你作什麼,你都沒法訪問 Enemy.fight 類以外的 hit 變量:

>>> print(foe.hit)
Traceback (most recent call last):
  File "./enclass.py", line 38, in <module>
    print(foe.hit)
AttributeError: 'Enemy' object has no attribute 'hit'

>>> print(foe.fight.hit)
Traceback (most recent call last):
  File "./enclass.py", line 38, in <module>
    print(foe.fight.hit)
AttributeError: 'function' object has no attribute 'hit'
複製代碼

hit 變量包含在 Enemy 類中,而且只能「存活」到在戰鬥中發揮做用。

更模塊化

本例使用與主應用程序相同的文本文檔中的類。在一個複雜的遊戲中,咱們更容易將每一個類看做是本身獨立的應用程序。當多個開發人員處理同一個應用程序時,你會看到這一點:一個開發人員負責一個類,另外一個開發人員負責主程序,只要他們彼此溝通這個類必須具備什麼屬性,就能夠並行地開發這兩個代碼塊。

要使這個示例遊戲模塊化,能夠把它拆分爲兩個文件:一個用於主應用程序,另外一個用於類。若是它是一個更復雜的應用程序,你可能每一個類都有一個文件,或每一個邏輯類組有一個文件(例如,用於建築物的文件,用於天然環境的文件,用於敵人或 NPC 的文件等)。

將只包含 Enemy 類的一個文件保存爲 enemy.py,將另外一個包含其餘內容的文件保存爲 main.py

如下是 enemy.py

import random

class Enemy():
    def __init__(self,ancestry,gear):
        self.enemy=ancestry
        self.weapon=gear
        self.hp=random.randrange(10,20)
        self.stg=random.randrange(0,20)
        self.ac=random.randrange(0,20)
        self.alive=True

    def fight(self,tgt):
        print("You take a swing at the " + self.enemy + ".")
        hit=random.randrange(0,20)

        if self.alive and hit > self.ac:
            print("You hit the " + self.enemy + " for " + str(hit) + " damage!")
            self.hp = self.hp - hit
            print("The " + self.enemy + " has " + str(self.hp) + " HP remaining")
        else:
            print("You missed.")

        if self.hp < 1:
            self.alive=False
複製代碼

如下是 main.py

#!/usr/bin/env python3

import enemy as en

# game start
foe=en.Enemy("troll","great axe")
print("You meet a " + foe.enemy + " wielding a " + foe.weapon)

# main loop
while True:
   
    print("Type the a key and then RETURN to attack.")

    action=input()

    if action.lower() == "a":
        foe.fight(foe)

    if foe.alive == False:
        print("You have won...this time.")
        exit()
複製代碼

導入模塊 enemy.py 使用了一條特別的語句,引用類文件名稱而不用帶有 .py 擴展名,後跟你選擇的命名空間指示符(例如,import enemy as en)。這個指示符是在你調用類時在代碼中使用的。你須要在導入時添加指示符,例如 en.Enemy,而不是隻使用 Enemy()

全部這些文件名都是任意的,儘管在原則上不要使用罕見的名稱。將應用程序的中心命名爲 main.py 是一個常見約定,和一個充滿類的文件一般以小寫形式命名,其中的類都以大寫字母開頭。是否遵循這些約定不會影響應用程序的運行方式,但它確實使經驗豐富的 Python 程序員更容易快速理解應用程序的工做方式。

在如何構建代碼方面有一些靈活性。例如,使用該示例代碼,兩個文件必須位於同一目錄中。若是你只想將類打包爲模塊,那麼必須建立一個名爲 mybad 的目錄,並將你的類移入其中。在 main.py 中,你的 import 語句稍有變化:

from mybad import enemy as en
複製代碼

兩種方法都會產生相同的結果,但若是你建立的類足夠通用,你認爲其餘開發人員能夠在他們的項目中使用它們,那麼後者更好。

不管你選擇哪一種方式,均可以啓動遊戲的模塊化版本:

$ python3 ./main.py 
You meet a troll wielding a great axe
Type the a key and then RETURN to attack.
a
You take a swing at the troll.
You missed.
Type the a key and then RETURN to attack.
a
You take a swing at the troll.
You hit the troll for 8 damage!
The troll has 4 HP remaining
Type the a key and then RETURN to attack.
a
You take a swing at the troll.
You hit the troll for 11 damage!
The troll has -7 HP remaining
You have won...this time.
複製代碼

遊戲啓動了,它如今更加模塊化了。如今你知道了面對對象的應用程序意味着什麼,但最重要的是,當你向獸人發起決鬥的時候,你知道是哪個。


via: opensource.com/article/19/…

做者:Seth Kenlon 選題:lujun9972 譯者:MjSeven 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章
相關標籤/搜索