如何“完全”覆盖字典?

我怎样才能使“完美”的字典尽可能的子类? 最终目标是要有一个简单的字典 ,其中的键是小写字母。

似乎应该是我可以重写的一些微小的基本元素来完成这项工作,但是我所有的研究和尝试都使得它看起来并不是这样:

  • 如果我重写__getitem__ / __setitem__ ,那么get / set不起作用。 我如何使他们工作? 当然,我不需要单独实施?

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

  • 我需要reprupdate__init__

  • 我应该只使用mutablemapping (它似乎不应该使用UserDictDictMixin )? 如果是这样,怎么样? 文件不完全启发。

这是我第一次去, get()至less不起作用,毫无疑问,有许多微小的细微问题:

 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() 

你可以用集合模块中的ABC (抽象基类)很容易地编写一个类似字典的对象。 它甚至告诉你,如果你错过了一个方法,所以下面是closuresABC的最小版本。

 import collections class TransformedDict(collections.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获得一些免费的方法:

 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 assert pickle.loads(pickle.dumps(s)) == s # works too since we just use a normal dict 

我不会直接子类(或其他内置的)。 这往往是没有意义的,因为你真正想要做的是实现一个字典的接口 。 而这正是ABCs的。

我怎样才能使“完美”的字典尽可能的子类?

最终目标是要有一个简单的字典,其中的键是小写字母。

  • 如果我重写__getitem__ / __setitem__ ,那么get / set不起作用。 我如何使他们工作? 当然,我不需要单独实施?

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

  • 我需要repr,更新和__init__

  • 我应该只使用mutablemapping (它似乎不应该使用UserDictDictMixin )? 如果是这样,怎么样? 文件不完全启发。

被接受的答案将是我的第一个方法,但是由于它有一些问题,并且由于没有人提出了替代scheme,所以实际上划分了一个dict ,我打算在这里做。

接受的答案有什么问题?

这对我来说似乎是一个相当简单的要求:

我怎样才能使“完美”的字典尽可能的子类? 最终目标是要有一个简单的字典,其中的键是小写字母。

被接受的答案实际上并不是dict子类,而对此的testing失败:

 >>> isinstance(MyTransformedDict([('Test', 'test')]), dict) False 

理想情况下,任何types检查代码将testing我们期望的接口或抽象基类,但是如果我们的数据对象被传递到正在testingdict函数中 – 而且我们不能“修复”这些函数,代码将失败。

其他人可能会做的一些狡辩:

  • 接受的答案也是缺lessclassmethod: fromkeys
  • 被接受的答案也有冗余__dict__ – 因此占用更多的空间:

     >>> s.foo = 'bar' >>> s.__dict__ {'foo': 'bar', 'store': {'test': 'test'}} 

其实子类dict

我们可以通过inheritance来重用字典方法。 我们所需要做的就是创build一个接口层,确保密钥以小写forms传入字典(如果它们是string的话)。

如果我重写__getitem__ / __setitem__ ,那么get / set不起作用。 我如何使他们工作? 当然,我不需要单独实施?

那么,单独实现它们是这种方法的缺点,也是使用MutableMapping (参见接受的答案)的好处,但实际上并不是那么多的工作。

首先,我们将Python 2和3之间的区别_RaiseKeyError ,创build一个单例( _RaiseKeyError )以确保我们知道是否实际得到了dict.pop的参数,并创build一个函数来确保我们的string键是小写的:

 from itertools import chain try: # Python 2 str_base = basestring items = 'iteritems' except NameError: # Python 3 str_base = str, bytes, bytearray items = 'items' _RaiseKeyError = object() # singleton for no-default behavior def ensure_lower(maybe_str): """dict keys can be any hashable object - only call lower if str""" return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str 

现在我们实现 – 我使用super完整的参数,以便这个代码适用于Python 2和3:

 class LowerDict(dict): # dicts take a mapping or iterable as their optional first argument __slots__ = () # no __dict__ - that would be redundant @staticmethod # because this doesn't make sense as a global function. def _process_args(mapping=(), **kwargs): if hasattr(mapping, items): mapping = getattr(mapping, items)() return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)())) def __init__(self, mapping=(), **kwargs): super(LowerDict, self).__init__(self._process_args(mapping, **kwargs)) def __getitem__(self, k): return super(LowerDict, self).__getitem__(ensure_lower(k)) def __setitem__(self, k, v): return super(LowerDict, self).__setitem__(ensure_lower(k), v) def __delitem__(self, k): return super(LowerDict, self).__delitem__(ensure_lower(k)) def get(self, k, default=None): return super(LowerDict, self).get(ensure_lower(k), default) def setdefault(self, k, default=None): return super(LowerDict, self).setdefault(ensure_lower(k), default) def pop(self, k, v=_RaiseKeyError): if v is _RaiseKeyError: return super(LowerDict, self).pop(ensure_lower(k)) return super(LowerDict, self).pop(ensure_lower(k), v) def update(self, mapping=(), **kwargs): super(LowerDict, self).update(self._process_args(mapping, **kwargs)) def __contains__(self, k): return super(LowerDict, self).__contains__(ensure_lower(k)) def copy(self): # don't delegate w/ super - dict.copy() -> dict :( return type(self)(self) @classmethod def fromkeys(cls, keys, v=None): return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v) def __repr__(self): return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__()) 

对于任何引用键的方法或特殊方法,我们使用几乎锅炉板的方法,但除此之外,通过inheritance,我们可以获得方法: lenclearitemskeyspopitemvalues 。 虽然这需要一些仔细的想法才能得到正确的结果,但看到这一点很有效。

(请注意, haskey在Python 2中被弃用,在Python 3中被删除。)

这里有一些用法:

 >>> ld = LowerDict(dict(foo='bar')) >>> ld['FOO'] 'bar' >>> ld['foo'] 'bar' >>> ld.pop('FoO') 'bar' >>> ld.setdefault('Foo') >>> ld {'foo': None} >>> ld.get('Bar') >>> ld.setdefault('Bar') >>> ld {'bar': None, 'foo': None} >>> ld.popitem() ('bar', None) 

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

酸洗

而字典子类腌菜很好:

 >>> import pickle >>> pickle.dumps(ld) b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.' >>> pickle.loads(pickle.dumps(ld)) {'foo': None} >>> type(pickle.loads(pickle.dumps(ld))) <class '__main__.LowerDict'> 

__repr__

我需要repr,更新和__init__

我们定义了update__init__ ,但是默认情况下有一个漂亮的__repr__

 >>> ld # without __repr__ defined for the class, we get this {'foo': None} 

但是,编写__repr__来提高代码的可debugging性是很好的。 理想的testing是eval(repr(obj)) == obj 。 如果你的代码很容易做,我强烈build议:

 >>> ld = LowerDict({}) >>> eval(repr(ld)) == ld True >>> ld = LowerDict(dict(a=1, b=2, c=3)) >>> eval(repr(ld)) == ld True 

你看,正是我们需要重新创build一个等价的对象 – 这可能会出现在我们的日志或回溯:

 >>> ld LowerDict({'a': 1, 'c': 3, 'b': 2}) 

结论

我应该只使用mutablemapping (它似乎不应该使用UserDictDictMixin )? 如果是这样,怎么样? 文件不完全启发。

是的,这些是更多的代码行,但它们的目的是全面的。 我的第一个想法是使用被接受的答案,如果有问题,我会看看我的答案 – 因为它有点复杂,没有ABC来帮助我正确地使用我的界面。

不成熟的优化在search性能方面越来越复杂。 MutableMapping更简单 – 所以它获得了一个直接的边缘,其他的都是平等的。 尽pipe如此,为了展示所有的差异,我们来比较一下。

我应该补充说,有一个推动把类似的字典放入collections模块,但它被拒绝 。 你可能应该这样做,而不是:

 my_dict[transform(key)] 

它应该更容易debugging。

比较和对比

有6个接口函数使用MutableMapping (缺lessfromkeys )和11个dict子类实现。 我不需要实现__iter____len__ ,而是必须实现getsetdefaultpopupdatecopyfromkeysfromkeys – 但这些都是相当简单的,因为我可以在大多数实现中使用inheritance。

MutableMapping在Python中实现了一些dict实现的东西 – 所以我希望在某些情况下dict子类可以更MutableMapping

我们在这两种方法中都得到了一个免费的__eq__ ,只要其他字典都是小写字母,它们都假设它们是平等的,但是我认为dict子类会比较快。

概要:

  • 子类化MutableMapping更简单,错误的机会更less,但速度更慢,需要更多的内存(见多余的字典),并失败isinstance(x, dict)
  • 子类化dict更快,使用更less的内存,并通过isinstance(x, dict) ,但它实现更复杂。

哪个更完美? 这取决于你对完美的定义。

我的要求有点严格:

  • 我不得不保留大小写信息(string是显示给用户的文件的path,但它是一个Windows应用程序,所以内部所有的操作必须不区分大小写)
  • 我需要尽可能小的键(它在内存性能方面有所不同,从370分割出110mb)。 这意味着caching小写版本的密钥不是一个选项。
  • 我需要尽可能快地创build数据结构(这次再次在性能和速度上有所不同)。 我不得不与一个内置的

我最初的想法是将我们笨重的Path类replace为不区分大小写的unicode子类 – 但是:

  • certificate很难做到这一点 – 请参阅: 在Python中不区分大小写的string类
  • 事实certificate,明确的字典键处理使得代码冗长和混乱 – 而且容易出错(结构传递到那里,并且不清楚它们是否具有CIStr实例作为键/元素,容易忘记加上some_dict[CIstr(path)]是丑陋的)

所以我终于写下这个不区分大小写的字典。 感谢@AaronHall的代码 ,这个代码变得简单了10倍。

 class CIstr(unicode): """See https://stackoverflow.com/a/43122305/281545, especially for inlines""" __slots__ = () # does make a difference in memory performance #--Hash/Compare def __hash__(self): return hash(self.lower()) def __eq__(self, other): if isinstance(other, CIstr): return self.lower() == other.lower() return NotImplemented def __ne__(self, other): if isinstance(other, CIstr): return self.lower() != other.lower() return NotImplemented def __lt__(self, other): if isinstance(other, CIstr): return self.lower() < other.lower() return NotImplemented def __ge__(self, other): if isinstance(other, CIstr): return self.lower() >= other.lower() return NotImplemented def __gt__(self, other): if isinstance(other, CIstr): return self.lower() > other.lower() return NotImplemented def __le__(self, other): if isinstance(other, CIstr): return self.lower() <= other.lower() return NotImplemented #--repr def __repr__(self): return '{0}({1})'.format(type(self).__name__, super(CIstr, self).__repr__()) def _ci_str(maybe_str): """dict keys can be any hashable object - only call CIstr if str""" return CIstr(maybe_str) if isinstance(maybe_str, basestring) else maybe_str class LowerDict(dict): """Dictionary that transforms its keys to CIstr instances. Adapted from: https://stackoverflow.com/a/39375731/281545 """ __slots__ = () # no __dict__ - that would be redundant @staticmethod # because this doesn't make sense as a global function. def _process_args(mapping=(), **kwargs): if hasattr(mapping, 'iteritems'): mapping = getattr(mapping, 'iteritems')() return ((_ci_str(k), v) for k, v in chain(mapping, getattr(kwargs, 'iteritems')())) def __init__(self, mapping=(), **kwargs): # dicts take a mapping or iterable as their optional first argument super(LowerDict, self).__init__(self._process_args(mapping, **kwargs)) def __getitem__(self, k): return super(LowerDict, self).__getitem__(_ci_str(k)) def __setitem__(self, k, v): return super(LowerDict, self).__setitem__(_ci_str(k), v) def __delitem__(self, k): return super(LowerDict, self).__delitem__(_ci_str(k)) def copy(self): # don't delegate w/ super - dict.copy() -> dict :( return type(self)(self) def get(self, k, default=None): return super(LowerDict, self).get(_ci_str(k), default) def setdefault(self, k, default=None): return super(LowerDict, self).setdefault(_ci_str(k), default) __no_default = object() def pop(self, k, v=__no_default): if v is LowerDict.__no_default: # super will raise KeyError if no default and key does not exist return super(LowerDict, self).pop(_ci_str(k)) return super(LowerDict, self).pop(_ci_str(k), v) def update(self, mapping=(), **kwargs): super(LowerDict, self).update(self._process_args(mapping, **kwargs)) def __contains__(self, k): return super(LowerDict, self).__contains__(_ci_str(k)) @classmethod def fromkeys(cls, keys, v=None): return super(LowerDict, cls).fromkeys((_ci_str(k) for k in keys), v) def __repr__(self): return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__()) 

隐式还是显式仍然是一个问题,但是一旦尘埃落定,以ci开头的属性/variables的重命名(以及一个大胖子doc解释ci代表不区分大小写)我认为是一个完美的解决scheme – 因为代码的读者必须充分意识到我们正在处理不区分大小写的基础数据结构。 这将有希望修复一些难以重现的错误,我怀疑这个错误归结为区分大小写。

意见/更正欢迎:)

你所要做的就是

 class BatchCollection(dict): def __init__(self, *args, **kwargs): dict.__init__(*args, **kwargs) 

要么

 class BatchCollection(dict): def __init__(self, inpt={}): super(BatchCollection, self).__init__(inpt) 

我个人使用的示例用法

 ### EXAMPLE class BatchCollection(dict): def __init__(self, inpt={}): dict.__init__(*args, **kwargs) def __setitem__(self, key, item): if (isinstance(key, tuple) and len(key) == 2 and isinstance(item, collections.Iterable)): # self.__dict__[key] = item super(BatchCollection, self).__setitem__(key, item) else: raise Exception( "Valid key should be a tuple (database_name, table_name) " "and value should be iterable") 

:仅在python3中进行testing