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

Shorthand for union/subclassing #387

Closed
nimish opened this issue Feb 6, 2017 · 15 comments
Closed

Shorthand for union/subclassing #387

nimish opened this issue Feb 6, 2017 · 15 comments

Comments

@nimish
Copy link

nimish commented Feb 6, 2017

Hi,

I searched and couldn't find a discussion on this:

Currently the syntax for declaring intersections and type upper bounds is verbose and unwieldy, for someone like me who is annotating a large codebase: Union[Foo, Bar, Baz....] and TypeVar('T', bound=SuperType).

In comparison, TypeScript and Flow both allow for using | ; other languages use < for upper bounds.

If we view types as sets, it is surprising that although sets have overloaded operators, types don't--we have to use a very verbose syntax for declaring types, when we have a perfectly good one: Python itself.

Could someone point me to any previous discussion on this?

@ilevkivskyi
Copy link
Member

There was a similar discussion #211 where a short notation for variance was proposed as +T for covariant, and -T for contravariant types. I could also imagine T<Comparable for bounds. So that we would have:

class MyClass(Generic[-T, U<Comparable, +S], Mapping[T, int], Set[S]):

    def my_method(self, lst: List[T]) -> S:
        ...

I like such syntax in principle but there are some problems:

  • It is maybe to late to change this, many people are already get used to current notation.
  • This syntax is difficult to google, if someone encounters it in code.
  • It is still not possible to use | for unions because of built-in types. (This would require a corresponding slot in type which is a non-starter).
  • There are currently no volunteer to implement this in mypy.

@JukkaL
Copy link
Contributor

JukkaL commented Feb 6, 2017

Also, I haven't seen frequent use of upper bounds or co/contravariant type variables in annotated code. If these features are only used occasionally, being easy to google is more important than brevity. Can you give some examples of use cases where these come up frequently? If you use union types a lot, type aliases may help.

@nimish
Copy link
Author

nimish commented Feb 6, 2017

Upper bounds come up a bit when using generic types with subclasses, but it's the union types that show up often enough that it gets really irritating to have to define up a type alias each time. Furthermore this clearly doesn't scale--it'd require me to implement a type alias for every possible combination of types ;)

Personally, I find the infix notation more consistent and easier to understand than the Lisp-y prefix stuff, and we already have convenience operators for sets.

I don't particularly care about the covariant and contravariant markers since they are quite rare. Using < or <= for subclassing does make it easy to remember which is which: if A < B implies F[A] < F[B] then F is covariant; if A < B implies F[B] < F[A] then F is contravariant.

The "extremely difficult to Google" bit I don't buy since TypeScript and Flow use them heavily, and I have never encountered that as a complaint. It certainly doesn't stop the use of google-unfriendly operators in python code!

@ilevkivskyi
Copy link
Member

it'd require me to implement a type alias for every possible combination of types ;)

There are generic aliases, you could write:

Vec = Iterable[Tuple[T, T]]
v: Vec[int] # Same as Iterable[Tuple[int, int]]
UInt = Union[List[T], int]
data: UInt[bytes] # Same as Union[List[bytes], int]
fancy: Vec[UInt[str]] # I am scared to expand this

etc.

it's the union types that show up often enough

And this is the hardest part, str | int is a non-starter. Alternatively, we could add aliases for all built-in types in typing, like Int, Str, etc. But I am not sure this is a good idea either.

@JukkaL
Copy link
Contributor

JukkaL commented Feb 6, 2017

For the kind of code I've worked on, needing to import Int, Str and other built-in types from typing would be a bad trade-off for getting a nicer syntax for Union. I'd be okay with using | for constructing a union if it would be possible to write int | str, but as @ilevkivskyi pointed out, that is not an option (at least until Python 4).

@nimish
Copy link
Author

nimish commented Feb 11, 2017

Hmm, that would be irritating. What's the technical issue that means it breaks compatibility?

Anyway, I ran into another issue where having union types comes up often in my codebase: Numpy array vs Pandas DataFrame.

@JukkaL
Copy link
Contributor

JukkaL commented Feb 16, 2017

@nimish PEP 484 related changes to Python are currently localized to the typing module. Adding support for int | str would require this policy to be changed, or we'd need to introduce a new concept beyond typing.Union to represent the result of int | str to avoid introducing a dependency to typing in builtins. Additionally, supporting this would likely break compatibility with existing code that overloads | for class objects using a metaclass. We could perhaps work around this by making | inside an annotation context different from the regular | operator, but this wouldn't be very elegant.

A technically feasible approach for supporting int | str and friends that would be unlikely to break a lot of existing code and wouldn't require a PEP 484 policy change could look something like this:

Special case | within a type annotation to mean a new operation, the result of which is an instance of a new primitive type type_or.

For example, the annotation object for x in def f(x: int | str): ... would be type_or(int, str). Static type checkers wouldn't have to care about this, of course. This would still break code that already uses | within a type annotation, though it seems likely that it wouldn't be hard to modify such code to support the new behavior.

This is sufficiently magical that it might be hard to get this accepted to Python 3.7, though.

@JukkaL
Copy link
Contributor

JukkaL commented Feb 16, 2017

There is one more way of shortening union types that already works: import ... as. Example:

from typing import Union as U

def f(x: U[int, str]) -> None:
    ...

This even looks a bit like the mathematical symbol for union.

You can also use from typing import Optional as O or from typing import Optional as Opt.

@markshannon
Copy link
Member

Why not use int or str?

It is very fast, it is perfectly clear what it means, and static checkers should have no trouble parsing it.

@ilevkivskyi
Copy link
Member

This even looks a bit like the mathematical symbol for union.

This is an interesting idea, U[int, str] looks very nice.

@gvanrossum
Copy link
Member

Let's not do this.

@Pitometsu
Copy link

Pitometsu commented Dec 21, 2017

To avoid unnecessary issues, let me ask here: what is current proper way to compare two TypingMeta objects on covariance/contravariance? I mean like issubclass, but for TypingMeta representation?

E.g. how to compare List[a] and List[b]?

@ilevkivskyi
Copy link
Member

@Pitometsu If you mean a runtime subclass check then this is not supported. There are several runtime type checking libraries like enforce or typecheck-decorator that may help with this, or you can access the type variables manually via __parameters__ and __args__ (but this is reserved internal API). Also note the TypingMeta will not be present in Python 3.7, see PEP 560.

After all, typing is designed primarily to be used with static type checkers like mypy or pytype.

@Pitometsu
Copy link

Pitometsu commented Dec 23, 2017

@ilevkivskyi, thank you for answer. Well, I need it for own DSL implementation (and yes, I use 2.7 yet), so doing check by myself. There's is_subtype in mypy https://github.com/python/mypy/blob/master/mypy/subtypes.py#L45. But I wondered is there common way in typing for this.

@ilevkivskyi
Copy link
Member

But I wondered is there common way in typing for this.

Unfortunately, there are none, you would need to implement your own.

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

6 participants