Search code examples
pythonclassdictionarypylance

Pylance reportUnknownMemberType even though variable type is known


I'm using Pylance on VSCode and I'm getting this reportUnknownMemberType warning/error for this variable, even though I can see that it knows the type.

I'm pretty new to classes so if there's a better way to do this, please let me know. My aim is to extend a parent class dict with some more keys in a subclass. The program is actually working, the parent dict gets the new key, value pair, but I want to know why I'm getting the Pylance error.

The following is the inheritance chain for these classes, the names are simplified but the structure is real. There's 3 classes involved, inheriting like Base > SubClass > SubSubClass. The Base class is found in a separate file:

#base.py
     
class Base:
    def __init__(self, param0: str) -> None:
        self.param0 = param0
        self.exportable = dict[str, object]()        
 

The 2 other classes are found in a second file:

# subclasses.py

class SubClass(Base):
    def __init__(self, param0: str, param1: str, param2: str, 
                     param3: str, param4: str, 
                     param5: bool=True) -> None:
        super().__init__(param0)
    
        self.param1 = param1
        self.param2 = param2
        self.param3 = param3
        self.param4 = param4
        self.param5 = param5

        self.exportable = {
            self.param0: {
                'param1': self.param1,
                'param2': self.param2,
                'param3': self.param3,            
                'param4': self.param4,
                'param5': self.param5
            }
        }


class SubSubClass(SubClass):
    def __init__(self, param1: str, param3: str) -> None:        
        super().__init__(name='hardcodedparam0', param1=param1, type='hardcodedparam2', param3=param3, 
                param4='hardcodedparam4')

    self.properties = {
        'name': {
            'type': "string"
        },
        'contentBytes': {
            'type': 'string',
            'format': 'byte'
        }
    }

    # this is where I get the error
    new_exportable = self.exportable.copy()
    self.exportable = {**new_exportable, **self.properties}

My original plan was to just use self.exportable[self.name]['properties'] = self.properties instead of that dict merge, but I get this cannot assign error.

I also tried in SubSubClass to access SubClass's self.exportable with super().exportable, although I don't think it should be necessary, but when I run the program, I get an error saying that super() has no attribute 'exportable'. For what it's worth, this super() attempt also gives the same 'cannot assign' error from above when not running the program.

With the first option (the dict merge) and the second option (assigning the new properties key and value), the program works and self.properties dict is successfully tacked on to the inherited self.exportable dict. But I want to know if I'm doing something wrong anyway or if Pylance is just getting confused. I think it's confused because in SubClass it saw the dict was just dict[str,Union[str,bool]] but then SubClass B, instead of that Union[str,bool] value, tries to add the properties dict, which is a set of dict's itself?

Of course, I can just silence the reportUnkownMemberType error in the Pylance config, but I'm worried I'm masking something I don't know about.

Thanks


Solution

  • I think I may have solved this by being a little more flexible with SubClass's exportable. That is, creating an empty dict, tacking on the initial properties, and in SubSubClass tacking on any additions:

    # subclasses.py
    
    class SubClass(Base):
        def __init__(self, param0: str, param1: str, param2: str, 
                         param3: str, param4: str, 
                         param5: bool=True) -> None:
            super().__init__(param0)
        
            self.param1 = param1
            self.param2 = param2
            self.param3 = param3
            self.param4 = param4
            self.param5 = param5
    
            # The following two lines are a bit different
            self.exportable = {}
            self.exportable[self.param0] = {
                'param1': self.param1,
                'param2': self.param2,
                'param3': self.param3,            
                'param4': self.param4,
                'param5': self.param5
            }
     
    
    
    class SubSubClass(SubClass):
        def __init__(self, param1: str, param3: str) -> None:        
            super().__init__(name='hardcodedparam0', param1=param1, type='hardcodedparam2', param3=param3, 
                    param4='hardcodedparam4')
            self.properties = {
                'name': {
                    'type': "string"
                },
                'contentBytes': {
                    'type': 'string',
                    'format': 'byte'
                }
            }
    
            # Now I can assign a key and value with no Pylance error
            self.exportable[self.name]['properties'] = self.properties
    

    The difference seems to be between

    • Setting the initial exportable dict as having the fixed key (param0) and value (subdict of param1, param2, param3, etc.)
    • Setting the initial exportable as an empty dict, assigning the param0 key and value, and then in SubSubClass just assigning a new key value

    I'm not sure if this is just a workaround, if I'm just managing to trick Pylance into not complaing, or if this is the actual solution. In any case, I no longer get the Pylance errors and the program (still) works.