python equivalent to c# where T : new()

576 Views Asked by At

Is there a way in python to restrict generic types to ones that can be instantiated?

I'm looking for an equivalent to C#'s

 where T : new()

I am already using type hints as follows

T = TypeVar("T", bound=Button)

And I'd like to know if there something like

 T = TypeVar("T", bound=Button,new())

For example, given that Button is an abstract class (ABC) this code should create a menu composed of any subtype of Button

class Button(ABC):
    def __init__(self) -> None:
        self._name = ""
    @abstractmethod
    def show(self) -> None:
        pass
    def set_name(self, value:str) -> None:
        self._name = value

class SquareButton(Button):
    def show(self) -> None:
        print("[",self._name,"]")

class CircleButton(Button):
    def show(self) -> None:
        print("(",self._name,")")

T = TypeVar("T", bound=Button) # restricting type here

class MenuBuilder:
    def build(self,class_type: Type[T]) -> None:
        pb1: Button = class_type()
        pb2: Button = class_type()
        pb3: Button = class_type()
        pb1.set_name("button1")
        pb2.set_name("button2")
        pb3.set_name("button3")
        pb1.show(); pb2.show(); pb3.show()

#main
mb: MenuBuilder = MenuBuilder()
mb.build(Button) # fails at run time - I want a pre-run error here
mb.build(CircleButton) # OK

As it stands now, it allows calling the method with an abstract type Button, which fails at run time when I try to create instances of it.

I am using python 3.8.1 and VS Code with pylance

Edit: One of the answers suggested replacing the Generics by Protocol to make sure Buttons functions were implemented. While this prevents sending the abstract class, it allows sending classes that don't inherit from Button. I'm looking for away to enforce both restrictions.

1

There are 1 best solutions below

6
NiziL On

I'm not familiar with this topic, but Protocol might be the way to go here.

from typing import Protocol


class Button(Protocol):
    def show(self) -> None:
        ...

    def set_name(self, value: str) -> None:
        ...


class BaseButton(object):
    def __init__(self) -> None:
        self._name = ""

    def set_name(self, value:str) -> None:
        self._name = value


class SquareButton(BaseButton):
    def show(self) -> None:
        print("[",self._name,"]")


class CircleButton(BaseButton):
    def show(self) -> None:
        print("(",self._name,")")


class MenuBuilder:
    def build(self, class_type: Type[Button]) -> None:
        pb1: Button = class_type()
        pb2: Button = class_type()
        pb3: Button = class_type()
        pb1.set_name("button1")
        pb2.set_name("button2")
        pb3.set_name("button3")
        pb1.show()
        pb2.show()
        pb3.show()