Python覆盖__getattr__用于嵌套赋值,但不用于引用?

时间:2011-02-08 00:42:20

标签: python

以下是我正在寻找的行为:

>>> o = SomeClass()
>>> # Works: 
>>> o.foo.bar = 'bar' 
>>> print o.foo.bar
'bar'
>>> # The in-between object would be of type SomeClass as well:
>>> print o.foo 
>>> <__main__.SomeClass object at 0x7fea2f0ef810>

>>> # I want referencing an unassigned attribute to fail: 
>>> print o.baz
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
    print o.baz
AttributeError: 'SomeClass' object has no attribute 'baz'

换句话说,我希望以类似于defaultdict的方式覆盖__getattr____setattr__(以及可能__getattribute__),允许分配任意属性,但如果是属性只是被引用但未分配给它,它会像往常一样抛出AttributeError。

这可能吗?

6 个答案:

答案 0 :(得分:6)

这在Python中是不可能的。

你要问的是:

>>> o = SomeClass()
>>> o.foo.bar = 'bar' 
>>> print o.foo.bar
'bar'
>>> a = o.baz
raises AttributeError

这不可能。没有办法区分

>>> o.foo.bar = 'bar' 

>>> temp = o.foo
>>> temp.bar = 'bar' 

它们在逻辑上是等价的,并且在两种情况下Python都在做同样的事情。你无法区分它们以便在后一种情况下引发异常而不是前者。

答案 1 :(得分:2)

怎么样:

class AutoVivifier(object):
    def __getattr__(self, key):
        value = type(self)()
        object.__setattr__(self,key,value)
        return value

o=AutoVivifier()
o.foo.bar='baz'
print(o.foo.bar)
# baz
print(o.foo.baz)
# <__main__.AutoVivifier object at 0xb776bb0c>
o.foo.baz='bing'
print(o.foo.baz)
# bing

这不会引发任何AttributeErrors,但很容易判断属性链何时没有先前赋值 - 表达式将是Autovivifier的实例。也就是说,isinstance(o.foo.baz,AutoVivifier)为True。

我认为实现更加清晰,而不是定义各种特殊方法,如__str____eq__来提升AttributeErrors。

我仍然不清楚为什么你需要首先引发AttributeErrors,但也许使用AutoVivifier你可以编写实现目标的函数或方法,isinstance(...,AutoVivifier)测试替换try...except AttributeError

答案 2 :(得分:2)

我不确定你的意思。语言功能已经允许您这样做:

>>> class MyClass(object):
...     pass
...
>>> f = MyClass()
>>> f.foo = 5
>>> print f.foo
5
>>> f.baz
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'baz'
>>>

答案 3 :(得分:0)

[~/.src/pyusb-1.0.0-a1]
|4>class SomeClass: pass
   ...: 

[~/.src/pyusb-1.0.0-a1]
|5>o = SomeClass()

[~/.src/pyusb-1.0.0-a1]
|6>o.foo='bar'

[~/.src/pyusb-1.0.0-a1]
|7>print o.foo
bar

[~/.src/pyusb-1.0.0-a1]
|8>print o.baz
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)

AttributeError: SomeClass instance has no attribute 'baz'

[~/.src/pyusb-1.0.0-a1]
|9>

答案 4 :(得分:0)

这真的很讨厌,但也许是你想要的开始:

class SomeClass(object):
    def __init__(self):
        object.__setattr__(self, "_SomeClass__children", {})
        object.__setattr__(self, "_SomeClass__empty", True)

    def __getattr__(self, k):
        if k not in self.__children:
            self.__children[k] = SomeClass()
        return self.__children[k]

    def __setattr__(self, k, v):
        object.__setattr__(self, "_SomeClass__empty", False)
        object.__setattr__(self, k, v)

    def __str__(self):
        if not self.__hasvalue():
            raise AttributeError("Never truly existed")
        return object.__str__(self)

    def __hasvalue(self):
        if not self.__empty:
            return True
        return any(v.__hasvalue() for v in self.__children.itervalues())

o = SomeClass()
o.foo.bar = 'bar'
print o.foo.bar
print o.foo
print o.baz

输出:

bar
<__main__.SomeClass object at 0x7f2431404c90>
Traceback (most recent call last):
  File "spam.py", line 29, in <module>
    print o.baz
  File "spam.py", line 17, in __str__
    raise AttributeError("Never truly existed")
AttributeError: Never truly existed

答案 5 :(得分:0)

这是我到目前为止所得到的:

def raise_wrapper(wrapped_method=None):
    def method(tmp_instance, *args, **kawrgs):
        raise AttributeError("'%s' object has no attribute '%s'" % (
                type(tmp_instance._parent).__name__, tmp_instance._key))
    if wrapped_method:
        method.__doc__ = wrapped_method.__doc__
    return method


class TemporaryValue(object):
    def __init__(self, parent, key):
        self._parent = parent
        self._key = key

    def __setattr__(self, key, value):
        if key in ('_parent', '_key'):
            return object.__setattr__(self, key, value)

        newval = ObjectLike()
        object.__setattr__(self._parent, self._key, newval)
        return object.__setattr__(newval, key, value)

    __eq__ = raise_wrapper(object.__eq__)
    # __del__ = raise_wrapper()
    # __repr__ = raise_wrapper(object.__repr__)
    __str__ = raise_wrapper(object.__str__)
    __lt__ = raise_wrapper(object.__lt__)
    __le__ = raise_wrapper(object.__le__)
    __eq__ = raise_wrapper(object.__eq__)
    __ne__ = raise_wrapper(object.__ne__)
    __cmp__ = raise_wrapper()
    __hash__ = raise_wrapper(object.__hash__)
    __nonzero__ = raise_wrapper()
    __unicode__ = raise_wrapper()
    __delattr__ = raise_wrapper(object.__delattr__)
    __call__ = raise_wrapper(object.__call__)


class ObjectLike(object):
    def __init__(self):
        pass

    def __getattr__(self, key):
        newtmp = TemporaryValue(self, key)
        object.__setattr__(self, key, newtmp)
        return newtmp

    def __str__(self):
        return str(self.__dict__)


o = ObjectLike()
o.foo.bar = 'baz'
print o.foo.bar
print o.not_set_yet
print o.some_function()
if o.unset > 3: 
    print "yes" 
else:
    print "no"