How to get type checking on protocol class references? #7453
|
Hello, I've been trying to figure out how I can get the type checker to detect that a passed in class reference is invalid when it is missing a class variable that is defined in the base protocol. I've provided some repo code below. I'm not sure if this is something I'm doing incorrectly, that isn't supported, or a bug, but would appreciate any feedback or insight. NOTE: This is with Pylance in from enum import Enum
from typing import ClassVar, Protocol
class ActionType(Enum):
NONE = 0
SINGLE = 1
MULTI = 2
class Action(Protocol):
action_type: ActionType
display_name: str
wrapping_chars: str
class SingleArgAction(Action):
action_type = ActionType.SINGLE
wrapping_chars = "[]"
class MultiArgAction(Action):
action_type: ClassVar = ActionType.MULTI
wrapping_chars: ClassVar = "{}"
seperating_char = "|"
class UpperAction(SingleArgAction):
display_name: ClassVar = "Add"
class SumAction(MultiArgAction):
# display_name: ClassVar = "Sum"
...
action_registry: dict[str, type[Action]] = {}
def register_action(action: type[Action]) -> None:
if action.display_name in action_registry:
raise ValueError(f"Action {action.display_name} already registered")
action_registry[str(action.display_name)] = action
# No error - Expected
register_action(UpperAction)
# No Error - Unexpected - SumAction has no display_name attribute
register_action(SumAction)
test_direct: type[Action] = SumAction
# Error - Expected
test: Action = SumAction()Thanks. |
Replies: 1 comment 1 reply
|
When you have a non-protocol class that inherits from a protocol class, the child class effectively "promises" that it will implement the protocol. In the case of a method, this means it must implement that method. In the case of a variable, it's not so clear what "implements" means. In general, pyright doesn't enforce that a class or instance variable is assigned a value. My recommendation is to not use the protocol class as a base class here. Simply remove class Action(Protocol):
action_type: ClassVar[ActionType]
display_name: ClassVar[str]
wrapping_chars: ClassVar[str]There is one bug here that I noticed. Pyright should notify you in the case that a non-protocol class derives from a protocol class and redefines a non-ClassVar as a ClassVar or vice versa. I've filed this bug to track this issue. |
When you have a non-protocol class that inherits from a protocol class, the child class effectively "promises" that it will implement the protocol. In the case of a method, this means it must implement that method. In the case of a variable, it's not so clear what "implements" means. In general, pyright doesn't enforce that a class or instance variable is assigned a value.
My recommendation is to not use the protocol class as a base class here. Simply remove
Actionfrom the base class list when definingSingleArgActionandMultiArgAction. Pyright will then detect a protocol mismatch if these classes don't declare the three class variables defined inAction. I presume that you intended for…