2

How to do python typing for list of classes.

Suppose I have following classes.

from dataclasses import dataclass

@dataclass
class A:
    name: str

@dataclass
class B:
   age: int

#...
CLASSES = [A, B]  #... more classes in list

Now I define a function that instantiate any one of the following class

def func(resource: Union[*CLASSES], value: Union[str, int]):
    return resource(value)

But type-hinting is not valid here. How to do type hinting from List?

PaxPrz
  • 1,778
  • 1
  • 13
  • 29
  • Incase the possibility are 10 or more classes. There would be a lot of code for typing; making code look dirty. Besides this makes me **change only at one list** instead of **changing on every function signature** if I add new class. :) – PaxPrz Dec 07 '20 at 06:57
  • 2
    What do you mean by "type casting", exactly? Please be specific. Note, "type casting" is not really a term that is applicable to python, although it is used informall to mean "type conversion". – juanpa.arrivillaga Dec 07 '20 at 06:58
  • 2
    If you have 10 or more possibly classes, there's already something seriously wrong with your code - it's likely they share a super-class that would be a better match; at any rate, you can't use expansion on a *variable* to get at the types that are stored in it to define the type of function parameters - you're mixing runtime and compile-time information – Grismar Dec 07 '20 at 06:59
  • 2
    Type hints must be static so that the linters can validate them. – Selcuk Dec 07 '20 at 07:02
  • @juanpa.arrivillaga Sorry for mentioning type casting. I'll correct it – PaxPrz Dec 07 '20 at 07:03
  • @Grismar I was just considering for a scenario. Don't worry my code isn't that poor. I was just wondering how to do it :D – PaxPrz Dec 07 '20 at 07:04
  • @Selcuk So in that case I must go with `def func(resource: [A, B, C,...])`. Because I know in runtime these hinting doesn't make any value. or does it? – PaxPrz Dec 07 '20 at 07:05
  • 2
    No, type hints are only for the IDE/linter to make it easier to spot bugs. They are not used by the interpreter (at least not by CPython). You don't have to use a `Union`; you can use what @Grismar suggested in their answer. Unions are only necessary if you are using built-in or third party classes that you can't modify. – Selcuk Dec 07 '20 at 07:05
  • Do you actually need ``CLASSES`` to be a list? – MisterMiyagi Dec 14 '20 at 12:07
  • 1
    Does this answer your question? [Python typing: Dynamically Create Literal Alias from List of Valid Values](https://stackoverflow.com/questions/64522040/python-typing-dynamically-create-literal-alias-from-list-of-valid-values) – MisterMiyagi Dec 14 '20 at 12:12
  • @MisterMiyagi, They can be any iterables. – PaxPrz Dec 15 '20 at 01:28
  • In that case, does the suggested duplicate answerr your question? – MisterMiyagi Dec 15 '20 at 05:53

1 Answers1

2

This achieves what you want using a super-class that all the intended classes share:

from typing import Union
from dataclasses import dataclass


@dataclass
class MyDataMixin:
    pass


@dataclass
class A(MyDataMixin):
    name: str


@dataclass
class B(MyDataMixin):
    age: int


def func(resource: MyDataMixin, value: Union[str, int]):
    return resource

You can't use expansion in the type definition of the parameter like you were trying to do, that only works on variables at runtime.

As @Selcuk indicated in the comments, naming it MyDataMixin makes more sense, as the class is not intended to be used by itself. You could go further and ensure it's just an abstract class - but that's beyond the scope of the question.

Grismar
  • 27,561
  • 4
  • 31
  • 54
  • 1
    Better name it a `Mixin` so that other (actual) superclasses can be extended if needed. – Selcuk Dec 07 '20 at 07:02
  • Shall I use **ABC** instead of *dataclass* as Parent? – PaxPrz Dec 07 '20 at 07:10
  • Yeah, the mixin doesn't have to be a dataclass itself, although it certainly can be. I wouldn't go the route of using `ABC` unless you have specific reasons to want to use an abstract base class, like requiring an abstract method to be implemented on each subclass - but that goes way beyond what you were asking. Just inheriting from a shared base class is enough. Everything else is a different matter. – Grismar Dec 07 '20 at 07:21