文章閱讀:Python 函式中的類型提示

如何在 Python 函式中標示類型?

詳情可參閱上方連結。

Duck Typing

作為一種動態程式語言,Python 採用「鴨子型別」的方式進行物件識別:

「當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。」

例如以下的程式:

class Duck:
    def quack(self):
        print("這鴨子在呱呱叫")
    def feathers(self):
        print("這鴨子擁有白色與灰色羽毛")
    def getMyFeathers(self):
        return ["白色羽毛", "灰色羽毛"]

class Person:
    def quack(self):
        print("這人正在模仿鴨子")
    def feathers(self): 
        print("這人在地上拿起1根羽毛然後給其他人看")
    def getMyFeathers(self):
        return "地上的羽毛"

def in_the_forest(duck):
    duck.quack()
    duck.feathers()

#Start the game.
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)

以上程式碼顯示,不管你是 Duck 類型還是 Person 類型,一樣可以進入 in_the_forest 函式。

很明顯地,我們的 john 可以玩到直到「穿幫」為止。

也呼應了 Python 的設計風格:

We're all consenting adults here.

只要知道自己在做什麼,Python 可以讓寫作方式不會太過拘謹。

PEP 484 簡讀

不過 Python 中還是可以「提醒」工程師,function 中究竟該輸入什麼、會得到什麼。

這裡使用「提醒」這個字,代表著仍然能不按照規定輸入輸出(因為我們是動態語言),當然也會抱著被拆穿的風險。

使用 PEP 484 的規則設計,最大的好處是可以不須使用註解來標明,畢竟同一色系常常會打錯字或是會錯意。

而且 Python 直譯器以及絕大多數的 IDE 都會幫你檢查是否為正確的類型。

需要注意的是,typing 模組是在 Python 3.5 加入。

輸入

先來展示基本的寫法,提醒 john 不要進入 in_the_forest 函式:

def in_the_forest(duck: Duck):
    duck.quack()
    duck.feathers()
    whiteFeather, grayFeather = duck.getMyFeathers()

這裡使用冒號 : 後加上類型物件可以標示這個輸入的類型。

如果在其他檔案,懶得找到鴨子,也可以用字串表示有種生物叫鴨子。

def in_the_forest(duck: "Duck"):

預設項目的用法也是以此類推,比如沒有 donald,自己找隻鴨子。

def in_the_forest(duck: Duck =Duck()):

多個輸入也雷同,使用逗號隔開即可。若是很多項,則是建議換行。

多個項目中可以選擇特定的標示,這些都不是硬性規定。

下面的範例留了一個安全的位子給 john。

def in_the_forest(
    duck1: Duck =Duck(),
    duck2: Duck =Duck(),
    duck3: Duck =Duck(),
    person: Person =Person()
):
    Creatures = [duck1, duck2, duck3, person]
    for i, duck in enumerate(Creatures):
        duck.quack()
        duck.feathers()
        if i!=3:
            whiteFeather, grayFeather = duck.getMyFeathers()

in_the_forest(person=john)

類型中的方法 (method) 也是一樣,不過因為第一個輸入 self 本來就是該類型,通常都不會標示。

這樣是不是清楚很多呢?

輸出

我們注意到 donald 和 john 在 getMyFeathers 函式中拿出的物件不一樣,為了瞭解類型的函式究竟會得到什麼,可以使用輸出表示的方法。

輸出表示是一個箭號形狀擺在 function 尾端的冒號 : 之前:

class Person:
    def getMyFeathers(self) -> str:
        return "地上的羽毛"

當然只會做事的函式是回傳 None,可以視情況決定要不要寫:

class Person:
    def quack(self) -> None:
        print("這人正在模仿鴨子")

若為多個,也是使用逗號分隔,這裡就不示範了。

容器物件

若是輸入或輸出的物件為容器 (container),可以使用檢索符號 [ ] 中括弧來標示。

from typing import List

class Duck:
    def getMyFeathers(self) -> List[str, str]:
        return ["白色羽毛", "灰色羽毛"]

從 typing 模組匯入的類型名稱字首為大寫,注意不要與一般類型混淆了。

可呼叫物件

可呼叫 (callable) 的 function 類型和常用的 lambda 如下表示:

from typing import Callable

def async_query(
    on_success: Callable[[int], None],
    on_error: Callable[[int, Exception], None]
):

第一項為「輸入類型」,第二項為「輸出類型」。

若是懶得管第一項,可以使用 Ellipsis 表示。

一個簡單的 lambda 物件如下表示:

lambda x, y: str(x+y)

Callable[[int, int], str]
Callable[..., str]

泛型物件

若有多重類型的物件可以適用,便可從 typing 模組匯入泛型物件。

例如無序集合:

from typing import Mapping, Set

def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]):

或是有序集合:

from typing import Sequence, TypeVar

T = TypeVar('T')

def first(l: Sequence[T]) -> T:
    return l[0]

或是任何字串:

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y

上面的範例中,透過 TypeVar 函式可以將你的自訂類型正規化,讓直譯器不會出錯,是一個比字串更好的表達方式。

還有更多使用方式,這裡就不細數了,更多內容可以參閱官方的說明。


Comments

comments powered by Disqus