Python 键入一个递归转换容器中所有值的函数

问题描述 投票:0回答:1

我正在处理一个来自数据库的对象,该数据库是

dict
的子类,但它没有以可以接受任何类型参数的方式定义。在我下面的示例中,我将此类型命名为
SpecialDict
,出于我正在做的事情的目的,我只想将其转换为标准
dict
.

现在,我正在创建一个函数,将这个

SpecialDict
的实例转换为标准
dict
- 然而,这个
SpecialDict
(以及后续的
dict
)可以任意嵌套但有一定的限制:

  1. 钥匙总是
    str
  2. 它可以包含
    SpecialDict
  3. 的其他实例
  4. 它可以包含
    list
  5. 这些容器最终可以包含
    int
    str

真实的例子是以同样的方式设置的,但是允许的原子类型不仅仅是

int
str
,但这不应该影响问题。

鉴于上述情况,上述函数的简单递归实现将是:

SUPPORTED_CONTAINER_TYPES = (dict, list)
SUPPORTED_ATOMIC_TYPES = (str, int)


class SpecialDict(dict):
    pass


def converter(container):
    if isinstance(container, dict):
        dict_ = {}
        for key, value in container.items():
            if isinstance(value, SUPPORTED_CONTAINER_TYPES):
                dict_[key] = converter(container=value)
            elif isinstance(value, SUPPORTED_ATOMIC_TYPES):
                dict_[key] = value
            else:
                raise TypeError(f"{type(value)} is not supported.")

        return dict_

    if isinstance(container, list):
        datastore_list = []
        for item in container:
            if isinstance(item, SUPPORTED_CONTAINER_TYPES):
                datastore_list.append(converter(container=item))
            elif isinstance(item, SUPPORTED_ATOMIC_TYPES):
                datastore_list.append(item)
            else:
                raise TypeError(f"{type(item)} is not supported.")

        return datastore_list

    raise TypeError(f"{type(container)} is not supported.")

本质上,我们递归地遍历

SpecialDict
并填充一个新的
dict
。问题是我无法正确输入此内容。

该函数可以将

list
dict
SpecialDict
(虽然也是
dict
子类)作为输入,并返回
list
dict

但是,由于这些都可以任意包含它们自己或其他容器类型,我会尝试按照这些行递归地定义类型:

from typing import Union

SUPPORTED_CONTAINER_TYPES = (dict, list)
SUPPORTED_ATOMIC_TYPES = (str, int)


class SpecialDict(dict):
    pass


MyList = list[Union[str, int, "MyList", "MyDict", SpecialDict]]
MyDict = dict[str, Union[str, int, MyList, "MyDict", SpecialDict]]

MyListWithoutSpecialDict = list[Union[str, int, "MyList", "MyDict"]]
MyDictWithoutSpecialDict = dict[str, Union[str, int, MyList, "MyDict"]]


def converter(container: MyList | MyDict) -> MyListWithoutSpecialDict | MyDictWithoutSpecialDict:
    if isinstance(container, dict):
        dict_ = {}
        for key, value in container.items():
            if isinstance(value, SUPPORTED_CONTAINER_TYPES):
                dict_[key] = converter(container=value)
            elif isinstance(value, SUPPORTED_ATOMIC_TYPES):
                dict_[key] = value
            else:
                raise TypeError(f"{type(value)} is not supported.")

        return dict_

    if isinstance(container, list):
        datastore_list = []
        for item in container:
            if isinstance(item, SUPPORTED_CONTAINER_TYPES):
                datastore_list.append(converter(container=item))
            elif isinstance(item, SUPPORTED_ATOMIC_TYPES):
                datastore_list.append(item)
            else:
                raise TypeError(f"{type(item)} is not supported.")

        return datastore_list

    raise TypeError(f"{type(container)} is not supported.")

但这给出了一些 mypy 错误,而且似乎也没有真正捕捉到我正在尝试做的事情。将函数分解成另一个执行某些类型强制的函数可能会有所帮助,但在一天结束时,我总是会留下这个最外层函数的类型提示,我不确定如何解决.

有什么建议吗?

python python-3.x mypy typing
1个回答
2
投票

你想要的是类型级别的映射

  • MyList -> MyListWithoutSpecialDict
  • MyDict -> MyDictWithoutSpecialDict

然后你可以定义类似的东西

# N.B. MyList and MyDict need to be real types, not type hints
T = TypeVar('T', MyList, MyDict)

def converter(container: T) -> TF[T]:
    ...

在哪里

TF
提供映射。

我认为 Python 的类型提示还不支持这样的概念。


请注意,您在

converter
上的类型提示隐式定义了一个类型

Callable[[Union[MyList, MyDict]], 
         Union[MyListWithoutSpecialDict, MyDictWithoutSpecialDict]]

和你要的类型不一样,即

Union[
    Callable[[MyList], MyListWithoutSpecialDict],
    Callable[[MyDict], MyDictWithoutSpecialDict]
]

如果语法允许,你可以这样写

converter: Union[Callable[...], Callable[...]]  # per above
def converter(container):
    ...

假设的类型映射

TF
是让您在 Python 的语法中定义等效类型的部分

Callable[[T], TF[T]]
© www.soinside.com 2019 - 2024. All rights reserved.