It seems that currently it is underspecified under which conditions precisely isinstance(obj, DataProtocol)
succeeds. It would be helpful if some simple, precise conditions could be written down. This is motivated by the following example failing:
from typing import Protocol, runtime_checkable # python 3.13.2
from torch import nn # v2.6.0
@runtime_checkable
class HasWeight(Protocol):
weight: nn.Parameter
model = nn.Linear(3, 4)
assert hasattr(model, "weight") # ✅
assert isinstance(model.weight, nn.Parameter) # ✅
assert "weight" in model.__static_attributes__ # ✅
assert "weight" in model.__annotations__ # ✅
assert isinstance(model, HasWeight) # ❌
I was able to identify the precise origin of this failure: torch
stores different types of attributes in different dictionaries _parameters
, _buffers
, _modules
instead of __dict__
, and inspect.getattr_static
, which is internally used by typing._ProtocolMeta.__isinstancecheck__
, only seems to check for __dict__
-entries in the class hierarchy.
Reproduction using only built-ins.
from typing import Any, Protocol, runtime_checkable
class Tensor(float): ...
@runtime_checkable
class HasBias(Protocol):
bias: Tensor
class Module:
def __init__(self) -> None:
self._parameters: dict[str, Tensor] = {}
def register_parameter(self, name: str, value: float) -> None:
if "_parameters" not in self.__dict__:
raise AttributeError("Call __init__() first")
self._parameters[name] = Tensor(value)
def __getattr__(self, name: str) -> Any:
if name in self._parameters:
return self._parameters[name]
raise AttributeError(f"Model has no attribute '{name}'")
def __setattr__(self, name: str, value: Any) -> None:
if isinstance(value, Tensor):
self.register_parameter(name, value)
else:
super().__setattr__(name, value)
class Linear(Module):
weight: Tensor
bias: Tensor
def __init__(self):
super().__init__()
self.weight = Tensor(1.0)
self.bias = Tensor(1.0)
def forward(self, x: Tensor) -> Tensor:
return Tensor(x * self.weight + self.bias)
model = Linear()
assert "bias" in model.__annotations__ # ✅
assert "bias" in model.__static_attributes__ # ✅
# fails because it relies on inspect.getattr_static, which inspects __dict__
assert isinstance(model, HasBias) # ❌
Motivated by this example, I propose the following changes to make isinstance(obj, DataProtocol)
both simpler and more broadly applicable:
getattr_static
is too strong. There should be a weaker version hasattr_static
:
- trust
obj.__annotations__
: if an attribute is annotated, the Protocol should trust the class author thatgetattr(obj, name)
will provide the attribute. - trust
obj.__static_attributes__
: if an attribute is identified to exist statically by this mechanism, checkinggetattr_static
should be superfluous as well. - use
getattr_static
only as a fallback if the previous two methods return a negative.
Hence, for annotated code, isinstance(obj, DataProtocol)
should be guaranteed to succeed if the object provides annotations for the required attributes.