-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feature] Get the type from a variable #769
Comments
I don't see the use case for your first example, but I do see some use for the second -- it might give us a nice way to spell complex function types (e.g. using defaults or keyword args). |
@gvanrossum def fn() -> int:
pass
assert ReturnType[type[fn]] == int # rather than ReturnType[fn] |
I like the idea of inferring the (structural) type of a given object a lot, especially for functions! Obviously, one would have to make sure to return a @type
def MyFunctionType(fruits: dict, *args, **kwargs) -> Any: ... As for the syntax, I'm not sure I like I'll be calling this function @type_of # A type of a function type? Uhh…?
def MyFunctionType(fruits: dict, *args, **kwargs) -> Any: ... (Maybe a longer name like Anyway, finding a decent name is certainly the smallest challenge here. The big question for me is whether For this reason, I think it would make much more sense for def func() -> int:
pass
type_of(func) (Note that the module's Then, however, the question is whether and, if so, how class Foo:
a: int = 1
b: str = "hello, world"
def __call__(self, param1: bool, param2):
...
class FooProtocol(Protocol):
a: int
b: str
def __call__(self, param1: bool, param2: Any) -> Any:
...
my_foo = Foo()
assert FooProtocol == type_of(my_foo) I guess it would be quite natural for this assertion to hold. But what if we add some non-annotated variables and methods to the class Notably, PEP-544 says that what is important is the presence of a method in the body of the protocol class (and similarly for variables, though in contrast to methods they must come with an annotation or they will be ignored). Maybe one could do a similar thing here and only analyze the body of In a way, this seems similar to the (rejected) idea for PEP 544 to turn classes into protocols. While in the PEP it was discussed whether this should be done automatically and this would have obviously brought about all kinds of problems, a feature like I'm starting to like this idea. What does everyone else think? |
After a good night's sleep, I've come to the conclusion that in my last comment I ended up talking about three related but rather different ideas: 1. Dynamically determining the type of a function (not a generic callable)…based on type hints the function object carries (and default parameter values). Again, this would also give a really nice and concise way to write down complex function types: @function_type
def ApplesVsOrangesComparator(apples: Sequence[Apple], oranges: Sequence[Orange]) -> bool: ... One could then also think about enforcing such a type during function definition time: @implements_type(ApplesVsOrangesComparator)
def my_comparator(apples: List[Apple], oranges: List[Orange]) -> bool:
# Put implementation here
... This seems very useful to me and I've been in the situation where I wanted to do this a number of times now. 2. Dynamically converting a class into a protocol…based on type hints in the class body. As much as I like this idea on theoretical grounds, I'm somewhat unsure how useful this is, given that we can always add And then, of course, there's a third idea (which I somewhat sidestepped in my previous post) which is: 3. Dynamically determining the type of an object…based on the object alone (as opposed to its class – i.e. after inheritance, considering built-in magic methods, monkey-patching the object etc. etc.). As I laid out in my previous post, I'm not sure at all how this would work, given that we don't have any annotations we can build upon: So where do we get type hints from? Should we infer them? If yes, how concrete vs. how abstract should the types be that we infer? Consider my_dict = { "a": 1, "b": 2 }
T = type_of(my_dict)
some_types_my_dict_conforms_to = [
Dict[str, int],
Dict[str, Union[Literal[1], Literal[2]] ],
TypedDict("foo", { "a": Literal[1], "b": Literal[2] }),
# ... (other combinations thereof)
]
print(some_types_my_dict_conforms_to.find(T)) # ?? In any case, I don't see a real benefit of this third option. Structural types are used to precisely define programming interfaces and do static type checking. But interfaces are rarely designed dynamically based on given dynamically defined objects and dynamically derived types and interfaces also don't make much sense for static type checking. |
I have some use cases for this. I have a class PanedWindow(tkinter.PanedWindow):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
... Would be nice to do this instead, and actually take advantage of all the widget option types I added to typeshed last year instead of ruining it with class PanedWindow(tkinter.PanedWindow):
__init__: TypeOf[tkinter.PanedWindow.__init__]
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
... As a workaround, I can also do: class PanedWindow(tkinter.PanedWindow):
if not TYPE_CHECKING:
def __init__(self, *args, **kwargs):
... but then I get no type checking inside the Another use case is a context manager named backup_open: TypeOf[open]
def backup_open(*args, **kwargs):
... I also thought about extending |
I found a perfectly working workaround for my needs: _T = TypeVar("_T")
def copy_type(f: _T) -> Callable[[Any], _T]:
return lambda x: x
class PanedWindow(tkinter.PanedWindow):
@copy_type(tkinter.PanedWindow.__init__)
def __init__(self, *args: Any, **kwargs: Any) -> None:
...
@copy_type(open)
@contextlib.contextmanager
def backup_open(file: Any, *args: Any, **kwargs: Any) -> Any:
... I hope someone finds this useful or considers adding this to typing.py :) |
@Akuli While I like your solution, it only helps to enforce the correct type of
Ideally, I would like to have a means to enforce both, in the spirit of the |
I see a lot of value in this as well. It would be especially nice to have some form of dependency to follow when working with dataclasses for example. import dataclasses
@dataclasses.dataclass
class Thing:
id: int
# Currently (not connected to source)
def get_thing_by_id(id: int) -> Thing:
pass
# Or, currently (static type checkers can't infer from this runtime value)
def get_thing_by_id(id: Thing.__dataclass_fields__['id'].type) -> Thing:
pass It would be nice to have something like this to access property types: def get_thing_by_id(id: type[Thing.id]) -> Thing:
pass Then, with a code change to update the definition could automatically update usage at the same time: @dataclasses.dataclass
class Thing:
id: str
# get_thing_by_id would now have an inferred static type `Callable[[str], Thing]` I know that's incredibly hard to implement given how python works right now, but that's still my dream. Edit: I guess that's what |
Another use-case... Currently I do this: ParameterName = Literal["MONGODB_URI", "SNOWFLAKE_PASSWORD"]
PARAMETER_NAMES: tuple[ParameterName, ...] = ParameterName.__args__ # type: ignore (the but I really wanted to do this, and have a way to infer the resulting PARAMETER_NAMES: Final = ("MONGODB_URI", "SNOWFLAKE_PASSWORD") e.g. with that definition I can write a function like: def value_mapping(values: list[str]):
return list(zip(PARAMETER_NAMES, values, strict=True)) ...and Pylance is able to infer the return type as which is great! but if I wanted to annotate the return type explicitly I'd have to write out the members of the Literal again myself in TypeScript you can do: const animals = ['cat', 'dog', 'mouse'] as const
type Animal = typeof animals[number]
// type Animal = 'cat' | 'dog' | 'mouse' the |
My use case here is for a dict inside a function that might contain the same types as the parameters to the function. def foo(a:str, b:int):
mydict = {x:a}
if whatever:
mydict.y=b The type inference here for mydict is |
I recommend using type aliases. |
That would still require changing both places if the type of the parameter changes. |
Feature
A similar feature in typescript
Pitch
The expected way in future python.
Possible implementation: implement
__class_getitem__
fortype
:The text was updated successfully, but these errors were encountered: