Search code examples
pythonpython-typingpyright

Explanation of reportAttributeAccessIssue of pylance


I have following class

class ProcessRunner:

    def __init__(self, network: nn.Module) -> None:
        self.network: nn.Module = network

    def run(self, processes: List[Process]):
        for process in processes:
            process.network = self.network
            self.network = process.update()

Which shows in function run for self.network and process.update() following Errors:

Cannot assign member "network" for type "Process"
  Type "Module | None" cannot be assigned to type "Module"
    "None" is incompatible with "Module"PylancereportAttributeAccessIssue 

and

Cannot assign member "network" for type "ProcessRunner*"
  "None" is incompatible with "Module"PylancereportAttributeAccessIssue

I think I understand that those issues a are partial coming from my Protocol Class Process, which looks like this:

@runtime_checkable
class Process(Protocol):

    network: nn.Module

    def update(self, **kwargs):
        ...

But I do not understand completely what causes the issue here:

  1. The second issue might appear because, the update method doesn't return the network here. Okay solution could be that the network is only part of the ProcessRunner class. But how can a method access it in this case?
  2. I have no clue what the first issues triggers. The object in the Protocol class as well in the ProcessRunner class are both of type nn.Module. Why is it not satisfied then? or is this an issue which appears by the second issue?

Solution

  • The main problem is that you are assigning the result of process.update() to self.network, which is inferred to be None.

    self.network has the type of, as you would expect, Module. None clearly isn't of the same type, hence the first error:

    Cannot assign member "network" for type "ProcessRunner*"
      "None" is incompatible with "Module"
    

    Now that you have assigned None to self.network, Pyright thinks that it is of type Module | None. However, Process.network is of type Module, so a second error is reported:

    Cannot assign member "network" for type "Process"
      Type "Module | None" cannot be assigned to type "Module"
        "None" is incompatible with "Module"
    

    One solution is to explicitly specify the types:

    (playground)

    @runtime_checkable
    class Process(Protocol):
        network: Module
    
        def update(self, **kwargs: Any) -> Module:
            ...
    

    If .update() is not intended to return anything, then don't assign that to self.network:

    (playground)

        def run(self, processes: list[Process]):
            for process in processes:
                process.network = self.network
                process.update()
                self.network = process.network
    

    You could also define a more ergonomic Process interface:

    (playground)

    @runtime_checkable
    class Process(Protocol):
    
        def update(self, network: Module, **kwargs: Any) -> None:
            ...
    
    class ProcessRunner:
    
        def run(self, processes: list[Process]):
            for process in processes:
                process.update(self.network)