python __getattribute__ override and @property dec

2020-03-31 07:17发布

问题:

I had to write a class of some sort that overrides __getattribute__. basically my class is a container, which saves every user-added property to self._meta which is a dictionary.

class Container(object):
    def __init__(self, **kwargs):
        super(Container, self).__setattr__('_meta', OrderedDict())
        #self._meta = OrderedDict()
        super(Container, self).__setattr__('_hasattr', lambda key : key in self._meta)

        for attr, value in kwargs.iteritems():
            self._meta[attr] = value

    def __getattribute__(self, key):
        try:
            return super(Container, self).__getattribute__(key)
        except:
            if key in self._meta : return self._meta[key]
            else:
                raise AttributeError, key

    def __setattr__(self, key, value):
        self._meta[key] = value
#usage:
>>> a = Container()
>>> a
<__main__.Container object at 0x0000000002B2DA58>
>>> a.abc = 1 #set an attribute
>>> a._meta
OrderedDict([('abc', 1)]) #attribute is in ._meta dictionary

I have some classes which inherit Container base class and some of their methods have @property decorator.

class Response(Container):
    @property
    def rawtext(self):
        if self._hasattr("value") and self.value is not None:
            _raw = self.__repr__()
            _raw += "|%s" %(self.value.encode("utf-8"))

            return _raw

problem is that .rawtext isn't accessible. (I get attributeerror.) every key in ._meta is accessible, every attributes added by __setattr__ of object base class is accessible, but method-to-properties by @property decorator isn't. I think it has to do with my way of overriding __getattribute__ in Container base class. What should I do to make properties from @property accessible?

回答1:

I think you should probably think about looking at __getattr__ instead of __getattribute__ here. The difference is this: __getattribute__ is called inconditionally if it exists -- __getattr__ is only called if python can't find the attribute via other means.



回答2:

I completely agree with mgilson. If you want a sample code which should be equivalent to your code but work well with properties you can try:

class Container(object):
    def __init__(self, **kwargs):
        self._meta = OrderedDict()
        #self._hasattr = lambda key: key in self._meta  #???
        for attr, value in kwargs.iteritems():
            self._meta[attr] = value

    def __getattr__(self, key):
        try:
            return self._meta[key]
        except KeyError:
            raise AttributeError(key)

    def __setattr__(self, key, value):
        if key in ('_meta', '_hasattr'):
            super(Container, self).__setattr__(key, value)
        else:
            self._meta[key] = value

I really do not understand your _hasattr attribute. You put it as an attribute but it's actually a function that has access to self... shouldn't it be a method? Actually I think you should simple use the built-in function hasattr:

class Response(Container):
    @property
    def rawtext(self):
        if hasattr(self, 'value') and self.value is not None:
            _raw = self.__repr__()
            _raw += "|%s" %(self.value.encode("utf-8"))

            return _raw

Note that hasattr(container, attr) will return True also for _meta.

An other thing that puzzles me is why you use an OrderedDict. I mean, you iterate over kwargs, and the iteration has random order since it's a normal dict, and add the items in the OrderedDict. Now you have _meta which contains the values in random order. If you aren't sure whether you need to have a specific order or not, simply use dict and eventually swap to OrderedDict later.

By the way: never ever use an try: ... except: without specifying the exception to catch. In your code you actually wanted to catch only AttributeErrors so you should have done:

try:
    return super(Container, self).__getattribute__(key)
except AttributeError:
    #stuff