2012-08-09 63 views
15

我在Python 2.7中有一个自定义容器类,并且一切按预期工作,除了如果我通过尝试扩展实例为**kwargs功能:使自定义容器与** kwargs一起工作(Python如何扩展参数?)

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__iterkeyskeysitemsiteritems,(和__eq____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 
+0

尝试使用调试器(或在每个重载函数中写入'print'语句)来查看在参数扩展时调用哪个函数。 – Lanaru 2012-08-09 19:03:02

+0

请注意,即使这样做,check_args会得到一个* new *字典,而不是您的子类。参见[函数定义文档](http://docs.python.org/reference/compound_stmts.html#function-definitions);特别是*“如果表单”** identifier“存在,它将被初始化为一个新的字典,接收任何多余的关键字参数,默认为一个新的空字典。”*。 – 2012-08-09 19:05:37

+0

@Lanaru放入一个'import pdb; pdb.set_trace()'在调用'check_kwargs'之前立即执行,并且执行一个单独的步骤让我超过了这个点。在每个重写的函数中放入相同的'set_trace'表明它们都没有被调用。 – grncdr 2012-08-09 19:06:59

回答

9

创建一个关键字参数字典时,该行为是一样的通过你的对象为dict()初始化,它结果在字典{'b': 2}cm对象:

>>> cm = ChainableMap({'a': 1}) 
>>> cm['b'] = 2 
>>> dict(cm) 
{'b': 2} 

A的为什么是这样的情况下,更详细的说明如下,但总结是,你的映射是CONV如果参数本身是另一个字典,则绕过Python函数调用并直接检查底层的C对象,从而在C代码中使用Python字典进行一些优化。

有接近这种情况的解决方案,无论是确保标的字典包含了你想要的一切,或停止从字典继承(这将需要其他的变化,以及,至少是一个__setitem__法)的几种方法。

编辑:这听起来像BrenBarn's suggestion为继承collections.MutableMapping而不是dict的伎俩。

您可以简单地通过将self.update(parent)添加到ChainableMap.__init__()来完成第一种方法,但我不确定这是否会对您班级的行为造成其他副作用。

退房的字典对象以下CPython的代码:为什么dict(cm){'b': 2}

说明
http://hg.python.org/releasing/2.7.3/file/7bb96963d067/Objects/dictobject.c#l1522

dict(cm)被调用(当关键字参数解包),该PyDict_Merge函数调用cm作为b参数。由于ChainableMap从字典继承,则进入if语句在行1539:

if (PyDict_Check(b)) { 
    other = (PyDictObject*)b; 
    ... 

从那里,从other项添加到正在通过直接访问C对象,它绕过所有新创建的字典你重写的方法。

这意味着,通过parent属性访问的ChainableMap实例中的任何项都不会被添加到由dict()创建的新字典或关键字参数解包中。

+0

不幸的是,其他副作用对我的用例来说是不能接受的。我已经更改为从'collections.MutableMapping'继承,并解决了我的问题。 – grncdr 2012-08-09 19:29:39

+0

@grncdr:即使从'collections.MutableMapping'继承,你仍然应该提供'__len __()',这是你的示例代码中缺少的。 – 2012-08-09 19:35:56

+0

@FJ:关键字参数解包实际上不会在**之后的参数上调用'dict()',而是使用新创建的字典和映射调用'PyDict_Update()',但是这个调用也会在'PyDictMerge()',所以它基本如你所说。 – 2012-08-09 19:37:43

相关问题