Pyslvs 使用手冊

  • Home
    • Site Map
    • reveal
    • blog
  • 簡介
    • 機構與機器
    • Pyslvs 介面
      • Mechanism
      • Inputs
      • Synthesis
  • 操作
    • 模擬操作
      • 曲柄滑塊
      • 四連桿
    • 尺寸合成操作
    • 構造合成操作
    • 由現有設計進行尺寸合成
  • 下載
  • 分析
    • 三角形表示式
  • 範例
    • 模擬範例
      • Quick return mechanism
      • Watt six-bar linkage
      • Stephenson six-bar linkage
      • Klann linkage
    • 尺寸合成範例
    • 結構合成範例
    • 產品設計範例
  • Python 教學
    • 控制流程
    • 簽章
    • 複製與參照
    • 物件導向
    • 類型註解
  • 開發
    • Windows 開發
      • 環境修正
      • PyInstaller 對 Windows 的支援
    • Ubuntu 開發
      • AppImage 包裝
    • Mac OS X 開發
      • PyInstaller 對 Mac OS X 的支援
    • 參與協同
      • 註解規則
      • 命名規則
      • 類型註解規則
    • Kmol-editor
  • 參考
    • Misc
物件導向 << Previous Next >> 開發

類型註解

基於協同開發,Python 引入了選擇性的類型註解。

學員可以藉由類型註解,在程式碼協同時較快辨認變數類型。

類型註解

類型註解 (Typing) 是一種註解,可以為每種參數進行標示。由於文法問題,類型註解並不是強迫性的,不過仍有效用。可藉由每個範圍 (Scope) 的 __annotations__ 名稱取得,若有工具或 IDE 的功能,可以進行靜態分析。

以下內容依據為 PEP 484。

省略記號

省略記號 (Ellipsis)「...」在 Python 中是一種佔位符號,可以在註解中代表多個類似物件,也能取代 pass 關鍵字。

等價表示

預設該值如果為 Any 或可以明確推斷,可以選擇不標示。

其中:

  • type(None) 標示為 None。
  • object 標示為 Any。
  • 會導致錯誤時標示為 NoReturn,僅可用於回傳值。

基本文法

Python 為弱型別 (Weak typing) 語言,又稱鴨子型別 (Duck type),相較於強型別 (Strong typing) 語言,只要變數能用即可,不能用就引發錯誤。當然這只能在相對安全的直譯式語言 (Interpreted language) 中,因為這裡只要使用 try 語句即可:

try:
    # 測試 a 能不能執行 test_method。
    b = a.test_method()
except AttributeError:
    # 沒有 test_method。
    c = 20
else:
    c = b * 2

但是因為執行效能與開發效率問題,一直測試顯然不是好方法,因此 Python 在簽章上引入了類型註解的文法。

簽章的參數中,使用「:」符號後連接類型名稱;回傳值則是使用「->」記號連接。參照於一般英文符號以及運算子必須由空白環繞 (PEP 8) 的規定,「:」符號會連接前一個表示式,與下一個表示式間隔空白;「->」記號則是必須由空白環繞。

# 只能從預設值或參數名稱猜測。
def func(p0, p1=20, p2=True):
    ...

# 直接規定型別。
def func(p0: int, p1: int = 20, p2: bool = True) -> List[str]:
    ...

# 若是太長可以利用括弧換行。
def func(
    number: int,
    size: int = 20,
    reverse: bool = True
) -> List[str]:
    ...

在 Python 3.6 新增單一變數的類型註解文法 (PEP 526)。

不過一般可直接辨識的變數就不會使用,例如直接賦予類型的初始化物件。

# 等等會裝入整數。
a: List[int] = []
# 這麼明顯就不用了。
a: MyClass = MyClass()

類型註解也支援名稱替換:

Point = Tuple[float, float]
PointPair = Tuple[Point, Point]
graphics: Dict[str, List[PointPair]] = {}

參數輸入值的類型註解一般也是用**鴨子型別**的概念標示,提醒開發者「需要這樣使用」,而非「一定需要這種類型」。如:

def func(w) -> bool:
    """w 是一個會進行疊代與檢索的物件。"""
    for i in range(20):
        for k in w:
            if w[i + 2] == 'z':
                return True

# 應該標示成序列:
w: Secquence[int]
# 而非強迫成某種型態:
w: List[int]
w: Tuple[int, ...]

自 Python 3.5 起支援,支援放入或回傳的型別必須由標準模組 typing 提供。類型中的類型註解名稱會跟一般內建類型名稱不一樣,改成字首大寫。

通常 typing 模組的導入習慣將相同性質的類型一起擺放。

from typing import (
    # 狀態變數
    TYPE_CHECKING,
    # 序列
    Tuple,
    List,
    Secquence,
    # 二元搜尋樹
    Set,
    Dict,
    # 可呼叫物(函式)
    Callable,
    # 迭代器與生產器
    Iterator,
    Generator,
    # 邏輯判斷
    Optional,
    Union,
    Any,
    # 泛型
    Generic,
    # 函式或裝飾器
    TypeVar,
    overload,
)

以下將介紹上述常用的類型標示。

容器

使用單一項目的容器:

