Python TypedDict

Khi làm việc với các dịch vụ siêu nhỏ trong Python, một mẫu phổ biến mà tôi thấy là—việc sử dụng các từ điển được điền động dưới dạng tải trọng của API REST hoặc hàng đợi tin nhắn. Để hiểu ý của tôi về điều này, hãy xem xét ví dụ sau

# src.py
from __future__ import annotations

import json
from typing import Any

import redis  # Do a pip install.


def get_payload[] -> dict[str, Any]:
    """Get the 'zoo' payload containing animal names and attributes."""

    payload = {"name": "awesome_zoo", "animals": []}

    names = ["wolf", "snake", "ostrich"]
    attributes = [
        {"family": "Canidae", "genus": "Canis", "is_mammal": True},
        {"family": "Viperidae", "genus": "Boas", "is_mammal": False},
    ]
    for name, attr in zip[names, attributes]:
        payload["animals"].append[  # type: ignore
            {"name": name, "attribute": attr},
        ]
    return payload


def save_to_cache[payload: dict[str, Any]] -> None:
    # You'll need to spin up a Redis db before instantiating
    # a connection here.
    r = redis.Redis[]
    print["Saving to cache..."]
    r.set[f"zoo:{payload['name']}", json.dumps[payload]]


if __name__ == "__main__":
    payload = get_payload[]
    save_to_cache[payload]

Ở đây, hàm

docker run -d -p 6379:6379 redis:alpine
0 xây dựng một tải trọng được lưu trữ trong Redis DB trong hàm
docker run -d -p 6379:6379 redis:alpine
1. Hàm
docker run -d -p 6379:6379 redis:alpine
0 trả về một lệnh biểu thị một tải trọng giả định có chứa dữ liệu của một sở thú tưởng tượng. Để thực thi đoạn mã trên, trước tiên bạn cần tạo cơ sở dữ liệu Redis. Bạn có thể sử dụng Docker để làm như vậy. Cài đặt và định cấu hình Docker trên hệ thống của bạn và chạy

docker run -d -p 6379:6379 redis:alpine

Nếu bạn chạy đoạn mã trên sau khi khởi tạo máy chủ Redis, nó sẽ chạy mà không gây ra bất kỳ lỗi nào. Bạn có thể kiểm tra nội dung được lưu trong Redis bằng lệnh sau [giả sử bạn đã cài đặt

docker run -d -p 6379:6379 redis:alpine
3 và
docker run -d -p 6379:6379 redis:alpine
4 trong hệ thống của mình]

echo "get zoo:awesome_zoo" | redis-cli | jq

Điều này sẽ trả lại tải trọng sau cho bảng điều khiển của bạn

________số 8_______

Mặc dù quy trình công việc này hoạt động trong thời gian chạy, nhưng có một vấn đề lớn ở đây. Rất khó để hình dung hình dạng của

docker run -d -p 6379:6379 redis:alpine
5 từ đầu ra của hàm
docker run -d -p 6379:6379 redis:alpine
0; . Đầu tiên, nó khai báo một từ điển với hai trường—
docker run -d -p 6379:6379 redis:alpine
7 và
docker run -d -p 6379:6379 redis:alpine
8. Ở đây,
docker run -d -p 6379:6379 redis:alpine
7 là một giá trị chuỗi biểu thị tên của sở thú. Trường khác
docker run -d -p 6379:6379 redis:alpine
8 là danh sách chứa tên và thuộc tính của động vật trong sở thú. Sau đó, vòng lặp for lấp đầy từ điển với các cấu trúc dữ liệu lồng nhau. Chuỗi hoạt động này khiến bạn khó có thể cụ thể hóa hình dạng cuối cùng của kết quả
docker run -d -p 6379:6379 redis:alpine
5 trong tâm trí của mình

Trong trường hợp này, bạn sẽ phải kiểm tra nội dung của bộ đệm Redis để hiểu đầy đủ về hình dạng của dữ liệu. Viết mã theo cách trên là dễ dàng nhưng nó khiến người tiếp theo làm việc trên cơ sở mã thực sự khó hiểu được trọng tải trông như thế nào mà không cần chạm vào bộ lưu trữ dữ liệu. Có một cách tốt hơn để truyền đạt một cách khai báo hình dạng của tải trọng mà không liên quan đến việc viết các chuỗi tài liệu lớn không thể xác định được. Đây là cách bạn có thể tận dụng

