24

Here is a minimal case.

def foo(x:int, y:int) -> tuple: 
    return (x*y, y//2)

It's very tempting to be able to write -> tuple(:int, :int) which is not a valid format. Is there a correct approach in this case, or is it still a gray area until python moves further down the type annotation road?

edit: It's apparently possible to do something of the like

def bar(x, y) -> ((str, int), (str, int)): 
     return ("%s+%s" %(x,y), x+y), ("%s-%s" %(x,y), x-y) 
2
  • See the relevant PEP, which has recently seen a lot of development. If you're really curious, you can also check out the python-ideas list archives, where Guido started a thread about this PEP in mid-January 2015. Commented Jan 30, 2015 at 20:14
  • Since python 3.9 PEP 585 we can write tuple[int, int] Commented Apr 30, 2021 at 12:07

3 Answers 3

41

There is now a way to annotate this case:

from typing import Tuple
def foo(x:int, y:int) -> Tuple[int, int]: 
    return (x*y, y//2)

Note: python run-time won't complain about -> (int, int), but it is not correct type annotation according to PEP 484. In other words, you can use it if you want to create your a mini-language using type hints for your own purposes; but it's not accepted by the python typing standard (PEP 484).

Sign up to request clarification or add additional context in comments.

5 Comments

how to annotate [[str, int]] ? Can i use Tuple despite the element [str, int] being list like List[Tuple[str,int]]?
@qrtLs No you cannot. Python type annotations can only represent a list where all items have the same type. There is no way to say that my_list is a list with two items of type str and int. Rationale: a variable cannot change its type, and it's far too common for a list to change its length. If length can change, and items are not of the same type, it's unclear what the type system should do when user attempts to append say an int to a List[int, str]. The chosen solution is that you cannot specify length at all, and the type of all items has to be the same.
@qrtLs But you can and maybe should just use an actual tuple in your return value: if the list is always two elements, and they always have the same type, why is it a list to begin with? To make it easier for the function that receives it to mutate it? Is that a strong enough reason in your case.
Since python 3.9 PEP 585 we can write tuple[int, int]
What if the tuple elements can also be None? Can we do something like -> (int | None, int | None)?
20

typing.Tuple is now deprecated as of Python 3.9. You should use the built-in tuple type instead (no import needed):

def foo(x:int, y:int) -> tuple[int, int]: 
    return (x*y, y//2)

The docs for typing.Tuple state:

Deprecated since version 3.9: builtins.tuple now supports subscripting ([]). See PEP 585 and Generic Alias Type.

2 Comments

Note: tuple(int, int) is incorrect format. Python will complain: tuple expected at most 1 argument, got 2
You should use tuple[int, int], not tuple(int, int). The code in the answer typechecks correctly -- see this mypy playground link.
3

Edit (2023): Although this answer was useful when it was posted and is still historically interesting, its advice is now somewhere between irrelevant, misleading, and harmful. Other answers already mention that PEP-484 formalized a different syntax since this was posted.

No, there is not a canonical way to do this as of yet. Python type annotations are a relatively new addition to the language, so they are still somewhat limited.

For now, you could maybe use a tuple literal:

def foo(x:int, y:int) -> (int, int):
    return (x*y, y//2)

That, or a string literal such as:

def foo(x:int, y:int) -> 'tuple(int, int)':
    return (x*y, y//2)

Both of these convey your intention pretty clearly.

9 Comments

I'm still hoping the actual input and return type will be enforced at some point, rather than just serving as a fancy comment syntax.
Eh, I'm not very eager for that. It defeats the purpose of Python's dynamic typing and ruins a great feature of the language. I mean, if you want static typing, why not use Java or C++ (which are both generally faster)? Python is great because it is dynamic. Of course, that's just my opinion. :)
I should clarify, I only meant enforced (on annotated code) - it's really weird for me that I can write def foo() -> int: and return a str; granted that probably doesn't happen often; but def foo() -> int returning a float probably does.
In light of the recent developments in PEP 484, this answer might be a little out of date. See my comment on OP's question.
@user3467349: Python, as we know it, is never going to have enforced static types. That's just not what Python is. If you follow the development of Python, you'll get a visceral sense of just how adamant Guido and the core developers are that these are annotations, not declarations. But if you like static typing, or even hybrid systems which have optional, cherry-picked static typing, you have other choices besides Python. The two that come most quickly to mind are Cython and Nim (formerly Nimrod), and they are by no means the only ones.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.