I have a Protocol
subclass that defines objects with attributes from an external library:
class P(Protocol):
val: int
For testing purposes, I want to turn this protocol class into something I can instantiate easily. However, when I try to turn it into a dataclass, an error pops up:
import dataclasses
from typing import Protocol
class P(Protocol):
val: int
PInst = dataclasses.dataclass(P)
PInst(val=4) # TypeError: Protocols cannot be instantiated
Is there an easy solution to use P
to create a class that satifies its protocol and is instantiable, without redeclaring its attributes?
I have a Protocol
subclass that defines objects with attributes from an external library:
class P(Protocol):
val: int
For testing purposes, I want to turn this protocol class into something I can instantiate easily. However, when I try to turn it into a dataclass, an error pops up:
import dataclasses
from typing import Protocol
class P(Protocol):
val: int
PInst = dataclasses.dataclass(P)
PInst(val=4) # TypeError: Protocols cannot be instantiated
Is there an easy solution to use P
to create a class that satifies its protocol and is instantiable, without redeclaring its attributes?
1 Answer
Reset to default 2You are asking for a non-protocol class derived from the attributes in your protocol class. They are stored in the annotations, which are accessible as typing.get_type_hints(P)
.
In my first try, I dynamically created that class with builtin type
, passing it the protocol's type hints to define an equivalent non-protocol class, and pass that to dataclass
. But it needed me to manually set dunder annotations before it could correctly create the init method.
But messing with internal attributes is a red flag telling me there should be an easier way. So I looked at the dataclasses public interface and found make_dataclass
. You can just pass the annotations directly to its fields parameter.
from dataclasses import make_dataclass
from typing import Protocol, get_type_hints
class P(Protocol):
val: int
DP = make_dataclass('DP', get_type_hints(P))
instance = DP(val=4)
print(instance)
output:
DP(val=4)
P
is supposed to match;@dataclasses.dataclass
implicitly adds attributes (such as an__init__
), and the type-checker sees these implicit attributes, so it's unclear from your definition whether items which are supposed to matchP
should also include matching implicit attributes or not. If they should include matching implicit attributes, then just ditchProtocol
and subclass fromP
; otherwise I'd go for a class-based@typing.dataclass_transform
and switch the base class ofP
based on compile-time constants. – dROOOze Commented Mar 13 at 9:16defineConstant
configuration. – dROOOze Commented Mar 13 at 10:27Nominal
in dROOOze's example as a real case that you might encounter that follows the ProtocolP
, i.e. it is assignable toP
and nothing you need to define in your code. – Daraan Commented Mar 13 at 13:43Nominal
in the playground example is a nominal type placeholder for something in your real code that is assignable to the structural typeP
. From your example, the point of using a protocol (structural type) is that arbitrary nominal types in your real code can match this structural type without subclassing from P. – dROOOze Commented Mar 13 at 17:45