Skip to content
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

type builtin function should return an instance of typing.Type? #1758

Closed
Michael0x2a opened this issue Jun 28, 2016 · 5 comments
Closed

type builtin function should return an instance of typing.Type? #1758

Michael0x2a opened this issue Jun 28, 2016 · 5 comments

Comments

@Michael0x2a
Copy link
Collaborator

I've run into a snippit of code which essentially boils down into this:

class SomeObject(object):
    @classmethod
    def test_class_method(cls) -> None:
        print('Test class method')

    @staticmethod
    def test_static_method() -> None:
        print('Test static method')

s = SomeObject()
t = type(s)

# Doesn't typecheck
t.test_static_method()
t.test_class_method()

The behavior I was expecting was for t to be of type Type[SomeObject] -- instead, it's of type type, due to how type is annotated in Typeshed:

class type:
    __bases__ = ...  # type: Tuple[type, ...]
    __name__ = ...  # type: str
    __qualname__ = ...  # type: str
    __module__ = ...  # type: str
    __dict__ = ...  # type: Dict[str, Any]
    __mro__ = ...  # type: Tuple[type, ...]

    @overload
    def __init__(self, o: object) -> None: ...
    @overload
    def __init__(self, name: str, bases: Tuple[type, ...], dict: Dict[str, Any]) -> None: ...
    @overload
    def __new__(cls, o: object) -> type: ...
    @overload
    def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> type: ...
    def __call__(self, *args: Any, **kwds: Any) -> Any: ...
    def __subclasses__(self) -> List[type]: ...
    # Note: the documentation doesn't specify what the return type is, the standard
    # implementation seems to be returning a list.
    def mro(self) -> List[type]: ...

I attempted modifying the definition in Typeshed by first changing __new__ to have the signature def __new__(cls, o: _T) -> Type[_T], but that had no effect. I then tried commenting out both __init__s entirely in case mypy was defaulting to looking at those, but that also did nothing. I'm not entirely sure why the change to __new__ isn't working, though it may be related to issue 1020?

I did find a workaround for this particular case by doing:

from typing import cast, Type

# ...snip...

s = SomeObject()
t2 = cast(Type[SomeObject], type(s))

# typechecks
t2.test_static_method()
t2.test_class_method()

...but it would be nice if type could be changed so this casting is unnecessary, though I'm not entirely sure how this would be done.

@gvanrossum
Copy link
Member

I think mypy would have to special-case type(x) to return Type[C] where
C is what it knows of the type of x. The same should apply for
x.__class__. Previously such types were hard to express. They're still a
little cumbersome internally in mypy but at least they're possible.

Are you interested in trying to fix this? I expect complications in the
case what's known about x is not a simple class but a union or type
variable.

On Mon, Jun 27, 2016 at 9:08 PM, Michael Lee notifications@github.com
wrote:

I've run into a snippit of code which essentially boils down into this:

class SomeObject(object):
@classmethod
def test_class_method(cls) -> None:
print('Test class method')

@staticmethod
def test_static_method() -> None:
    print('Test static method')

s = SomeObject()
t = type(s)

Doesn't typecheck

t.test_static_method()
t.test_class_method()

The behavior I was expecting was for t to be of type Type[SomeObject] --
instead, it's of type type, due to how type is annotated in Typeshed:

class type:
bases = ... # type: Tuple[type, ...]
name = ... # type: str
qualname = ... # type: str
module = ... # type: str
dict = ... # type: Dict[str, Any]
mro = ... # type: Tuple[type, ...]

@overload
def __init__(self, o: object) -> None: ...
@overload
def __init__(self, name: str, bases: Tuple[type, ...], dict: Dict[str, Any]) -> None: ...
@overload
def __new__(cls, o: object) -> type: ...
@overload
def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> type: ...
def __call__(self, *args: Any, **kwds: Any) -> Any: ...
def __subclasses__(self) -> List[type]: ...
# Note: the documentation doesn't specify what the return type is, the standard
# implementation seems to be returning a list.
def mro(self) -> List[type]: ...

I attempted modifying the definition in Typeshed by first changing new
to have the signature def new(cls, o: _T) -> Type[_T], but that had
no effect. I then tried commenting out both __init__s entirely in case
mypy was defaulting to looking at those, but that also did nothing. I'm not
entirely sure why the change to new isn't working, though it may be
related to issue 1020 #1020?

I did find a workaround for this particular case by doing:

from typing import cast, Type

...snip...

s = SomeObject()
t2 = cast(Type[SomeObject], type(s))

typechecks

t2.test_static_method()
t2.test_class_method()

...but it would be nice if type could be changed so this casting is
unnecessary, though I'm not entirely sure how this would be done.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#1758, or mute the thread
https://github.com/notifications/unsubscribe/ACwrMkroNqtnnjor0B3LAjrUiAvc_QtSks5qQJ5AgaJpZM4I_uT-
.

--Guido van Rossum (python.org/~guido)

@Michael0x2a
Copy link
Collaborator Author

(For the record: I've talked to @gvanrossum offline and am currently working on this)

@ddfisher
Copy link
Collaborator

Is there some reason we shouldn't make the general case of def f(x: T) -> Type[T] work?

@rwbarton
Copy link
Contributor

I would expect it to work in general, but maybe the problem is that type isn't a normal function, it's the constructor of a class? Does it make sense to make type a generic class?

@gvanrossum
Copy link
Member

gvanrossum commented Jun 29, 2016

Yes general case already works (it's the reason Type[T] exists :-).

The definition of type() is complicated because it overloads a class and a function. The 1-arg version type(x) is a function that is roughly equivalent to x.__class__ (the "roughly" is because a metaclass can override __class__ but not type()). But type is also a class. The 3-arg version instantiates that class, and metaclasses are defined by subclassing it.

I don't think we can express this fully by clever overloads in a stub; mypy must understand that type(x) doesn't just return an instance of type; it returns a value whose type is Type[X] where X is the type of the argument x. Ditto for x.__class__. (IMO mypy should treat these two the same, despite the possible difference. There are very few actual use cases for overloading __class__; it was allowed to enable extension modules to create perfect proxy classes.)

Michael0x2a pushed a commit to Michael0x2a/mypy that referenced this issue Jul 1, 2016
This commit is a fix for python#1758 -- it special-cases the single
constructor `type` builtin function within mypy so it returns an
instance of `typing.Type` rather then just the generic `type` object.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants