pythonpython-typingpython-dataclasses

How to make an easily instantiable derivative attribute-only protocol class?


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?


Solution

  • You 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)