學習軌道
/
Python
Python
/
練習
/
Ellen 的外星人遊戲
Ellen 的外星人遊戲

Ellen 的外星人遊戲

學習練習

簡介

Python 的物件導向程式設計

如果你過去是用函式、宣告式或命令式風格寫程式,那麼把重心轉移到物件導向程式設計(OOP)時,可能會覺得有點陌生。 物件導向的做法,會要求程式設計者把問題想成一個或多個會彼此互動、保存狀態,並對資料採取行動的 objects。 物件可以代表真實世界的實體(例如車子或貓咪),也可以是更抽象的概念(例如整數、交通工具或哺乳類)。 每個物件都會在電腦記憶體中成為一個獨特的實例,代表整體模型中的某個部分。

類別

Classes 是新物件型別的定義,新的物件 instances 就是由它們建立出來的。 類別通常會把資料與操作這些資料的程式碼或函式綁在一起。 從這個角度來看,類別就像是_藍圖_,或是一組指示,讓我們能據以建立並使用許多相似型別的物件。 一個複雜的程式可以有許多類別,每個類別都能建立出各種不同風貌的物件。 從類別建立物件的過程稱為 instantiation(也就是建立該類別的實例)。

在 Python 中,類別的定義很直接:

class MyClass:
    # Class body goes here

類別屬性

類別欄位(又稱為 properties、attributes、data members 或 variables)可以加進類別的主體中:

class MyClass:
    number = 5
    string = "Hello!"

只要呼叫 MyClass(就像呼叫函式一樣),就能建立它的實例(物件),並把它綁定到某個名稱上:

>>> new_object = MyClass()

# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>

Class attributes 會由從類別建立的所有物件(或實例)共享,可以透過dot notation存取,也就是把 . 放在物件名稱之後、屬性名稱之前:

>>> new_object = MyClass()

# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5

# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'

# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()

>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>

# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True

類別屬性定義在類別本身的主體中,排在所有其他方法之前。 它們屬於類別,因此可以在類別的各個實例之間共享。 正因為這些屬性是被共享的,所以可以_直接_從類別存取與操作它們的值。 改變類別屬性的值,就會改變_所有從該類別實例化出來的物件_的值:

>>> obj_one = MyClass()
>>> obj_two = MyClass()

# Accessing a class attribute from an object.
>>> obj_two.number
5

# Accessing the class attribute from the class itself.
>>> MyClass.number
5

# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27

# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27

>>> obj_two.number
27

讓一群物件隨時都保持同步的資料,其實沒什麼特別用處。 幸運的是,從類別建立的物件可以用自己的 instance attributes(或實例屬性、變數或欄位)來客製化。 顧名思義,實例屬性是每個物件各自獨有的,可以獨立修改。

用 __init__() 客製化物件實例化

特殊的「dunder 方法」(是 "double underscore method" 的簡稱)__init__() 用來客製化類別實例,也可以為物件初始化實例屬性或特性。 由於 __init__() 負責初始化實例屬性,它也常被稱為 class constructor 或 initializer。 __init__() 會接受一個必要的參數,叫做 self,代表剛初始化或建立的物件。 接著,實例屬性或特性的資料就可以當成 __init__() 的引數傳入,接在 self 參數後面。

下面這個 MyClass 現在有兩個實例屬性,叫做 location_x 和 location_y。 如你所見,這兩個屬性被指定成傳入 __init__() 的 location(一個元組)引數的第一個和第二個索引值。 當你實例化這個類別、建立物件時,類別實例的 location_x 和 location_y 屬性就會被初始化:

class MyClass:
    # These are class attributes, variables, or fields.
    number = 5
    string = "Hello!"

    # This is the class "constructor", with a "location" parameter that is a tuple.
    def __init__(self, location):

        # This is an instance or object property, attribute, or variable.
        # Note that we are unpacking the tuple argument into two separate instance variables.
        self.location_x = location[0]
        self.location_y = location[1]

# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))

# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))

# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1

>>> new_object_two.location_x
-8

注意,上面初始化 MyClass 時,你只需要傳入一個引數就好,當類別被呼叫時,Python 會自動幫你傳入 self。

方法

method 是一種 function,它會綁定到類別本身(稱為類別方法,之後的練習會討論)或類別的_實例_(物件)。 要對物件(實例)進行操作的方法,定義時必須把 self 當成第一個參數。 接著,你可以像定義「一般」或非綁定函式那樣,定義其餘的參數:

