Как создать подкласс словаря, чтобы он поддерживал подсказки универсального типа?

Как можно разделить словарь на подклассы, чтобы подкласс поддерживал подсказку универсального типа? Он должен во всех отношениях вести себя как словарь и поддерживать подсказки типов ключей и значений. Подкласс добавит функции, которые обращаются к данным словаря и управляют ими. Например, у него будет функция valueat(self, idx:int), которая возвращает значение словаря по заданному индексу.

Он не требует OrderedDict в качестве базового класса, но словарь должен иметь предсказуемый порядок. Так как OrderedDict поддерживает порядок вставки и поддерживает подсказки типов, это кажется разумным местом для начала. Вот что я пробовал:

from collections import OrderedDict

class ApplicationSpecificDict(OrderedDict[str, int]):
    ...

Однако это не удается с ошибкой: TypeError: 'type' object is not subscriptable

Это не поддерживается в Python 3.7+, или я что-то упустил?


person Tom Jordan    schedule 09.04.2020    source источник
comment
Вы уже можете ввести обычный dict с помощью typing.Dict[str, int]. Вы искали что-то другое?   -  person user2357112 supports Monica    schedule 09.04.2020
comment
Не уверен в специфике того, что вы пытаетесь сделать, но исходя из того, что вы спрашиваете, классы данных могут быть более элегантным решением для достижения ваших целей. Они обрабатывают ввод, значения по умолчанию и имеют метод replace() для обновления. Они также довольно хорошо взаимодействуют со словарями, включая инициализацию с помощью **dict_val и преобразование переменных экземпляра в словарь с использованием его метода asdict(), если вам действительно нужна чистая функциональность словаря.   -  person John S    schedule 09.04.2020
comment
@JohnS Спасибо. Я использую dataclass в нескольких модулях. Чего не хватает в dataclass, так это поведения dict (очевидно, однако можно добавить это, реализуя методы dict, как я сделал в TypedDict), а также отсутствие поддержки подсказок типов. Как пользователь может указать типы данных ключ/значение члена данных dict класса данных?   -  person Tom Jordan    schedule 09.04.2020
comment
Смысл класса данных состоит в том, чтобы не иметь никаких полей, а только некоторые четко определенные.   -  person LtWorf    schedule 09.04.2020
comment
@user2357112 user2357112 Цель состоит в том, чтобы иметь класс, который действует как класс словаря, но с дополнительными методами для доступа к записям словаря и управления ими в соответствии с подсказками типа ключей и значений.   -  person Tom Jordan    schedule 28.04.2020
comment
Похоже, здесь был дан ответ: stackoverflow.com/questions/34736275/   -  person ex-nerd    schedule 02.07.2020
comment
Отвечает ли это на ваш вопрос? как вводить подсказки collections.OrderedDict через модуль ввода python 3.5   -  person ex-nerd    schedule 02.07.2020
comment
Я также голосую за классы данных (python 3.7+). realpython.com/python-data-classes   -  person Dmitry Belaventsev    schedule 12.10.2020
comment
Отвечает ли это на ваш вопрос? Как добавить аннотации типов к пользовательскому подклассу dict в питон?   -  person Alex    schedule 12.10.2020


Ответы (3)


Пакет typing предоставляет общие классы, соответствующие неуниверсальные классы в collections.abc и коллекции. Эти универсальные классы можно использовать в качестве базовых для создания определяемых пользователем универсальных классов, таких как настраиваемый универсальный словарь.

Примеры универсальных классов, соответствующих типам в collections.abc:

  • typing.AbstractSet(Sized, Collection[T_co])
  • typing.Container(Generic[T_co])
  • typing.Mapping(Sized, Collection[KT], Generic[VT_co])
  • typing.MutableMapping(Mapping[KT, VT])
  • typing.MutableSequence(Sequence[T])
  • typing.MutableSet(AbstractSet[T])
  • typing.Sequence(Reversible[T_co], Collection[T_co])

Примеры универсальных классов, соответствующих типам в collections:

  • typing.DefaultDict(collections.defaultdict, MutableMapping[KT, VT])
  • typing.OrderedDict(collections.OrderedDict, MutableMapping[KT, VT])
  • typing.ChainMap(collections.ChainMap, MutableMapping[KT, VT])
  • typing.Counter(collections.Counter, Dict[T, int])
  • typing.Deque(deque, MutableSequence[T])

