Python的靜態類型之旅

本文系掘金Python月專題文章,轉載請註明來源java

江湖有句話:「動態類型一時爽,代碼重構火葬場」被廣爲流傳,這話通常出自靜態語言擁護者口中,聽起來有點聳人聽聞,但也沒有想象中的那麼嚴重,Python在大型項目的應用太多了,Instagram就是最好的例子。python

Python做爲動態語言,在定義變量、函數返回值、方法參數都不須要指定數據類型,某種程度上讓代碼變得無比簡潔、靈活,拋開程序運行效率,但動態語言也存在不足,例如:程序員

一、IDE的智能提示比較雞肋,由於沒法判斷變量類型,因此IDE就不知道變量有那些屬性和方法,沒有智能提示對老鳥來講是很是痛苦的,舉個簡單例子,印象中str有個startwith方法,但正確的寫法是 startswith,有個s,我不得不去查個文檔。(不過我的建議初學者仍是老老實實用編輯器手敲代碼,以此加深記憶)express

二、編譯過程當中,只能發現語法錯誤,類型不匹配的問題只有等程序真正運行了才知道。雖然能夠經過單元測試來規避,可是若是在代碼編寫的過程當中有IDE給咱們指出來不是更好嗎。app

三、接口調用全靠文檔註釋說明,調用某個方法或函數,返回值和參數類型說明只能根據文檔來肯定。雖然咱們能夠要求程序員使用docstring或者註釋來講明函數的參數類型以及返回值類型,有個問題是即便一開始你規規矩矩的寫了docstring,可是代碼更新以後,你的docstring可能就沒有同步更新。編輯器

這些問題在大型項目,特別是多人合做的項目上顯得尤其突出。代碼規範、Code Review 就變得更重要了。正由於這些問題,社區對靜態類型特性的引進呼聲愈來愈強烈,因此在 Python3.5,也就是 PEP484 中有了類型提示(Type Hints)。定義函數時,能夠指定函數的返回值類型、參數的類型。函數

之前寫一個函數是這樣的:工具

def greeting(name):
    return "Hello" + name

>>> greeting("bob")
'Hellobob'
>>> greeting(1)
TypeError: must be str, not int
複製代碼

當你不去看文檔或者源代碼的時候,你根本不知道你能夠傳遞什麼類型的值進去,而當你傳入整數1時,只有等到程序運行的時候才能發現錯誤,若是有一種數據類型檢查工具在程序啓動前事先查一遍就能夠避免程序出錯。單元測試

在Python3.5中,用 Type Hint 的寫法是這樣的:開發工具

def greeting(name: str) -> str:
    return 'Hello ' + name
複製代碼

上面就是靜態類型的寫法,多了 「: str」與 「-> str」,前者用來講明 name 的類型,後者指函數返回值的類型。這樣一來,IDE像PyCharm這樣的工具也能即時的發現代碼的問題。

固然,除了IDE以外,咱們還有更強大的靜態類型檢查工具,叫 mypy,這個工具也是由Python之父GUido親自操刀實現的靜態類型檢查工具。

pip install mypy

$ mypy test.py
test.py:4: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"
複製代碼

有了類型提示,Python在代碼調用、重構、甚至是靜態分析等方面有了更好的效果,不但減輕了開發時自行進行型態檢查的負擔,更重要的是,因爲有了型態上的提示,讓過去Python整合開發工具上作很差的各類智能提示、重構等功能有了統一的參考標準。

某種意義上類型提示只是一種輔助功能,雖然咱們加了數據類型提示,可是對於Python解釋器來講,它會直接忽略掉類型提示信息,即時類型有誤也不會阻止程序的運行。

而對於變量的類型,在PEP484中能夠經過類型註釋來講明,就是以註釋的方式來講明變量的類型,例如:

from typing import List

x = []                # type: List[Employee]
y = [1, 2]            # type: List[int]
y.append("a")
複製代碼

上面類型註釋表示x必須是 Employee 對象組成的列表,y必須是 int 構成的列表,整數列表y追加一個字符串後,咱們用 mypy 來檢查代碼有什麼問題:

mypy test.py
xx.py:3: error: Name 'Employee' is not defined
xx.py:5: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int"
複製代碼

在 Python3.6,也就是 PEP526 的提案中,針對變量註解作了進一步優化,將類型的聲明做爲了語法的一部分,這樣比起註釋可讀性更強,例如:

my_var: int  # 聲明爲整數類型的變量
my_var = 5  # 經過類型檢查
other_var: int = 'a'  # 給整數類型變量賦值字符串,檢查器會報錯,可是解釋器運行是不會有任何問題
print(other_var)
複製代碼
mypy xx.py  # 運行類型檢查器
xx.py:3: error: Incompatible types in assignment (expression has type "str", variable has type "int")

python test.py # 運行解釋器
a
複製代碼

類型提示雖然給了IDE智能提示、重構帶來了很大的便利,而偏偏由於這些類型信息的聲明,使得動態語言變得臃腫起來,例如:

T = TypeVar('T')
S = TypeVar('S')
class Foo(Generic[T]):
    def method(self, x: T, y: S) -> S:
    # Body
複製代碼

這是一段有泛型的註解,看起來跟Java代碼沒什麼區別了。而諷刺的是,Java也開始加入了動態語言的特性,例如在Java10中,就有本地變量類型推斷特性,可使用關鍵字 var 來定義變量,而不須要指定數據類型,意味着,靜態語言也開始往動態語言特性方面發展。

public class VarTest {

    public static void main(String[] args) {
        var name = "java";
        System.out.println(name);
    }
}
複製代碼

那麼究竟是靜態語言好仍是動態語言好,Java和Python各自做爲靜態語言和動態語言的表明,一個明顯的特色就是都在互相借鑑彼此的優勢,所謂天下大勢,分久必合,合久必分。沒有一種語言是完美的,Python靈活但可控性沒那麼強,更像是一位開放的家長,在語言的處理上給開發者最大的自由。畢竟 We are all consenting adults! 而反觀Java,就像一位嚴苛的家長同樣,當心翼翼地對待每一個開發者,生怕你闖禍。

相關文章
相關標籤/搜索