dotmap icon indicating copy to clipboard operation
dotmap copied to clipboard

super().__init__ won't accept nested dictionaries when inheriting from DotMap

Open dkrako opened this issue 4 years ago • 2 comments

I am using DotMap as the baseclass of a custom Config-class. As a constructor-argument I only want to pass the path to a yaml-file which holds the config values.

This works quite well if I am just using plain dictionaries without nesting:

from dotmap import DotMap

class ConfigNotNested(DotMap):
    def __init__(self, filepath='config.yaml'):
        # load config-dict from yaml
        config_dict = {'a': 1, 'b': 2}
        super().__init__(config_dict)
print(ConfigNotNested())

Output:

ConfigNotNested(a=1, b=2)

But now with a nested dictionary this stops working:

class ConfigNested(DotMap):
    def __init__(self, filepath='config.yaml'):
        # load config-dict from yaml
        config_dict = {'a': 1, 'b': {'c': 3, 'd': 4}}
        super().__init__(config_dict)
print(ConfigNested())

Output:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-87a4fd97b248> in <module>
     11         example = {'a': 1, 'b': {'c': 3, 'd': 4}}
     12         super().__init__(example)
---> 13 print(ConfigNested())

<ipython-input-5-87a4fd97b248> in __init__(self)
     10     def __init__(self):
     11         example = {'a': 1, 'b': {'c': 3, 'd': 4}}
---> 12         super().__init__(example)
     13 print(ConfigNested())

venv/lib/python3.7/site-packages/dotmap/__init__.py in __init__(self, *args, **kwargs)
     46                     else:
     47                         trackedIDs[idv] = v
---> 48                         v = self.__class__(v, _dynamic=self._dynamic, _prevent_method_masking = self._prevent_method_masking, _trackedIDs = trackedIDs)
     49                 if type(v) is list:
     50                     l = []

TypeError: __init__() got an unexpected keyword argument '_dynamic'

Expected output:

ConfigNested(a=1, b=DotMap(c=3, d=4))

I think the problem lies in using self.__class__ instead of DotMap, as this will break on subclassing. Replacing all self.__class__ occurences with DotMap fixes the issue for me. I can create a pull request if you don't mind or else I can also implement this in a manner you find more appropriate.

dkrako avatar Jul 13 '21 23:07 dkrako

One possible workaround is adding those constructor arguments:

class ConfigNestedWorkaround(DotMap):
    def __init__(self, filepath='config.yaml', **kwargs):
        if type(filepath) in [dict, DotMap]:
            super().__init__(filepath, **kwargs)
            return
        # load config-dict from yaml
        config_dict = {'a': 1, 'b': {'c': 3, 'd': 4}}
        super().__init__(config_dict)
print(ConfigNestedWorkaround())

Output

ConfigNestedWorkaround(a=1, b=ConfigNestedWorkaround(c=3, d=4))

dkrako avatar Jul 14 '21 00:07 dkrako

Hey @cornicis. For sure, I'd be happy to merge a PR that is passing tests and creates a new test for your new feature.

drgrib avatar Jul 14 '21 00:07 drgrib