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

Classmethod doesn't pass generic type #616

Closed
Peilonrayz opened this issue Feb 22, 2019 · 3 comments
Closed

Classmethod doesn't pass generic type #616

Peilonrayz opened this issue Feb 22, 2019 · 3 comments

Comments

@Peilonrayz
Copy link

I'd assume that Obj[int] is it's own class, different from Obj. However classmethod doesn't work this way. This means that I can't use Obj[int].from_str() to make an Obj[int], much like I can use Obj.from_str() to make an Obj. This can be seen in the following example.

from typing import Generic, TypeVar, Type

T = TypeVar('T')


class Obj(Generic[T]):
    value: T

    def __init__(self, value: T):
        self.value = value

    def __str__(self):
        name = getattr(self, '__orig_class__', type(self).__name__)
        return f'{name}(value={self.value!r})'

    @classmethod
    def from_str(cls, value: str):
        fn = getattr(cls, '__args__', [str])[0]
        return cls(fn(value))


def from_str(cls: Type[Obj[T]], value) -> Obj[T]:
    fn = getattr(cls, '__args__', [str])[0]
    return cls(fn(value))


if __name__ == '__main__':
    # untyped
    print(Obj.from_str('1'))
    print(from_str(Obj, '1'))

    # typed
    print(Obj[int].from_str('1'))
    print(from_str(Obj[int], '1'))

Which outputs:

Obj(value='1')
Obj(value='1')
Obj(value='1')
__main__.Obj[int](value=1)

It seems like classmethod and typing agree that it shouldn't be passed the generic type. However I think passing the type as well to be better than having to make all classmethods outside the class to support the generic type. It also doesn't look like it'd destroy current code as cls seems to work the same way whether it's typed or not.

@ilevkivskyi
Copy link
Member

I assume you are talking about Python 3.7. If this is the case, then this will be tricky to implement, since in Python 3.7 Obj[int] is not even a class object. It is a thin proxy to Obj class object, all attributes (variables and methods) accessed on the former are directly relayed to latter, so cls in your example will be always Obj. We can of course add some hacky special-casing, but I am not sure it is worth it.

(Also I hope you realize that __args__ and __orig_class__ are undocumented private attributes, and there are no guarantees about their behavior.)

@Peilonrayz
Copy link
Author

@ilevkivskyi Yes this is Python 3.7. I tried this in Python 3.6.8 by changing the from_str functions to the following which works the way I thought it would.

def from_str(cls: Type[Obj[T]], value) -> Obj[T]:
    return cls(value)

I don't know enough about typing to say whether or why it's worth it, other than the reason in the OP.

(Yes I'm aware they are no guarantees, as can be seen by having to change the code to work in Python 3.6.)

@ilevkivskyi
Copy link
Member

This is a duplicate of #629 (or rather vice-versa, but the other issue has more context and a possible workaround).

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

2 participants