FDE PulseViệc làm FDE đang mở 641Mới trong 7 ngày 48Công ty đang tuyển 98Nhận làm từ xa 25%Lương trung vị (Mỹ) $203kTuyển nhiều nhất Databricks 122
EN

Tờ báo của nghề Forward Deployed Engineer

Bách khoa

Type hint và pyright: đọc nhanh codebase của khách, bắt lỗi None trước khi chạy

Bạn chưa sửa dòng nào trong repo của khách, vậy mà pyright đã có thể cho biết hàm nào sẽ trả về None khi dữ liệu thật thiếu một giá trị.

Ảnh một kỹ sư đang đọc code Python trên màn hình laptop trong văn phòng.
Ảnh: Emile Perron / Unsplash
Chia sẻLinkedInFacebookX

Tóm tắt nhanh

  • Python không ép kiểu lúc chạy. Type hint chỉ có tác dụng khi có công cụ như pyright đọc chúng.
  • Ở cấu hình mặc định, pyright kiểm tra cả code không có annotation và tự suy ra kiểu trả về. Mypy thì mặc định bỏ qua những hàm không annotate.
  • Nên thêm hint ở chỗ dữ liệu đi vào trước, bắt đầu ở mức standard, và để strict cho module mới viết.
Hai khung code đặt cạnh nhau. Bên trái, khi chưa sửa gì, dòng return rate * price được đánh dấu màu cam vì pyright suy ra rate có kiểu float | None. Bên phải, sau khi thêm phép kiểm tra if rate is None thì raise ValueError, kiểu của rate ở cùng dòng đó được thu hẹp thành float.
Không có annotation nào, pyright vẫn suy ra load_rate trả về float | None. Thêm một phép kiểm tra None là kiểu của rate được thu hẹp thành float.

Ngày thứ hai ở chỗ khách, bạn được cấp quyền vào một repo Python đã vài năm tuổi. Người viết phần tính phí đã nghỉ việc, còn hàm nào cũng chỉ có tên tham số, không có lấy một dòng chú thích. Thứ Sáu này bạn phải nối pipeline của mình vào đúng đoạn code đó.

Thường thì người ta đọc từng file, rải print khắp nơi rồi chạy thử với dữ liệu mẫu. Cách này chậm, và nó bỏ sót đúng loại lỗi hay làm sập deployment thật: một hàm luôn trả về giá trị khi chạy với dữ liệu mẫu, nhưng lại trả về None khi dữ liệu của khách thiếu một dòng.

Type hint đi cùng một type checker như pyright giải quyết được cả hai việc. Bạn đọc code lạ nhanh hơn vì biết mỗi hàm nhận gì và trả gì, và bạn bắt được lỗi trước khi chạy. FDE gần như lúc nào cũng làm việc trên code mình không viết, nên đây là kỹ năng đáng đầu tư.

Python không kiểm tra kiểu thì ai kiểm tra?

Hiểu sai chỗ này thì mọi việc sau đều lệch, nên cần nói rõ trước. Tài liệu chính thức của module typing ghi rằng lúc chạy, Python không ép kiểu cho annotation của hàm hay biến. Annotation là để công cụ bên thứ ba đọc: type checker, IDE, linter.

Chuyện này được định sẵn từ đầu. PEP 3107 đưa vào cú pháp annotation cho hàm nhưng cố ý không quy định nó mang nghĩa gì. Phải đến PEP 484, annotation mới được dùng để chỉ kiểu. Chính PEP 484 cũng khẳng định Python sẽ vẫn là ngôn ngữ kiểu động, và các tác giả không muốn ép ai phải viết type hint.

Với bạn, điều này có hai hệ quả. Viết def f(x: int) không ngăn được ai truyền vào một chuỗi. Ngược lại, thêm hint vào code của khách cũng không làm code chạy khác đi, nên việc này gần như không có rủi ro. Hint chỉ thật sự có ích khi có một công cụ đọc chúng.

Vì sao pyright hợp với code không có hint

Pyright là một static type checker. Bạn có thể cài nó làm language server trong editor hoặc dùng bản command-line. Theo tài liệu của pyright, bạn không cần chú thích hết mới dùng được: thông tin kiểu càng nhiều thì nó càng giúp được nhiều.

Với code kế thừa, điểm quan trọng là pyright không chờ bạn viết hint. Theo tài liệu, ở cấu hình mặc định nó kiểm tra mọi đoạn code, dù có annotation hay không.

Nó suy ra kiểu của biến từ giá trị được gán vào. Kiểu trả về của hàm được coi là union của kiểu ở mọi câu lệnh return. Chỗ nào không suy ra được thì pyright coi là Unknown, một dạng của Any.

Mypy mặc định làm khác. Cũng theo tài liệu so sánh của pyright, mypy bỏ qua mọi hàm và method không có type annotation.

Đây là một cái bẫy. Trên repo không có hint, mypy báo sạch không có nghĩa là code ổn, chỉ có nghĩa là nó chưa đọc phần lớn code.

Tài liệu của pyright còn cho biết trên codebase lớn, pyright nhanh hơn mypy 3 đến 5 lần là chuyện thường gặp.

Một lỗi None, đi từ đầu đến cuối

