How to determine Python function return and argument types?

How can I determine the Python return type and argument types for functions?

While I am familiar with the concept of duck-typing in Python, I sometimes struggle with identifying the argument types for functions or the return type of a function.

If I wrote the function myself, I am aware of the types. But what if someone else wants to use and call my functions? How are they supposed to know the argument types and the return type? I usually provide type information in the function’s docstring (e.g., “…the id argument should be an integer…” and “…the function will return a (string, [integer]) tuple.”).

But is looking up this information in the docstring (and including it as a developer) really the standard practice?

I’ve been working with Python for years, and one of the cleanest ways to handle argument and return types is by using type hints (introduced in Python 3.5). They make your code more readable and enable tools like mypy to help catch type errors early.

from typing import List, Tuple

def process_data(id: int, name: str) -> Tuple[str, List[int]]:
    return name, [id]

Type hints are great for keeping your codebase consistent and avoiding confusion, especially in team projects.

Building on what Devan said, while type hints are fantastic, sometimes the data can get too complex to describe concisely. In those cases, docstrings come in handy. Docstrings allow you to provide detailed explanations about argument and return types, which can be incredibly useful when you’re dealing with complex structures.

from typing import List, Tuple

def get_nodes() -> List[Tuple[str, str, int]]:
    """
    Returns a list of tuples, where each tuple contains:
    - node_id (str)
    - node_name (str)
    - uptime_minutes (int)
    """
    return [("node1", "Node 1", 120), ("node2", "Node 2", 300)]

This way, your documentation complements the type hints, making it easier for others (and your future self) to understand the function.

Let me take this one step further. If the data you’re working with is really complex, you might want to define custom classes. This gives your code more structure and makes it much easier to manage and understand over time.

from typing import List

class Node:
    def __init__(self, node_id: str, node_name: str, uptime_minutes: int):
        self.node_id = node_id
        self.node_name = node_name
        self.uptime_minutes = uptime_minutes

def get_nodes() -> List[Node]:
    return [Node("node1", "Node 1", 120), Node("node2", "Node 2", 300)]

Using custom classes not only improves readability but also allows you to add methods and validations, making your code more robust and future-proof.