Can the semantics of special builtins be made available to users?

There are three particular items I have in mind, but a general question.

(1) There is currently a thread about using overload for single or multiple dispatch. I think a better solution would be to allow users to declare their dispatching decorators as having overload semantics.

(2) Under sqlalchemy, hybrid_property is a commonly used decorator with property-like meaning. Other libs have similar decorators, and all of their usages require the same workaround.

(3) PEP 724 / StrictTypeGuard aligns user defined guards more closely with isinstance. However, a comment in the discussion thread notes that isinstance has some special treatment in type checkers. That means that even with StrictTypeGuard, there could still be some gap between the two.

Most of us are familiar with a typical workaround for these cases. In terms of (2),

if TYPE_CHECKING:
    hybrid_property = property
else:
    from sqlalchemy.orm import hybrid_property

To some, I’m sure that seems fine. At typing time, use one, and at runtime, use the other.

To me, this looks like a “hack” to handle the fact that we cannot correctly declare user-defined constructs with the typing-time behavior of certain special symbols.

And in terms of (1), it won’t work nicely. Overload usage expects a non-overloaded variant, which is not a good interface requirement for dispatch systems.

Are the typing maintainers of one mind or the other regarding these cases? Is each one different, with some being good ideas and others not?

I’m sure isinstance’s special cases are tied to iterables and tuples of types. Perhaps that’s the only way in which it differs from StrictTypeGuard. Can it be retconned in typing docs and specifications to be described that way? “isinstance behaves as a StrictTypeGuard when used on a single type, of the form type[T] -> StrictTypeGuard[T]”?

This seems like a common problem with a potentially common solution: try to make the special constructs in the language less special and more user-available.
My main question is: is this a fruitful area to discuss and explore? When I find such things, should I be thinking that it’s a problem which typing can solve, or is that “just the way it is” and I will forever be using the typing-time aliasing trick?

1 Like

I think this is a worthwhile goal in general, but not an easy one. From the concrete examples you give, I do think isinstance is almost equivalent to:

def isinstance[T](obj: object, type: type[T]) -> TypeNarrower[T]: ...

(Using my suggested name from PEP 724: Stricter Type Guards - #54 by Jelle).

It’s almost equivalent because isinstance also supports nested tuples of types, which you could possibly express with a set of overloads but it certainly wouldn’t look nice.

property is hard to replace fully because the @...setter pattern is difficult to express from a type system perspective. However, PEP 612 ParamSpec is quite possibly enough for type checkers with good descriptor support to avoid special-casing @classmethod and @staticmethod.

2 Likes

For property there was a past discussion here to avoid special casing it. I think one key comment against was here. At moment some error messages/read only type behavior is special cased for property. Variable being read only vs mutable may impact other rules (invariance) and would be tricky to describe in type system today. Error messages a custom property like class may not make sense.

1 Like

I had a lot of trouble making decorators that expose the descriptor protocol, and ended up proposing something to fill the gap.

This is similar to Proposal: `@typing.decorator_transform` to annotate a decorator as having a specific behavior

I think for specific cases, we can consider specific solutions (like PEP 681 dataclass_transform). Or maybe it’s enough to just push type checkers to have certain behaviours with descriptors.

On the other hand, if you want something for the general case, as proposed in that other thread, I think if TYPE_CHECKING is actually very reasonable and is super explicit about what the resulting semantics are.

1 Like