2011-06-10 39 views
15

考虑以下代码:Python:在您定义函数__setattr__的方式中不一致?

class Foo1(dict): 
    def __getattr__(self, key): return self[key] 
    def __setattr__(self, key, value): self[key] = value 

class Foo2(dict): 
    __getattr__ = dict.__getitem__ 
    __setattr__ = dict.__setitem__ 

o1 = Foo1() 
o1.x = 42 
print(o1, o1.x) 

o2 = Foo2() 
o2.x = 42 
print(o2, o2.x) 

我希望相同的输出。然而,与CPython的2.5,2.6(类似3.2)我得到:

({'x': 42}, 42) 
({}, 42) 

随着PyPy 1.5.0,我得到预期的输出:

({'x': 42}, 42) 
({'x': 42}, 42) 

这是 “正确” 的输出? (或者应该是什么输出根据Python文档?)


Here是CPython的错误报告。

+1

有趣的谜题。我想知道CPython的行为与'dict .__ setitem__'是一个* slot wrapper *有什么关系。 – NPE 2011-06-10 11:05:43

回答

7

我怀疑它与查询优化做。源代码:

/* speed hack: we could use lookup_maybe, but that would resolve the 
     method fully for each attribute lookup for classes with 
     __getattr__, even when the attribute is present. So we use 
     _PyType_Lookup and create the method only when needed, with 
     call_attribute. */ 
    getattr = _PyType_Lookup(tp, getattr_str); 
    if (getattr == NULL) { 
     /* No __getattr__ hook: use a simpler dispatcher */ 
     tp->tp_getattro = slot_tp_getattro; 
     return slot_tp_getattro(self, name); 
    } 

快速路径不在类字典中查找它。

因此,获得所需功能的最佳方法是在类中放置覆盖方法。

class AttrDict(dict): 
    """A dictionary with attribute-style access. It maps attribute access to 
    the real dictionary. """ 
    def __init__(self, *args, **kwargs): 
     dict.__init__(self, *args, **kwargs) 

    def __repr__(self): 
     return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self)) 

    def __setitem__(self, key, value): 
     return super(AttrDict, self).__setitem__(key, value) 

    def __getitem__(self, name): 
     return super(AttrDict, self).__getitem__(name) 

    def __delitem__(self, name): 
     return super(AttrDict, self).__delitem__(name) 

    __getattr__ = __getitem__ 
    __setattr__ = __setitem__ 

    def copy(self): 
     return AttrDict(self) 

我发现我的预计工作。

3

这是一个已知的(也许不是很好)记录的差异。 PyPy不区分函数和内置函数。在CPython函数中,当存储在类中时,绑定为未绑定的方法(has __get__),而内置函数不会(它们不同)。

然而,在PyPy下,内建函数与python函数完全相同,因此解释器无法区分它们并将它们视为python级函数。我认为这被定义为实现细节,虽然有一些关于python-dev的讨论关于消除这种特殊的差异。

干杯,
fijal

1

注意以下几点:

>>> dict.__getitem__ # it's a 'method' 
<method '__getitem__' of 'dict' objects> 
>>> dict.__setitem__ # it's a 'slot wrapper' 
<slot wrapper '__setitem__' of 'dict' objects> 

>>> id(dict.__dict__['__getitem__']) == id(dict.__getitem__) # no bounding here 
True 
>>> id(dict.__dict__['__setitem__']) == id(dict.__setitem__) # or here either 
True 

>>> d = {} 
>>> dict.__setitem__(d, 1, 2) # can be called directly (since not bound) 
>>> dict.__getitem__(d, 1) # same with this 
2 

现在我们可以只是包装他们(__getattr__将工作即使没有):

class Foo1(dict): 
    def __getattr__(self, key): return self[key] 
    def __setattr__(self, key, value): self[key] = value 

class Foo2(dict): 
    """ 
    It seems, 'slot wrappers' are not bound when present in the __dict__ 
    of a class and retrieved from it via instance (or class either). 
    But 'methods' are, hence simple assignment works with __setitem__ 
    in your original example. 
    """ 
    __setattr__ = lambda *args: dict.__setitem__(*args) 
    __getattr__ = lambda *args: dict.__getitem__(*args) # for uniformity, or 
    #__getattr__ = dict.__getitem__      # this way, i.e. directly 


o1 = Foo1() 
o1.x = 42 
print(o1, o1.x) 

o2 = Foo2() 
o2.x = 42 
print(o2, o2.x) 

其中给出:

>>> 
({'x': 42}, 42) 
({'x': 42}, 42) 

机制在Python的'干净的'子集之外的问题背后(可能我不是专家)在“学习Python”或“简而言之Python”这样的详尽书籍中有所赘述,并且在python.org中有些松散地指定),并且与该实现记录的“按原样”记录的语言部分有关(并受(相当)频繁的变化)。

相关问题