Thử hình dung đoạn code tính phí theo vùng dưới đây. Code chạy tốt trên máy người viết, vì dữ liệu thử nghiệm lúc nào cũng có vùng EU.

def load_rate(rows, region):
    for row in rows:
        if row["region"] == region:
            return float(row["rate"])
    return None

def price_with_tax(rows, region, price):
    rate = load_rate(rows, region)
    return rate * price

Không dòng nào có annotation. Bạn chạy pyright trên thư mục này mà không sửa gì. rows không có kiểu nên pyright coi row là Unknown. Nhưng chỉ hai câu lệnh return đã đủ để nó suy ra load_rate trả về float | None.

Hệ quả nằm ở dòng cuối. Ở đó rate mang kiểu float | None, và nhân None với một con số thì chương trình sẽ dừng. Vì thông tin kiểu này đã có sẵn trước khi chạy, dòng đó là chỗ đầu tiên nên soi khi đọc kết quả của pyright. Nó trỏ đúng tới tình huống làm hỏng hóa đơn: khách có một vùng chưa có trong bảng giá.

Nếu dùng mypy với cấu hình mặc định, cả hai hàm đều bị bỏ qua vì không có annotation.

Cách sửa gồm hai bước. Bước đầu là viết rõ ý định bằng type hint. Cheat sheet của mypy hướng dẫn viết X | None cho giá trị có thể là None, còn Optional[X] cũng mang đúng nghĩa đó:

def load_rate(rows: list[dict[str, str]], region: str) -> float | None:
    for row in rows:
        if row["region"] == region:
            return float(row["rate"])
    return None

def price_with_tax(rows: list[dict[str, str]], region: str, price: float) -> float:
    rate = load_rate(rows, region)
    if rate is None:
        raise ValueError(f"Chưa có thuế suất cho vùng {region}")
    return rate * price

Bước thứ hai là phép kiểm tra if rate is None. Sau dòng đó, type checker thu hẹp kiểu của rate thành float. Code vẫn ngắn nhưng giờ đã nói rõ phải làm gì khi thiếu dữ liệu. Đây cũng là câu nên mang đi hỏi khách: vùng chưa có thuế suất thì báo lỗi hay dùng một mức mặc định?

Làm trên repo của khách theo thứ tự nào?

Bước một là chạy pyright với cấu hình mặc định, chưa sửa gì. Theo tài liệu, typeCheckingMode của pyright có bốn mức là off, basic, standard và strict, mặc định là standard. Lần chạy đầu tiên cho bạn một tấm bản đồ: file nào nhiều lỗi, hàm nào có thể trả về None, chỗ nào toàn Unknown.

Bước hai là thêm hint ở chỗ dữ liệu đi vào trước: những hàm đọc file, gọi API, truy vấn database, hoặc nhận đầu vào từ hệ thống của khách.

Lý do nằm ở cheat sheet của mypy: tham số không có kiểu bị coi là Any, và với Any thì công cụ không bắt được lỗi nào.

Bước ba là ghi cấu hình vào pyrightconfig.json trong nhánh của bạn, rồi đưa lệnh pyright vào CI nếu khách đồng ý. Phần code cũ để ở mức standard. Module mới do bạn viết thì có thể đặt strict, vì không có gì cũ phải dọn.

Những sai lầm hay gặp

Sai lầm đầu tiên là tưởng type hint bảo vệ code lúc chạy. Không phải vậy. Nếu dữ liệu từ API của khách có thể sai kiểu, bạn vẫn phải tự kiểm tra ngay chỗ nhận dữ liệu.

Sai lầm thứ hai là tin vào kết quả sạch của mypy trên một repo chưa có annotation, như trong ví dụ trên. Sai lầm thứ ba là rải Any khắp nơi cho hết lỗi đỏ. Số lỗi trông đẹp hơn, nhưng công cụ cũng chẳng còn kiểm tra được gì.

Sai lầm cuối cùng là bật strict cho cả repo cũ ngay từ ngày đầu. Bạn sẽ nhận một danh sách lỗi dài, khách sẽ thấy một pull request sửa hàng trăm file, còn những lỗi None thật sự quan trọng thì chìm giữa đống đó.

Muốn đưa kỹ năng này vào CV, hãy ghi lại số lỗi pyright báo lúc mới tiếp quản repo và những lỗi bạn đã chặn được trước khi gặp dữ liệu thật. Một dòng như vậy cho nhà tuyển dụng thấy bạn làm quen với codebase lạ nhanh đến đâu, điều mà chữ “Python” chung chung không làm được.

Lần sau được giao một repo không có tài liệu, việc đầu tiên nên làm là chạy pyright, trước cả khi viết câu print nào. Danh sách lỗi đầu tiên đó thường cho bạn biết khá rõ dữ liệu của khách có thể làm code hỏng ở đâu.

Bài này có hữu ích không?

Dùng cùng trợ lý AIHỏi Claude ↗Hỏi ChatGPT ↗
7 nguồn
Đọc tiếp trên lộ trình · Chặng 1: Nền tảngForward Deployed Analyst: vai trò dữ liệu đứng giữa nền tảng AI và khách hàngNgười giữ chức danh này phải hỏi dữ liệu của khách trả lời được câu hỏi nào rồi mới nạp nó vào nền tảng, và đây cũng là một cửa vào nghề forward-deployed cho người làm data, research hay consulting.