Python recursively __getattribute__

Roman Dolgiy tosters at gmail.com
Tue Nov 23 09:54:34 EST 2010


Thanks to Andreas Waldenburger, THC4k (http://stackoverflow.com/
questions/4247036/python-recursively-getattribute) and others for
their tips. I was able to find solution:

class Null(object):
    def __repr__(self):
        return "<Null>"

    def __str__(self):
        return ''

    def __nonzero__(self):
        return False


class ResultAttrFactory(type):
    _cache = {}

    @classmethod
    def prepare(cls, base, result):
        dict_ = ResultAttr.__dict__.copy()
        dict_.update({
                '_ResultAttr__base': base,
                '_ResultAttr__result': result})
        return ('ResultAttr', (base,), dict_)

    def __new__(cls, base, result):
        if (base, result) in cls._cache:
            type_ = cls._cache[(base, result)]
        else:
            type_ = super(ResultAttrFactory, cls).__new__(
                cls, *cls.prepare(base, result))
            cls._cache[(base, result)] = type_
        return type_

    def __init__(cls, base, result):
        pass


class ResultAttr:
    """Should be used only with ResultAttrFactory"""
    @staticmethod
    def __new__(cls, arg1, name):
        return cls.__base.__new__(cls, arg1)

    def __init__(self, arg1, name):
        self.__name = name
        super(self.__class__, self).__init__(arg1)

    def get_result_attr(self, name):
        if self.__result.is_denorm_attr(name):
            attr = getattr(self.__result, name, None)
        else:
            attr = getattr(self.__result, name)
        return attr

    def __getattr__(self, name):
        lookup_name = "%s__%s" % (self.__name, name)
        attr = self.get_result_attr(lookup_name)
        if type(attr).__name__ == 'ResultAttr':
            type_ = attr.__base
        elif attr is None:
            type_ = Null
        else:
            type_ = type(attr)
        result_attr = ResultAttrFactory(
            type_, self.__result)(attr, lookup_name)
        return result_attr


class BaseResult(object):
    """
    >>> class Result(BaseResult):
    ...     def __init__(self, *args, **kwargs):
    ...         self.x = 35
    ...         self.y = 5
    ...         self.y__a = 3
    ...         self.y__b = 'hello'
    ...         self.y__c__x = [1, 2, 3]
    ...         super(Result, self, *args, **kwargs)
    >>> r = Result()
    >>> r.x
    35
    >>> type(r.x)
    <type 'int'>
    >>> r.y
    5
    >>> type(r.y)  # doctest:+ELLIPSIS
    <class '....ResultAttr'>
    >>> r.y.a
    3
    >>> r.y.b
    'hello'
    >>> r.y.c  # Is there any way to raise AtrributeError here?
    <Null>
    >>> r.y.d
    Traceback (most recent call last):
    AttributeError: 'Result' object has no attribute 'y__d'
    >>> r.y.c.x
    [1, 2, 3]
    """
    def is_denorm_attr(self, name):
        return bool([k for k in self.__dict__.keys() if "%s__" % name
in k])

    def __getattribute__(self, name):
        attr = super(BaseResult, self).__getattribute__(name)
        if name in ('__dict__', 'is_denorm_attr'):
            return attr
        if self.is_denorm_attr(name):
            return ResultAttrFactory(type(attr), self)(attr, name)
        else:
            return attr


if __name__ == '__main__':
    import doctest
    doctest.testmod()

https://gist.github.com/710977

The only problem is:

>>> r.y.c
<Null>

Is there any way to raise AtrributeError here?

Also is there any negative side effects of such method?



More information about the Python-list mailing list