# 其實 tuple 容器是固定長度的。
my_tuple: Tuple[int, int] = (20, 20)
# 不限長度的容器。
my_list: List[float] = [20., 50.02, -3.006]
# 不限長度的 tuple 容器(其他變數決定)。
my_tuple: Tuple[int, ...] = tuple(i for i in range(s))
# 巢狀標示。
w: Set[Tuple[int, int]] = {(10, 20), (30, 40), (50, 60)}

使用成對項目的容器:

d: Dict[str, List[int]] = {
    's': [10, 20, 30],
    'b': [],
    'f': [77, 66, 55, 44],
}

函式物件

函式物件使用 Callable 來標示:

Callable[[input_type, ...], return_type]

範例:

def func(n: int) -> int:
    """一般函式"""
    return n * 6

# 匿名函式
k = lambda s, end: s.replace('gen', 'time') + end

func: Callable[[int], int]
k: Callable[[str, str], str]

迭代器與生產器

使用 yield 關鍵字可以搭配 def 關鍵字定義一個迭代器 (Iterator) 或生產器 (Generator) 函式:

def double_range(n):
    """這個 double_range 是一個函式
    但是可以產生以下程式碼的迭帶器物件。

    若當中使用 return 關鍵字,會引發 StopIteration 錯誤。
    不過也可以是無限迴圈。
    """
    for i in range(n):
        yield i * 2

# 建立迭帶器物件 "ten_double_range"。
ten_double_range = double_range(10)
# 使用用內建函式 next 可以產生下一個值。
print(next(ten_double_range))  # 0
print(next(ten_double_range))  # 2
print(next(ten_double_range))  # 4
# 或是使用 for 迴圈連續取值,
# 直到引發 StopIteration 錯誤(不會引發實際 Error)。
for factor in ten_double_range:
    print(factor)  # 6 8 10 12 14 16 18 20
# 當取完值後,再次呼叫會引發 StopIteration 錯誤。
# 此時迭帶器物件無法再使用,必須丟棄。
# next(ten_double_range)

生產器範例:

def double_inputs():
    """這個 double_inputs 是一個函式
    但是可以產生以下程式碼的生產器物件。

    生產器可以接收值。
    """
    while True:
         # 當 yield 擺在右值時可以接收值。
         x = yield
         # 當 yield 右邊有值時可以產生值。
         yield x * 2
         # 不使用名稱可以這樣寫。
         yield (yield) * 2

gen = double_inputs()
next(gen)  # 跳至第一個 yield,不過會回傳 None。
print(gen.send(10))  # 輸入 10,回傳 20。
next(gen)  # 跳至下一個 yield,不過會回傳 None。
print(gen.send(6))  # 輸入 6,回傳 12。

類型標註如下:

Iterator[yield_type]
# Python 3.6 增加
Generator[yield_type, input_type, return_type]

上述製造迭代器與生產器的函式應標註為:

def double_range(n: int) -> Iterator[int]:
    ...

def double_inputs() -> Generator[int, int, None]:
    ...

ten_double_range: Iterator[int]
gen: Generator[int, int, None]

邏輯判斷

類型註解包含被繼承類型,因此其實每個物件都適用 object 類型。

若是沒有繼承關係,但是可以進行相同操作,因此 typing 模組提供方便的邏輯標示。

聯集 (Union) 類型能夠代表多個不同的類型:

Union[T1, T2, ...]

選擇性 (Optional) 類型能夠代表該類型可能會為 None:

Optional[T]
# 同於 Union
Union[T, None]

可以用在簽章的預設值:

def func(w: List[int] = []):
    """這樣會導致預設值的指標被共用。"""
    ...

def func(w: Optional[List[int]] = None):
    """這樣就不會共用。"""
    if w is None:
        w = []
    ...

泛型

自訂類型中有客製化的類型選擇時,就可以用泛型標示。

例如:

from abc import abstractmethod
from typing import Iterator, Generic, TypeVar

_T = TypeVar('_T')

class BaseTable(OtherTable, Generic[_T]):
    @abstractmethod
    def data(self, row: int) -> _T:
        ...

    def data_iter(self) -> Iterator[_T]:
        for row in range(self.row_count()):
            yield self.data(row)

class MyTable(BaseTable[float]):
    def data(self, row: int) -> float:
        """實作此方法。"""
        ...

上述的 MyTable 類型的 data_iter 方法就會回傳 Iterator[float] 類型了。

遞迴引用

遞迴引用類型註解時,直接使用字串即可:

Node: Dict[str, Optional[List['Node']]]

在類型定義中引用自己:

class MyClass:
    def return_me(self, friend: 'MyClass') -> 'MyClass':
        """實例 self 可能是子類型,因此不會標示。"""
        ...

無法導入的名稱或模組:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from core import MyClass

class LocalClass:
    def __init__(self, parent: 'MyClass'):
        """想要取得父項的屬性。"""
        self.my_list = parent.parent_list
        ...

而在 Python 3.7 中,引入了新的延遲分析行為 (PEP 563: Postponed Evaluation of Annotations),將會強制將類型註解轉為字串再分析,並且這將是 Python 4 的預設作為。不過此行為牽涉到文法問題,因此使用向下相容模組 __future__ 來開啟。

這樣一來,在「舊版」的 Python 中就可以使用遞迴引用了。

from __future__ import annotations

class MyClass:
    def return_me(self, friend: MyClass) -> MyClass:
        ...


物件導向 << Previous Next >> 開發

Copyright © All rights reserved | This template is made with by Colorlib