Python Forum
Need explanation on this defaultdict code - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: General Coding Help (https://python-forum.io/forum-8.html)
+--- Thread: Need explanation on this defaultdict code (/thread-4740.html)



Need explanation on this defaultdict code - jaden5165 - Sep-06-2017

Hi,

I am reading legacy code but I could not understand the below code line why need to do so. Can someone pls explain to me. Thanks.

    def __init__(self, initdict=None):
        defaultdict.__setattr__(self, "_configentry", set())
        defaultdict.__init__(self, initdict)
The full source code is like this:
class DictConfig(defaultdict):
  
   
    def __init__(self, initdict=None):
        defaultdict.__setattr__(self, "_configentry", set())
        defaultdict.__init__(self, initdict)

    def AUTO_OFF(self):
        """Disable autovivification - deep"""
        defaultdict.__setattr__(self, "default_factory", None)
        for value in self.itervalues():
            tt = type(value)
            if tt is DictConfig:
                value.AUTO_OFF()

    def __getattr__(self, item):
        #        if item=="__setstate__":
        #            defaultdict.__getattr__(self, item)
        return self[item]

    def OVERRIDE(self, item, value):
        """Given a key and value, override it. Useful for unittests"""
        self.__setitem__(item, value)

    def __setattr__(self, item, value):
        cfgentry = self._configentry
        if item in cfgentry:
            if self[item] is value:
                return  # Do nothing, it is the same
            ErrMsg = "Key [" + item + "] already has value " + \
                     repr(repr(self[item]).rstrip().split('\n')) + \
                     ". This can be a config error. There should be no duplicate during assignment!"
            raise KeyError(ErrMsg)

        cfgentry.add(item)
        self[item] = value

    def _recursedict(self):  # iterator
        """Iterator for __repr__, returns a string"""
        for item, value in self.iteritems():
            if isinstance(value, DictConfig):
                for nitem, nvalue in value._recursedict():
                    yield item + "." + nitem, nvalue
            else:
                yield item, value

    def __repr__(self):
        """Return the display, just like a dictionary display"""
        return repr(recurse2dict(self))

    def PRETTY(self):
        """
        Return the following string equivalent of dictionary:
           key1.key2.key3 = value1
           key4.key5.key6 = value2
           key7.key8 = value3
        """
        sorted_iter = sorted(self._recursedict(), key=lambda x: x[0])  # sort by keys (element 0)
        return '\n'.join(item + " = " + repr(value) for item, value in sorted_iter)

    def GETLEVEL(self, key):
        """Given a string key (separated by '.'), return the object with that heirarchy"""
        target = self
        for level in key.split('.'):
            if level in target:
                target = target[level]
            else:
                raise KeyError('key[%s] is not found' % key)
        return target

    def SAME_AS(self, target):
        """
        Recursively/make-a-deep-copy of target.
        This is also the proper way of doing a dictionary .update() for TVPVConfigDict().

        Cannot use simple copy.deep() since it will do infiniteloop on DictConfig.
        If you see an Exception 'TypeError: 'DictConfig' object is not callable',
        it means that the target does not exist.
        """
        for item, value in target.iteritems():
            tt = type(value)
            if tt in BUILTIN_TYPES_SIMPLE:
                self[item] = value
            elif tt is DictConfig:
                self[item].SAME_AS(value)
            elif tt is list:
                self[item] = list(value)  # make a copy
            else:
                self[item] = deepcopy(value)

    def merge(self, dict2, _d1=None):
        """
        combine/merge dict2 into self.
        dict2 will be modified
        """
        if _d1 is None:
            _d1 = self
        for item, value in _d1.items():
            if item in dict2:
                if isinstance(dict2[item], DictConfig):
                    _d1[item] = self.merge(dict2.pop(item), value)
            else:
                _d1[item] = value
        for item, value in dict2.items():
            _d1[item] = value
        return _d1


def ConfigDict():
    """
    Factory function used on TVPVConfigDict for autovivification
    """
    return DictConfig(ConfigDict)


def recurse2dict(inp):
    """
    Converts inp to a built-in dict type, for use in marshal.
    It will recursively traverse thru all values deeply.
    inp must be a dictionary-like type (collections.defaultdict, autovivication, DictDot, etc)
    For single level dictionary (e.g. defaultdict), use res=dict(<var>) directly.
      example:  res = recurse_to_dict(var)
    """
    try:
        res = dict(inp)
    except TypeError:
        return inp
    except ValueError:
        return inp

    for k, v in inp.iteritems():
        if not type(v) in BUILTIN_TYPES_ALL:
            res[k] = recurse2dict(v)
    return res


common = ConfigDict()
common.dir_tag = ''
common.debug = 'abcde'



RE: Need explanation on this defaultdict code - Mekire - Sep-06-2017

So, first off this seems really stupid, so let's get that out of the way.

Normally when you set an attribute on a class you write self.some_attrib = some_value:
class SomeClass(object):
    def __init__(self, value):
        self.attrib = value


instance = SomeClass(5)
print(instance.attrib)
Internally when you set the attribute like that the __setattr__ method of the class is called.  You can overload this to do other stuff if you have the need.
class SomeClass(object):
    def __init__(self, value):
        self.attrib = value

    def __setattr__(self, name, value):
        print("Now I can do nonsense here.")
        object.__setattr__(self, name, value)


instance = SomeClass(5)
print(instance.attrib)
In your example the writer has overloaded __setattr__, but in the __init__ he wants to set an attribute without calling his own custom __setattr__ function.  So instead he calls the __setattr__ on the base class instead.
class SomeClass(object):
    def __init__(self, value):
        object.__setattr__(self, "attrib", value)

    def __setattr__(self, name, value):
        print("I won't print now.  I'm such a tricky programmer")
        object.__setattr__(self, name, value)


instance = SomeClass(5)
print(instance.attrib)
Honestly I feel like there is probably a much better way to do this while avoiding the confusion, but you did say legacy code.


RE: Need explanation on this defaultdict code - jaden5165 - Sep-08-2017

Thank you Mekire for the explanation :)