echo "get zoo:awesome_zoo" | redis-cli | jq
2 và
echo "get zoo:awesome_zoo" | redis-cli | jq
3 để đạt được mục tiêu

# src.py
from __future__ import annotations

import json

# In < Python 3.8, import 'TypedDict' from 'typing_extensions'.
# In < Python 3.9, import 'Annotated' from 'typing_extensions'.
from typing import Annotated, Any, TypedDict

import redis  # Do a pip install.


class Attribute[TypedDict]:
    family: str
    genus: str
    is_mammal: bool


class Animal[TypedDict]:
    name: str
    attribute: Attribute


class Zoo[TypedDict]:
    name: str
    animals: list[Animal]


def get_payload[] -> Zoo:
    """Get the 'zoo' payload containing animal names and attributes."""

    payload: Zoo = {"name": "awesome_zoo", "animals": []}

    names = ["wolf", "snake", "ostrich"]
    attributes: tuple[Attribute, ...] = [
        {"family": "Canidae", "genus": "Canis", "is_mammal": True},
        {"family": "Viperidae", "genus": "Boas", "is_mammal": False},
    ]
    for name, attr in zip[names, attributes]:
        payload["animals"].append[{"name": name, "attribute": attr}]
    return payload


def save_to_cache[payload: Annotated[Zoo, dict]] -> None:
    # You'll need to spin up a Redis db before instantiating
    # a connection here.
    r = redis.Redis[]
    print["Saving to cache..."]
    r.set[f"zoo:{payload['name']}", json.dumps[payload]]


if __name__ == "__main__":
    payload: Zoo = get_payload[]
    save_to_cache[payload]

Lưu ý, cách tôi đã sử dụng

echo "get zoo:awesome_zoo" | redis-cli | jq
2 để khai báo cấu trúc lồng nhau của tải trọng
echo "get zoo:awesome_zoo" | redis-cli | jq
5. Trong thời gian chạy, các thể hiện của các lớp typed-dict hoạt động giống như các dict bình thường. Ở đây,
echo "get zoo:awesome_zoo" | redis-cli | jq
5 chứa hai trường—
docker run -d -p 6379:6379 redis:alpine
7 và
docker run -d -p 6379:6379 redis:alpine
8. Trường
docker run -d -p 6379:6379 redis:alpine
8 được chú thích là
{
  "name": "awesome_zoo",
  "animals": [
    {
      "name": "wolf",
      "attribute": {
        "family": "Canidae",
        "genus": "Canis",
        "is_mammal": true
      }
    },
    {
      "name": "snake",
      "attribute": {
        "family": "Viperidae",
        "genus": "Boas",
        "is_mammal": false
      }
    }
  ]
}
0 trong đó
{
  "name": "awesome_zoo",
  "animals": [
    {
      "name": "wolf",
      "attribute": {
        "family": "Canidae",
        "genus": "Canis",
        "is_mammal": true
      }
    },
    {
      "name": "snake",
      "attribute": {
        "family": "Viperidae",
        "genus": "Boas",
        "is_mammal": false
      }
    }
  ]
}
1 là một kiểu đánh máy khác.
{
  "name": "awesome_zoo",
  "animals": [
    {
      "name": "wolf",
      "attribute": {
        "family": "Canidae",
        "genus": "Canis",
        "is_mammal": true
      }
    },
    {
      "name": "snake",
      "attribute": {
        "family": "Viperidae",
        "genus": "Boas",
        "is_mammal": false
      }
    }
  ]
}
1 typed-dict chứa một typed-dict khác có tên là
{
  "name": "awesome_zoo",
  "animals": [
    {
      "name": "wolf",
      "attribute": {
        "family": "Canidae",
        "genus": "Canis",
        "is_mammal": true
      }
    },
    {
      "name": "snake",
      "attribute": {
        "family": "Viperidae",
        "genus": "Boas",
        "is_mammal": false
      }
    }
  ]
}
3 xác định các thuộc tính khác nhau của động vật

Nhìn vào typed-dict

