What is the right way to extract PEP 593 annotations

@MegaIng I don’t mean common in terms of frequency executed, but common in terms of places it is directly used in code. You are citing a single unique case which seems to be the specific one I was calling out: custom meta classes (by which I mean, the code that generates class objects).

I don’t think this case, If you have a dataclass and subclass it with another dataclass then both classes annotations are necessary to make constructor. My experience writing dataclass like functionality is needing mro.

@dataclass
class A:
  x: int

@dataclass
class B(A):
  y: str

# Here B's constructor made by dataclass has both x/y so it needs full mro's fields, not just B's.

I’m neutralish though to this as walking mro is not much code. I think what runtime annotations would benefit from is less specific methods and more code recipes/tutorial documentation on common patterns.

You probably know this isn’t a metaclass (which has a clear and well defined definition within python). But misusing terminology is just going to be confusing.

Well, plus everything else that can be considered a dataclass transform. I am not actually aware of too many other uses of class annotations. Maybe you can cited a few actual examples where the current behavior is unnecessary burdensome, and how this would be changed without making the dataclass-style uses a lot harder?

Yes, if both are a dataclass. If the parent is not a dataclass [1], annotations should not be inherited. This behavior can only be correctly implemented if there is a clear way to get the annotations from a class without it’s children.


  1. this is very explictly allowed and somewhat encouraged by the fact that dataclasses is a decorator and not a metaclass/baseclass ↩︎

@MegaIng

You probably know this isn’t a metaclass (which has a clear and well defined definition within python).

Sure if you have an umbrella term I’d be happy to use it, but for now let’s just assume by “meta class” I meant code generating class objects themselves.

Well, plus everything else that can be considered a dataclass transform.

Well yes, you are still pretty much pointing to the one example I pointed out myself: Code generating class objects.

This behavior can only be correctly implemented if there is a clear way to get the annotations from a class without it’s children.

Nobody is remotely suggesting that such functionality should be taken away. Only that it is one, and only one, very specific use case. It’s not a “general case”.

1 Like

Ok, then I am confused. What is your point? I thought it was that get_annotations doesn’t do the right thing and should be changed. For what I am going to call the most common usecase, both in user code and in implementations, for runtime-annotation (you have yet to provide a different example), getting the annotations of just a class is the correct thing to do. If you want the annotations of all base classes combined it’s an ~4 line function build ontop of inspect.get_annotations. But I would guess that most consumers of these annotations have to be more careful about inheritance anyway (for example, disallow the same attribute name), so they probably want to manually walk the mro anyway. What is your “general case”? What are the concrete examples that you have in mind?

@MegaIng you are forcing this thread way off topic and down a rabbit hole. I’m loathed to summarise the last 25 posts for you. But here goes:

To recap: I am trying to map a typeddict onto an external data source by annotating it with PEP 593 typing.Annotated, but that is proving much more complex that I’d initially imagined.

The ancestry of any such typeddict classes is wholly irrelevant to me, as it is irrelevant to Pydantic. Pydantic being a pretty good analogy both for what I’m trying to achieve and what I thought PEP 593 was for in the first place.

I was, most recently, expressing surprise that as of Python 3.10 the inspect module lost the ability to extract annotations of a “whole” class instead returning only the child class, and with no option to do otherwise.

To my mind, the “general case” would be to let the developer chose.

In any case it seems remarkable that neither the updated documentation on get_annotations nor Annotations Best Practice make any mention of a need to follow the MRO.

1 Like

Yes, I agree with the original post, but I don’t think the new issue about inheritance s relevant for that. In that sense, it’s your fault that this is off-topic.

Might not be the best library to bring up, they don’t even rely on python to calculate the mro, or want to use typing.get_type_hints. This is exactly what I mean, real usecases will do more work than we can reasonably put into options for a function. They manually iterate through the bases and inherit the individual parts of the super models independent of the annotations, and in fact, use a metaclass (in either your sense or the official definition).


It sounds like all you want is something like this, right?

nn = {}
for base in reversed(cls.mro()):
    ann.update(inspect.get_annotations(base))

Yes, this should be mentioned in the docs, but I honestly don’t see a convinced reason to have this as part of the function. I do not thing that this is often enough the right thing to do, which I am trying to explain to you by mentioned the counterexamples.

I do not thing that this is often enough the right thing to do, which I am trying to explain to you by mentioned the counterexamples.

@MegaIng you are [still] trying to argue with something I’ve not actually said and that is how we ended up so far off topic. I was only expressing surprise not demanding python be changed on this point to fit my whims. The aim was for me to gain a better understanding of the space, not propose a new design.

1 Like

Well, I did nothing but interpret your words. If you are saying that my interpretation of your wording is completely wrong, then so be it.

You said the get_annotations does not fulfill it’s role and is “a bit crooked”. This is what I am arguing against. If all you wanted to do was “express surprise”, you could have done that out loud in an empty room. I added explanations as to why I think the current behavior is better, using examples of actual usecases (even if those, like pydantic, don’t actually use inspect.get_annotations).

You said the get_annotations does not fulfill it’s role and is “a bit crooked”.

No! I really didn’t say that at all. So there’s nothing to argue against. I said:

This all keeps failing the principle of least astonishment for me, and :rotating_light::point_right:I can’t quite figure out if that’s because I have a warped perception of what this is all intended for:point_left::rotating_light:, or if it is genuinely all a bit crooked itself.

A wider discussion of what this feature was really intended for might be useful. Demanding I put up specific use cases so you can shoot them down is really really not helpful.

I’m not trying to change it, just understand it

2 Likes

Ok, I don’t think arguing this any further is going to go anywhere. I still stand by my reading of your post, taking into account the stuff you didn’t just quote, so one of us is clearly failing to communicate, and I don’t think there is value in trying to find out who here. (If you want to continue this discuss, DM me, it is for sure off topic here)