python / python/typing

Support for `ParamSpec` for `type`

Open
#1,991 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

topic: feature
Dominant language
Python
Stars
1.8k
Forks
302
Avg merge
23h
Merged PRs (30d)
8

Description

The problem I'm having is that the type doesn't support ParamSpec, so I can't use that to enforce args and kwargs, in the same way as as Callable. I'm not 100% sure whether this is correct, but isn't every type a Callable, so in a sense that type is a subtype of Callable, so there should be a way to represent type in the same way as a Callable to obey Liskov?

For example, I'm trying to write an overload such that passing in a type will return ExpectType, while a general Callable returns ExpectCallable:

from collections.abc import Callable

import typing_extensions as t

P = t.ParamSpec("P")
T_co = t.TypeVar("T_co", covariant=True)

class ExpectCallable(t.Generic[P, T_co]): ...
class ExpectType(ExpectCallable[P, T_co]): ...

@t.overload
def expect(
    v: type[T_co], /, *args: P.args, **kwargs: P.kwargs
) -> ExpectType[P, T_co]: ...
@t.overload
def expect(
    v: Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs
) -> ExpectCallable[P, T_co]: ...
def expect(
    v: type[T_co] | Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs
) -> ExpectType[P, T_co] | ExpectCallable[P, T_co]:
    return ExpectType() if isinstance(v, type) else ExpectCallable()

class A:
    def __init__(self, inp: str, /) -> None: ...

def fn(inp: str, /) -> None: ...

t.assert_type(expect(A), ExpectType[[], A])  # Shouldn't be allowed  
t.assert_type(expect(fn, "inp"), ExpectCallable[[str], None])

As you can see that t.assert_type(expect(A), ExpectType[[], A]) passes with no errors, even though A is suppose to take in a str argument. I'm expecting expect(A) to require a str argument, similar to how expect(fn, "inp") requires the second parameter "inp".

Note that I haven't used *args and **kwargs in the example for simplicity, but they are meant to be used inside of the Expect classes.

Maybe the simplest way is to add a new class TypeCallable that allows for TypeCallable[P, T] instead of modifying type, so the above example is achievable:

@t.overload
def expect(
    v: TypeCallable[P, T_co], /, *args: P.args, **kwargs: P.kwargs
) -> ExpectType[P, T_co]: ...
@t.overload
def expect(
    v: Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs
) -> ExpectCallable[P, T_co]: ...
def expect(
    v: TypeCallable[P, T_co] | Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs
) -> ExpectType[P, T_co] | ExpectCallable[P, T_co]:
    return ExpectType() if isinstance(v, type) else ExpectCallable()

Note that I think https://github.com/python/typing/issues/1966 might also solve the above problem.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review the ParamSpec behavior for type and Callable described in the examples, then compare it with the possible direction in typing issue #1966. Done would require an agreed representation that preserves constructor parameter information and makes the proposed overload distinguishable, but this issue does not name implementation files or tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.