如何“完美"覆盖一个字典?

2022-01-17 00:00:00 python dictionary inheritance set get

问题描述

如何使 dict 的子类尽可能完美"?最终目标是拥有一个简单的 dict,其中的键是小写的.

How can I make as "perfect" a subclass of dict as possible? The end goal is to have a simple dict in which the keys are lowercase.

似乎应该有一些我可以重写的小原语来完成这项工作,但根据我所有的研究和尝试,情况似乎并非如此:

It would seem that there should be some tiny set of primitives I can override to make this work, but according to all my research and attempts it seem like this isn't the case:

  • 如果我覆盖__getitem__/__setitem__,那么get/set 不起作用.我怎样才能让它们工作?当然我不需要单独实现它们?

  • If I override __getitem__/__setitem__, then get/set don't work. How can I make them work? Surely I don't need to implement them individually?

我是否在阻止酸洗工作,是否需要实现 __setstate__ 等?

Am I preventing pickling from working, and do I need to implement __setstate__ etc?

我是否需要reprupdate__init__?

我应该只使用可变映射(似乎不应该使用UserDictDictMixin)?如果是这样,怎么做?这些文档并不完全有启发性.

Should I just use mutablemapping (it seems one shouldn't use UserDict or DictMixin)? If so, how? The docs aren't exactly enlightening.

这是我第一次尝试,get() 不起作用,毫无疑问还有许多其他小问题:

Here is my first go at it, get() doesn't work and no doubt there are many other minor problems:

class arbitrary_dict(dict):
    """A dictionary that applies an arbitrary key-altering function
       before accessing the keys."""

    def __keytransform__(self, key):
        return key

    # Overridden methods. List from 
    # https://stackoverflow.com/questions/2390827/how-to-properly-subclass-dict

    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    # Note: I'm using dict directly, since super(dict, self) doesn't work.
    # I'm not sure why, perhaps dict is not a new-style class.

    def __getitem__(self, key):
        return dict.__getitem__(self, self.__keytransform__(key))

    def __setitem__(self, key, value):
        return dict.__setitem__(self, self.__keytransform__(key), value)

    def __delitem__(self, key):
        return dict.__delitem__(self, self.__keytransform__(key))

    def __contains__(self, key):
        return dict.__contains__(self, self.__keytransform__(key))


class lcdict(arbitrary_dict):
    def __keytransform__(self, key):
        return str(key).lower()


解决方案

您可以使用 ABCs(抽象基类)来自 collections.abc 模块.它甚至会告诉您是否错过了某个方法,因此以下是关闭 ABC 的最小版本.

You can write an object that behaves like a dict quite easily with ABCs (Abstract Base Classes) from the collections.abc module. It even tells you if you missed a method, so below is the minimal version that shuts the ABC up.

from collections.abc import MutableMapping


class TransformedDict(MutableMapping):
    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

    def __getitem__(self, key):
        return self.store[self._keytransform(key)]

    def __setitem__(self, key, value):
        self.store[self._keytransform(key)] = value

    def __delitem__(self, key):
        del self.store[self._keytransform(key)]

    def __iter__(self):
        return iter(self.store)
    
    def __len__(self):
        return len(self.store)

    def _keytransform(self, key):
        return key

您可以从 ABC 获得一些免费方法:

You get a few free methods from the ABC:

class MyTransformedDict(TransformedDict):

    def _keytransform(self, key):
        return key.lower()


s = MyTransformedDict([('Test', 'test')])

assert s.get('TEST') is s['test']   # free get
assert 'TeSt' in s                  # free __contains__
                                    # free setdefault, __eq__, and so on

import pickle
# works too since we just use a normal dict
assert pickle.loads(pickle.dumps(s)) == s

我不会直接继承 dict (或其他内置函数).这通常是没有意义的,因为你真正想做的是实现一个dict的接口.这正是 ABC 的用途.

I wouldn't subclass dict (or other builtins) directly. It often makes no sense, because what you actually want to do is implement the interface of a dict. And that is exactly what ABCs are for.

相关文章