1

Let's say I have the following class:

from dataclasses import dataclass
from typing import Annotated

@dataclass
class Pizza:
    price: Annotated[float, "money"]
    size: Annotated[float, "dimension"]

    @dataclass
    class Topping:
        sauce: Annotated[str, "matter"]

These are obviously data classes (i.e. for storing data in other words) but I would like to use them for the type hinting as well. For example, it would be quite straightforward to write a functions like:

def bake_pizza(pizza: Pizza) -> Pizza:
    some_operation
    return new_pizza


def check_if_vegetarian(topping: Pizza.Topping) -> bool:
    some_operation
    return True

And the type comparison via e.g. isinstance will work without a problem.

Now my problem is that if I'm using the attributes, I can obviously not do the same, so for example I cannot write a function like:

def calculate_cost(cost: Pizza.price) -> float:
    some_calculation
    return cost

because price is an attribute and not a class. I know that instead I could write:

def calculate_cost(cost: Pizza.__annotations__["price"]) -> float:
    some_calculation
    return cost

Then I would get what I want, but it's for us a bit of a problem that depending on whether it's an attribute or a class the notation is so different. So far the only one possibility to do it without having to distinguish between attributes and classes was to use strings, e.g. "Pizza.Topping" or "Pizza.price", but I'm not sure how clean this method is. Does anyone happen to have a better idea?

5
  • 1
    A similar question was asked a few days ago: How can I reference the type hint for an instance variable on another class?. The answer there is not in anyway correct, however. There must be a good dupe for both yours and that one, but it hasn't been found yet. Commented Sep 26, 2024 at 20:29
  • 1
    Is Topping just for type hints? Or is it supposed to be a container for all the toppings on the pizza? Because nested classes are not nested containers (it's not like nested structs in C). Commented Sep 26, 2024 at 20:32
  • Topping is also a data container in the conventional sense and not made for type hinting Commented Sep 26, 2024 at 20:33
  • 1
    You can extract the type at runtime by iterating through the list returned by dataclasses.fields. If you want this for static typing, your only(?) option is to be proactive: define a type alias, and use that for both the dataclass definition and the function type hint. (I assume your goal is to have just one place to define the "real" type used in both places.) Commented Sep 27, 2024 at 12:13
  • 1
    There is no way that is supported by the current type system. This has come up several times. Commented Sep 27, 2024 at 19:06

1 Answer 1

0

In the end I managed to find a solution myself with the following function:

def append_types(cls):
    for key, value in cls.__dict__.items():
        if isinstance(value, type):
            append_types(getattr(cls, key))
    try:
        for key, value in cls.__annotations__.items():
            setattr(cls, key, value)
    except AttributeError:
        pass

And with this, I get:

print(Pizza)
print(Pizza.Topping)
print(Pizza.size)
print(Pizza.price)
print(Pizza.Topping.sauce)

Output:

<class '__main__.Pizza'>
<class '__main__.Pizza.Topping'>
typing.Annotated[float, 'dimension']
typing.Annotated[float, 'money']
typing.Annotated[str, 'matter']

The only one problem is that the default value is lost when there is one and it's accessed without instantiating the class, i.e.

@dataclass
class Pizza:
    size: Annotated[float, "dimension"] = 10

print(Pizza.size)
append_types(Pizza)
print(Pizza.size)

Output:

10
typing.Annotated[float, 'dimension']

But for my purpose it won't be a problem since I would always instantiate it. Otherwise it looks like it doesn't interfere with my common usage. This being said, I would have loved to find a solution that doesn't modify the original class (like by returning it instead of changing it in-place inside append_types), and if you someone knows a way to do so, I would very much appreciate it if you could leave a comment below.

Sign up to request clarification or add additional context in comments.

2 Comments

this doesn't mkae any sense, why are you doing setattr(cls, key, value)? And why are you iterating over .__dict__.items()? You should just iterate over the annotations, or since you are using dataclasses, the "fields"
Oh, you are nesting classes... but why? And why are you using setattr?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.