echo "get zoo:awesome_zoo" | redis-cli | jq
5 và theo cấu trúc lồng nhau của nó, hình dạng cuối cùng của tải trọng trở nên rõ ràng hơn mà chúng ta không cần phải tìm ví dụ về tải trọng. Ngoài ra, Mypy có thể kiểm tra xem tải trọng có phù hợp với hình dạng của loại được chú thích hay không. Tôi đã sử dụng
{
  "name": "awesome_zoo",
  "animals": [
    {
      "name": "wolf",
      "attribute": {
        "family": "Canidae",
        "genus": "Canis",
        "is_mammal": true
      }
    },
    {
      "name": "snake",
      "attribute": {
        "family": "Viperidae",
        "genus": "Boas",
        "is_mammal": false
      }
    }
  ]
}
5 trong tham số đầu vào của hàm
docker run -d -p 6379:6379 redis:alpine
1 để giao tiếp với người đọc rằng một thể hiện của lớp
echo "get zoo:awesome_zoo" | redis-cli | jq
5 là một lệnh tuân theo hợp đồng được đặt ra trong chính loại đó. Loại
echo "get zoo:awesome_zoo" | redis-cli | jq
3 có thể được sử dụng để thêm bất kỳ siêu dữ liệu tùy ý nào vào một loại cụ thể

Trong thời gian chạy, đoạn mã này sẽ thể hiện hành vi tương tự như đoạn mã trước. Mypy cũng phê duyệt điều này

Xử lý các cặp khóa-giá trị bị thiếu

Theo mặc định, trình kiểm tra loại sẽ xác thực cấu trúc hình dạng của chính tả được chú thích bằng một lớp

echo "get zoo:awesome_zoo" | redis-cli | jq
2 và tất cả các cặp khóa-giá trị mà chú thích mong đợi phải có trong chính tả. Có thể nới lỏng hành vi này bằng cách chỉ định toàn bộ. Điều này có thể hữu ích để xử lý các trường bị thiếu mà không bỏ qua loại an toàn. Xem xét điều này

from __future__ import annotations

from typing import TypedDict


class Attribute[TypedDict]:
    family: str
    genus: str
    is_mammal: bool


animal_attribute: Attribute = {
    "family": "Hominidae",
    "genus": "Homo",
}  # Mypy will complain about the missing 'is_mammal' key.

Mypy sẽ phàn nàn về việc thiếu chìa khóa

src.py:12: error: Missing key "is_mammal" for TypedDict "Attribute"
    animal_attribute: Attribute = {
                                  ^
Found 1 error in 1 file [checked 1 source file]

Bạn có thể thư giãn hành vi này như thế này

...


class Attribute[TypedDict, total=False]:
    family: str
    genus: str
    is_mammal: bool


...

Bây giờ Mypy sẽ không còn phàn nàn về trường bị thiếu trong chính tả được chú thích nữa. Tuy nhiên, điều này vẫn sẽ không cho phép các khóa tùy ý không được xác định trong

echo "get zoo:awesome_zoo" | redis-cli | jq
2. Ví dụ

TypedDict Python là gì?

TypedDict là một loại kiểu mới được công cụ đánh máy Python nhận dạng như mypy . Nó mô tả một từ điển/bản đồ có cấu trúc với một tập hợp các khóa chuỗi được đặt tên dự kiến ​​được ánh xạ tới các giá trị của các loại dự kiến ​​cụ thể. Các cấu trúc như vậy phổ biến khi trao đổi dữ liệu JSON, điều này thường xảy ra đối với các ứng dụng web Python.

Khi nào tôi nên sử dụng TypedDict?

TypedDict để hỗ trợ trường hợp sử dụng trong đó một đối tượng từ điển có một bộ khóa chuỗi cụ thể, mỗi khóa có một giá trị của một loại cụ thể .

Làm cách nào để sử dụng các loại trong Python?

Cú pháp hàm type[] trong Python . Khi một đối số duy nhất được chuyển đến hàm type[], nó sẽ trả về kiểu của đối tượng. Giá trị của nó giống như đối tượng. __class__ biến thể hiện. The type[] function is used to get the type of an object. When a single argument is passed to the type[] function, it returns the type of the object. Its value is the same as the object. __class__ instance variable.

Chú thích kiểu trong Python là gì?

Chú thích loại là gì. ? . used to indicate the data types of variables and inputs/outputs of functions and methods.

Chủ Đề