我在 Python 2.7 中有一个自定义容器类,一切都按预期工作,除非我通过尝试将实例扩展**kwargs
为函数:
cm = ChainableMap({'a': 1})
cm['b'] = 2
assert cm == {'a': 1, 'b': 2} # Is fine
def check_kwargs(**kwargs):
assert kwargs == {'a': 1, 'b': 2}
check_kwargs(**cm) # Raises AssertionError
我已经覆盖了__getitem__
, __iter__
, iterkeys
, keys
, items
, and iteritems
, (和__eq__
and __repr__
)但它们似乎都没有参与扩展**kwargs
,我做错了什么?
编辑 - 现在从 MutableMapping 继承并添加缺少的方法的工作更新源:
from itertools import chain
from collections import MutableMapping
class ChainableMap(MutableMapping):
"""
A mapping object with a delegation chain similar to JS object prototypes::
>>> parent = {'a': 1}
>>> child = ChainableMap(parent)
>>> child.parent is parent
True
Failed lookups delegate up the chain to self.parent::
>>> 'a' in child
True
>>> child['a']
1
But modifications will only affect the child::
>>> child['b'] = 2
>>> child.keys()
['a', 'b']
>>> parent.keys()
['a']
>>> child['a'] = 10
>>> parent['a']
1
Changes in the parent are also reflected in the child::
>>> parent['c'] = 3
>>> sorted(child.keys())
['a', 'b', 'c']
>>> expect = {'a': 10, 'b': 2, 'c': 3}
>>> assert child == expect, "%s != %s" % (child, expect)
Unless the child is already masking out a certain key::
>>> del parent['a']
>>> parent.keys()
['c']
>>> assert child == expect, "%s != %s" % (child, expect)
However, this doesn't work::
>>> def print_sorted(**kwargs):
... for k in sorted(kwargs.keys()):
... print "%r=%r" % (k, kwargs[k])
>>> child['c'] == 3
True
>>> print_sorted(**child)
'a'=10
'b'=2
'c'=3
"""
__slots__ = ('_', 'parent')
def __init__(self, parent, **data):
self.parent = parent
self._ = data
def __getitem__(self, key):
try:
return self._[key]
except KeyError:
return self.parent[key]
def __iter__(self):
return self.iterkeys()
def __setitem__(self, key, val):
self._[key] = val
def __delitem__(self, key):
del self._[key]
def __len__(self):
return len(self.keys())
def keys(self, own=False):
return list(self.iterkeys(own))
def items(self, own=False):
return list(self.iteritems(own))
def iterkeys(self, own=False):
if own:
for k in self._.iterkeys():
yield k
return
yielded = set([])
for k in chain(self.parent.iterkeys(), self._.iterkeys()):
if k in yielded:
continue
yield k
yielded.add(k)
def iteritems(self, own=False):
for k in self.iterkeys(own):
yield k, self[k]
def __eq__(self, other):
return sorted(self.iteritems()) == sorted(other.iteritems())
def __repr__(self):
return dict(self.iteritems()).__repr__()
def __contains__(self, key):
return key in self._ or key in self.parent
def containing(self, key):
"""
Return the ancestor that directly contains ``key``
>>> p2 = {'a', 2}
>>> p1 = ChainableMap(p2)
>>> c = ChainableMap(p1)
>>> c.containing('a') is p2
True
"""
if key in self._:
return self
elif hasattr(self.parent, 'containing'):
return self.parent.containing(key)
elif key in self.parent:
return self.parent
def get(self, key, default=None):
"""
>>> c = ChainableMap({'a': 1})
>>> c.get('a')
1
>>> c.get('b', 'default')
'default'
"""
if key in self:
return self[key]
else:
return default
def pushdown(self, top):
"""
Pushes a new mapping onto the top of the delegation chain:
>>> parent = {'a': 10}
>>> child = ChainableMap(parent)
>>> top = {'a': 'apple', 'b': 'beer', 'c': 'cheese'}
>>> child.pushdown(top)
>>> assert child == top
This creates a new ChainableMap with the contents of ``child`` and makes it
the new parent (the old parent becomes the grandparent):
>>> child.parent.parent is parent
True
>>> del child['a']
>>> child['a'] == 10
True
"""
old = ChainableMap(self.parent)
for k, v in self.items(True):
old[k] = v
del self[k]
self.parent = old
for k, v in top.iteritems():
self[k] = v