# \[Typing Spec\] \`isinstance\` checks on Data-\`Protocols\` / possible changes

**URL:** <https://discuss.python.org/t/typing-spec-isinstance-checks-on-data-protocols-possible-changes/83369>\
**Category:** Typing\
**Tags:** typing\
**Created:** [March 6, 2025, 2:54pm UTC](https://discuss.python.org/t/typing-spec-isinstance-checks-on-data-protocols-possible-changes/83369 "2025-03-06T14:54:48Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [March 6, 2025, 2:54pm UTC](https://discuss.python.org/t/typing-spec-isinstance-checks-on-data-protocols-possible-changes/83369/1 "2025-03-06T14:54:48Z")

</div>

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](https://github.com/python/typing/discussions/1941) failing:

```python
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.**
>
> ```python
> 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 that `getattr(obj, name)` will provide the attribute.
- trust `obj. __static_attributes__ `: if an attribute is identified to exist statically by this mechanism, checking `getattr_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.
