Python重写__getattr__以支持嵌套赋值,但不支持引用?

0 投票
6 回答
1566 浏览
提问于 2025-04-16 11:25

我想要的行为是这样的:

>>> 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'

换句话说,我想要重写 __getattr____setattr__(可能还包括 __getattribute__),让它们的工作方式类似于 defaultdict。也就是说,我希望可以给任意属性赋值,但如果只是引用了某个属性而没有赋值,就应该像平常一样抛出一个 AttributeError 错误。

这可能吗?

6 个回答

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'
>>>
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在这两种情况下做的事情是一样的。你无法区分它们,因此不能在后者的情况下抛出异常,而在前者的情况下不抛出。

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" 

撰写回答