Реализация пользовательского универсального словаря

Существует множество вариантов реализации пользовательского универсального словаря. Однако важно отметить, что если пользовательский класс явно не наследуется от Mapping или MutableMapping, статические проверки типов, такие как mypy не будет рассматривать класс как сопоставление.

Пример пользовательского универсального словаря

from collections import abc  # Used for isinstance check in `update()`.
from typing import Dict, Iterator, MutableMapping, TypeVar

KT = TypeVar('KT')
VT = TypeVar('VT')


class MyDict(MutableMapping[KT, VT]):

    def __init__(self, dictionary=None, /, **kwargs) -> None:
        self.data: Dict[KT, VT] = {}
        if dictionary is not None:
            self.update(dictionary)
        if kwargs:
            self.update(kwargs)
    
    def __contains__(self, key: KT) -> bool:
        return key in self.data

    def __delitem__(self, key: KT) -> None:
        del self.data[key]

    def __getitem__(self, key: KT) -> VT:
        if key in self.data:
            return self.data[key]
        raise KeyError(key)

    def __len__(self) -> int:
        return len(self.data)

    def __iter__(self) -> Iterator[KT]:
        return iter(self.data)

    def __setitem__(self, key: KT, value: VT) -> None:
        self.data[key] = value
    
    @classmethod
    def fromkeys(cls, iterable: Iterable[KT], value: VT) -> "MyDict":
        """Create a new dictionary with keys from `iterable` and values set 
        to `value`.

        Args:
            iterable: A collection of keys.
            value: The default value. All of the values refer to just a single 
                instance, so it generally does not make sense for `value` to be a 
                mutable object such as an empty list. To get distinct values, use 
                a dict comprehension instead.

        Returns:
            A new instance of MyDict.
        """
        d = cls()
        for key in iterable:
            d[key] = value
        return d

    def update(self, other=(), /, **kwds) -> None:
        """Updates the dictionary from an iterable or mapping object."""
        if isinstance(other, abc.Mapping):
            for key in other:
                self.data[key] = other[key]
        elif hasattr(other, "keys"):
            for key in other.keys():
                self.data[key] = other[key]
        else:
            for key, value in other:
                self.data[key] = value
        for key, value in kwds.items():
            self.data[key] = value

person Christopher Peisert    schedule 12.10.2020

Я разместил на этот вопрос, который у вас может быть обманом, но я также включу его здесь, потому что я нашел оба этих вопроса, когда гуглил, как это сделать.

По сути, вам нужно использовать тип Сопоставление универсального Это общая аннотация, которую использует dict, чтобы вы могли определять другие типы, такие как MyDict[str, int].

Как:

import typing
from collections import OrderedDict

# these are generic type vars to tell mutable-mapping 
# to accept any type vars when creating a sub-type of your generic dict
_KT = typing.TypeVar("_KT") #  key type
_VT = typing.TypeVar("_VT") #  value type


# `typing.MutableMapping` requires you to implement certain functions like __getitem__
# You can get around this by just subclassing OrderedDict first.
# Note: The generic you're subclassing needs to come BEFORE
# the `typing.MutableMapping` subclass or accessing indices won't work.

class ApplicationSpecificDict(
        OrderedDict, 
        typing.MutableMapping[_KT, _VT]
):
    """Your special dict"""
    ...

# Now define the key, value types for sub-types of your dict
RequestDict = MyDict[str, typing.Tuple[str, str]]
ModelDict = MyDict[str, typing.Any]

Теперь используйте пользовательские типы вашего подтипа dict:

from my_project.custom_typing import ApplicationSpecificDict #  Import your custom type

def make_request() -> ApplicationSpecificDict:
    request = ApplicationSpecificDict()
    request["test"] = ("sierra", "117")
    return request

print(make_request())

Будет выводиться как { "test": ("sierra", "117") }

person Alex    schedule 08.10.2020

Короче говоря, предположим, что у вас есть базовый класс MyDictBase. Чтобы добавить подсказки типа, выполните MyDictTyped = MyDictBase[str, int]

E.g.

class MyDictBase(dict):
    pass

MyDictTyped = MyDictBase[str, int]
person tejasvi88    schedule 18.07.2021