class MyClass:
    number = 5
    string = "Hello!"

    # Class constructor.
    def __init__(self, location):
        # Instance properties
        self.location_x = location[0]
        self.location_y = location[1]

    # Instance method. Note "self" as first parameter.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

和存取屬性一樣,呼叫方法只要把 . 放在物件名稱之後、方法名稱之前就行了。 被呼叫的方法不需要把物件本身當成第一個參數傳進去,Python 會自動幫你填入 self:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)

# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)

實例方法內也能存取類別屬性,方式和在類別外部存取時一樣:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

    # Alter class variable number for all instances from within an instance.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1


>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5

>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6

用 Pass 佔位或預留實作

在之前的概念練習和練習題骨架中,你應該看過在函式主體裡用 pass 關鍵字來代替實際的程式碼。 pass 關鍵字是語法上合法的佔位符,可以避免 Python 因為空的函式或類別定義而拋出語法錯誤。 基本上,這就像是在對 Python 翻譯員說:「別擔心!我_之後_會把程式碼放進來,只是還沒動手而已。」

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return  self.location_x, self.location_y

    # Alter class variable number for all instances
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1

    # This will compile and run without error, but has no current functionality.
    def pending_functionality(self):
       # Stubbing or placholding the body of this method.
       pass

說明

Ellen 正在做一款遊戲,玩家必須在其中對抗外星人。 她剛學會物件導向程式設計(OOP),很期待能運用 classes 為她的程式帶來的好處。

很高興的是,你答應要幫忙,於是她把程式設計這些玩家必須對抗的外星人的任務交給了你。

1. 建立 Alien 類別

定義 Alien 類別,並替它加上一個建構式,接受兩個參數<x_coordinate>和<y_coordinate>,將它們存入 x_coordinate 和 y_coordinate 這兩個實例變數。 每個外星人一開始的生命值也都是 3,所以 health 變數也要一併初始化。

>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3

現在,每個外星人都應該能在內部追蹤自己的位置和生命值。

2. hit方法

Ellen 希望 Alien class 有一個 hit 方法,呼叫時會把外星人物件的生命值減少 1。 這樣一來,她只要呼叫<alien>.hit()就好,不必手動更改外星人的生命值。 hit()要讓生命值剛好降到零還是降到零以下,由你決定。

>>> alien = Alien(0, 0)

# Initialized health value.
>>> alien.health
3

# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2

3. is_alive方法

你發現,生命值要是持續下降,總有一天會降到 0(甚至更低!)。 最好加上一個 is_alive 方法,讓 Ellen 可以快速呼叫,確認外星人是不是……呃……還活著。😉 <alien>.is_alive()應該要回傳布林值。

>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False

4. teleport方法

在 Ellen 的遊戲裡,外星人有瞬間移動的能力! 你需要寫一個 teleport 方法,接受新的 x_coordinate 和 y_coordinate 值,並據此改變外星人的座標。

>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4

5. collision_detection方法

顯然,如果外星人會被東西擊中,牠們就得能夠偵測到這種碰撞發生。 不過,碰撞偵測演算法可能很棘手,而你還不知道該怎麼實作。 Ellen 說她之後會處理,但她還是希望 collision_detection 方法先出現在類別裡,提醒之後要把這個功能做出來。 它需要接受某種變數(大概是另一個物件),但你知道的真的就只有這些。 你要確保把這個方法定義放進類別後,呼叫時不會產生任何錯誤:

>>> alien.collision_detection(other_object)
>>>

6. 外星人計數器

Ellen 回來找你,又有新的需求。 她想追蹤整場遊戲期間總共建立了多少個外星人。 她說這跟計分系統有關。

例如:

>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2

7. 建立外星人陣列

Ellen 很喜歡你目前做出來的成果,但她還想再請你幫個忙。 她想要一個獨立(在 Alien() 類別之外)的函式,它會接收一串位置(以 tuples 表示),並建立一個由 Alien() 物件組成的 list。

例如:

>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
...     print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Python Exercism

準備好開始 Ellen 的外星人遊戲 了嗎?

註冊 Exercism,透過 17 個概念146 個練習 和真人引導來學習並精通 Python,全部免費。