Search code examples
pythondictionaryinheritancenew-operatorsuper

Identical **kwargs, but one raising "cannot convert dictionary update sequence element #0 to a sequence"


I have a very weird issue with dict inheritance. I want to be able to pass custom objects in an instance of the dict object, so I made a custom __new__ method to format these objects so they are compatible with the dict.__new__ method.

But I am facing a very strange issue. I already read most of the topics talking about this error one stackoverflow, but none are going as far as my code is. Here is my code:

class TextureItem:
    def __init__(self, name: str = "stone") -> None:
        self.name = name

    def __repr__(self) -> str:
        return f'TextureItem({self.name})'

class GlobalPalette(dict[str, TextureItem]):
    def __new__(cls, *args, **kwargs):
        new_kwargs = {}
        print("args and kwargs:", args, kwargs, "\n")
        for arg in args:
            if isinstance(arg, list):
                for item in arg:
                    new_kwargs[item.name] = item
            elif isinstance(arg, dict):
                new_kwargs.update(arg)
            elif isinstance(arg, TextureItem):
                new_kwargs[arg.name] = arg
        if kwargs:
            new_kwargs.update(kwargs)
        print("new_kwargs:", new_kwargs, "\n")
        return super().__new__(cls, **new_kwargs)

a = GlobalPalette(stone=TextureItem(), v="OK")
print("a:", a, "\n\n")

b = GlobalPalette([TextureItem()], v="OK")
print("b:", b)

This is what I get when running the code:

args and kwargs: () {'stone': TextureItem(stone), 'v': 'OK'}
new_kwargs: {'stone': TextureItem(stone), 'v': 'OK'} 

a: {'stone': TextureItem(stone), 'v': 'OK'} 

args and kwargs: ([TextureItem(stone)],) {'v': 'OK'}
new_kwargs: {'stone': TextureItem(stone), 'v': 'OK'} 

Traceback (most recent call last):
  File "/Users/n/ainbt/t.py", line 28, in <module>
    b = GlobalPalette([TextureItem()], v="OK")
TypeError: cannot convert dictionary update sequence element #0 to a sequence

The error at the end means I filled an iterable like a list, tuple or set, that contains only "single" objects. Example: [("a": 1), ("b": 2)] will be ok, but ["a", "b"] will raise this error.

This means it comes from the list [TextureItem()] in b = GlobalPalette([TextureItem()], v="OK"). But why is it detecting it, my own __new__ should be executed before the super().__new__.

Where am I going wrong ? Thanks


Solution

  • You cannot use __new__ to set dictionary items; that can only be done in the __init__ method. That is, consider the following example:

    >>> dict.__new__(dict, v="OK")
    {}
    

    That is exactly what your code is doing.

    I think you want to override __init__, not __new__, like this:

    class TextureItem:
        def __init__(self, name: str = "stone") -> None:
            self.name = name
    
        def __repr__(self) -> str:
            return f'TextureItem({self.name})'
    
    class GlobalPalette(dict[str, TextureItem]):
        def __new__(cls, *args, **kwargs):
            new_kwargs = {}
            print("args and kwargs:", args, kwargs, "\n")
            for arg in args:
                if isinstance(arg, list):
                    for item in arg:
                        new_kwargs[item.name] = item
                elif isinstance(arg, dict):
                    new_kwargs.update(arg)
                elif isinstance(arg, TextureItem):
                    new_kwargs[arg.name] = arg
            if kwargs:
                new_kwargs.update(kwargs)
            print("new_kwargs:", new_kwargs, "\n")
            return super().__new__(cls, **new_kwargs)
    
    a = GlobalPalette(stone=TextureItem(), v="OK")
    print("a:", a, "\n\n")
    
    b = GlobalPalette([TextureItem()], v="OK")
    print("b:", b)
    

    Running this results in:

    args and kwargs: () {'stone': TextureItem(stone), 'v': 'OK'} 
    
    new_kwargs: {'stone': TextureItem(stone), 'v': 'OK'} 
    
    a: {'stone': TextureItem(stone), 'v': 'OK'} 
    
    
    args and kwargs: ([TextureItem(stone)],) {'v': 'OK'} 
    
    new_kwargs: {'stone': TextureItem(stone), 'v': 'OK'} 
    
    b: {'stone': TextureItem(stone), 'v': 'OK'}