You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为UserDict添加类型提示?让类型检查器自动识别自定义UserDict的值为Position类型

How to Make Type Checkers Recognize UserDict Keys as Strings and Values as Position Type

I'm trying to define a UserDict subclass that reads data from JSON, where keys are strings and values are of type Position. The JSON structure looks like this:

{ "pages": [ { "areas": [ { "name": "My_Name", "x": 179.95495495495493, "y": 117.92792792792793, "height": 15.315315315315303, "width": 125.58558558558553 }, ... ] } ] }

My current code is as follows, but type checkers like MyPy and Pylance don't recognize that page_1["My_Name"] is of type Position. How can I modify the code to fix this?

import json
from collections import UserDict
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Union, cast
from typing_extensions import Literal

JsonPosition = Dict[str, Union[str, float]]
JsonPage = Optional[Dict[Literal["areas"], List[JsonPosition]]]

@dataclass
class Position:
    """Information for a position"""
    name: str
    x: float
    y: float
    width: float
    height: float

    @classmethod
    def from_json(cls, dict_values: JsonPosition):
        return cls(**dict_values)  # type: ignore # dynamic typing

class Page(UserDict):
    """Information about positions on a page"""
    @classmethod
    def from_json(cls, page: JsonPage):
        """Get positions from JSON Dictionary"""
        if page is None:
            return cls()
        return cls({cast(str, p["name"]): Position.from_json(p) for p in page["areas"]})

JSON = Path("my_positions.json").read_text()
positions = json.loads(JSON)
page_1 = Page.from_json(positions["pages"][0])

To get type checkers to correctly infer that page_1["My_Name"] is of type Position, you need to fix the generic typing of your Page class and refine some type annotations. Here's how to modify your code step by step:

1. Specify Generic Parameters for UserDict

The UserDict class is generic, so you need to explicitly declare that your Page subclass uses str as keys and Position as values. Update the class definition to:

class Page(UserDict[str, Position]):

This tells type checkers exactly what key-value types the dictionary holds.

2. Refine the Position.from_json Method

You can remove the # type: ignore comment by adding precise type assertions to ensure the JSON values match the Position dataclass fields. This makes the type checker confident that the input is valid:

@classmethod
def from_json(cls, dict_values: JsonPosition) -> "Position":
    return cls(
        name=cast(str, dict_values["name"]),
        x=cast(float, dict_values["x"]),
        y=cast(float, dict_values["y"]),
        width=cast(float, dict_values["width"]),
        height=cast(float, dict_values["height"])
    )

By explicitly casting each field, you eliminate the dynamic typing warning and make the method's type behavior clear.

3. Improve JsonPage Type Definition

Your current JsonPage type can be made more precise. If your JSON's pages array always contains objects with an areas key, adjust it to:

JsonPage = Dict[Literal["areas"], List[JsonPosition]]

If None is still a possible input for from_json, keep the Optional wrapper, but ensure the type checker knows you're handling it correctly in the method.

4. Add Return Type Annotation to Page.from_json

Explicitly annotate the return type of the from_json method as Page to reinforce that it returns an instance of your typed dictionary:

@classmethod
def from_json(cls, page: JsonPage) -> "Page":

Full Modified Code

Putting it all together, here's the updated code:

import json
from collections import UserDict
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Union, cast
from typing_extensions import Literal

JsonPosition = Dict[str, Union[str, float]]
# Adjust Optional based on whether your JSON might have null pages
JsonPage = Dict[Literal["areas"], List[JsonPosition]]

@dataclass
class Position:
    """Information for a position"""
    name: str
    x: float
    y: float
    width: float
    height: float

    @classmethod
    def from_json(cls, dict_values: JsonPosition) -> "Position":
        return cls(
            name=cast(str, dict_values["name"]),
            x=cast(float, dict_values["x"]),
            y=cast(float, dict_values["y"]),
            width=cast(float, dict_values["width"]),
            height=cast(float, dict_values["height"])
        )

class Page(UserDict[str, Position]):
    """Information about positions on a page"""
    @classmethod
    def from_json(cls, page: JsonPage) -> "Page":
        """Get positions from JSON Dictionary"""
        return cls({cast(str, p["name"]): Position.from_json(p) for p in page["areas"]})

# If you still need to handle None pages, use this version of from_json:
# @classmethod
# def from_json(cls, page: Optional[JsonPage]) -> "Page":
#     if page is None:
#         return cls()
#     return cls({cast(str, p["name"]): Position.from_json(p) for p in page["areas"]})

JSON = Path("my_positions.json").read_text()
positions = json.loads(JSON)
page_1 = Page.from_json(positions["pages"][0])

# Now type checkers will recognize page_1["My_Name"] as Position
reveal_type(page_1["My_Name"])  # MyPy will output: Revealed type is "Position"

Why This Works

  • By specifying UserDict[str, Position], you're telling type checkers that every key in Page is a string and every value is a Position.
  • The refined from_json methods eliminate type ambiguity, so the type checker can trace the flow of data from JSON to your typed classes.
  • Explicit return type annotations reinforce the expected types, making it easier for tools like Pylance to provide accurate autocompletion and type hints.

内容的提问来源于stack exchange,提问作者Jean-Francois T.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:12:41