Skip to main content

Command Palette

Search for a command to run...

Generic Typing in Python

Updated
5 min read
Generic Typing in Python
S

A passionate software developer who enjoys sharing his thoughts with others and learning from them vise versa!

Since Python introduced the ability to add generic types to functions, the language has become much more type-friendly and encourages you to follow this convention for more maintainable code. Obviously, this will lead to a better development experience.

Stay with me as we discuss the basics of Generic Typing in Python and some best practices at the end.

What Does Generic Typing Help With?

This fascinating feature allows you to make type-checking more dynamic. Sometimes, we don't know the types of input parameters.

Before we continue, I encourage you to take a moment to try solving this problem.

How would you type-hint a function that takes one parameter and that’s a list of objects of any type (int, str, User, Book, etc) and returns just one random object of that type?

Any Turns off the Type-Checking!

During that challenge, you might have considered the following thought processes. Let's take a look.

Thought Process 1:

My function should take one parameter, which is a list of any type (List[Any]), and return a random single item from that list (Any).

def select(items: List[Any]) -> Any:
    return random.choice(items)

Answer:

This is incorrect. Although your type-checker might still pass the type-checking, there is a significant mistake in this typing. List[Any] means a list that can be empty or filled with items of any type. This allows the function to be called in a way that violates the requirement that the parameter should be a list of items of the same type.

select([1, "b", "c", "d", False, 3.14])

The type Any somehow neutralizes the type-checker and hides its pointer from being type-checked.

item: Any = ...  # just like we've put `type: ignore` here

Be very careful with Any and make sure you really need its effect.


Thought Process 2:

I can explicitly define what type my parameter can take. That way, not only I haven’t used the type Any, I have explicitly defined the input and output type.

def select(items: List[int | str | bool | float]) -> int | str | bool | float:
    return random.choice(items)

Answer:

This is a better solution, but it still doesn't fully meet the challenge where we said the function parameter could be anything. It might not be a string or an integer, for example. This function only works for the built-in types. What if I wanted to call it like this?

from typing import NewType

def select(...):
    .

User = NewType("User", str)
users = [User("Abby"), User("Chris"), User("Nick")]

user = select(users)
file.py:12: error: Argument 1 to "select" has incompatible type "list[User]"; expected "list[int | str | bool | float]"  [arg-type]

Not only did our type-checker (mypy) fail to check it, but it also doesn't meet the challenge.

Using Generic Typing

It’s a newly added syntax to Python. Yet I haven’t seen many people using it as they are still releasing for older Python versions like 3.8, 3.9, 3.10, and 3.11. Generic typing was introduced in Python 3.12.

Now, let’s talk about how we can use this feature to solve the challenge we described earlier.

Structure and Syntax

The structure is quite simple. You define new type variables and use them accordingly when defining the function. Just like defining variables that don't have a value at the time of declaration, you define placeholders in brackets right before the parentheses. (Here, I've declared only one variable/placeholder named T)

def FUNCTION_NAME[TYPES](ARGS..) -> TYPE:
def select[T](items...

And you can use it later when specifying the type of parameters and the return value of your function.

def select[T](items: List[T]) -> T:
    ...

Now, let’s define the above function. It has declared one T type variable and says, the items parameter can be a list of anything and I’ll consider that as T for now. That T is the placeholder for any time that items might have.

select(items=[1, 2, 3, 4, 5]) # T=int   &  List[T] = List[int]
select(items=["a", "b", "c"]) # T=str   &  List[T] = List[str]
select(items=[1.2, 1.3, 1.4]) # T=float &  List[T] = List[Float]

# Therefore, we have..
users = [User("Abby"), User("Chris"), User("Nick")]
select(items=users)           # T=User  &  List[T] = List[User]

As you can see, the type represented by T changes dynamically based on the values we put in the items parameter. We can now explain the select() function like this:

  • Function definition: def select[T](items: List[T]) -> T:

  • Type variable(s)/placeholder(s): T

  • Input type: List of any similar type

  • Output type: A type similar to one of the list items

Let’s Practice

P1: Now, let's say our function takes only one item and returns a tuple containing three of that item.

Answer:

def convert[B](item: B) -> Tuple[B, B, B]:
    return (item, item, item)

Hint: We use the letter "T" as the default primary type placeholder by convention, but you can choose any name you prefer as in the above example, we used the letter “B”.

P2: Let’s say we need a function that takes two parameters of different types and returns a tuple containing those items.

Answer:

def convert[A, B](first: A, second: B) -> Tuple[A, B]:
    return (first, second)

P3: A function that takes an ID and a list of dictionaries, where each dictionary has exactly three pairs (id, username, and is_verified), and returns the item that matches the given ID.

Hint: Avoid defining the type Users in this example to better understand Generic Typing. Defining it would make Generics seem unnecessary in this case.

Answer: You figure it out! :))

Conclusion

Generic Typing is a pretty new and hot topic in Python. It's helpful when defining functions and classes. In this article, we explored the function side of this powerful syntax. Hopefully, you found it useful.

E

For many people and businesses, navigating the frequently dangerous landscape of financial loss can be an intimidating and overwhelming process. Nevertheless, the knowledgeable staff at Wizard Hilton Cyber Tech provides a ray of hope and direction with their indispensable range of services. Their offerings are based on a profound grasp of the far-reaching and terrible effects that financial setbacks, whether they be the result of cyberattacks, data breaches, or other unforeseen tragedies, can have. Their highly-trained analysts work tirelessly to assess the scope of the damage, identifying the root causes and developing tailored strategies to mitigate the fallout. From recovering lost or corrupted data to restoring compromised systems and securing networks, Wizard Hilton Cyber Tech employs the latest cutting-edge technologies and industry best practices to help clients regain their financial footing. But their support goes beyond the technical realm, as their compassionate case managers provide a empathetic ear and practical advice to navigate the emotional and logistical challenges that often accompany financial upheaval. With a steadfast commitment to client success, Wizard Hilton Cyber Tech is a trusted partner in weathering the storm of financial loss, offering the essential services and peace of mind needed to emerge stronger and more resilient than before. Call; Email : wizardhiltoncybertech ( @ ) gmail (. ) com
OR support ( @ ) wizardhiltoncybertech (.) com

WhatsApp number +13024457895

J
jun hu1y ago

关于通用类型的精彩文章,以简单且用户友好的格式呈现。感谢 Sadra 准备这篇文章

S

Useful article about generic typing. Thanks for that.

10
N

Generic Typing in Python

Generic typing in Python allows developers to create more flexible and reusable code by defining types that work with multiple data types. This is especially useful for functions and classes that need to handle a variety of types but still enforce type safety. Here are the main elements of generic typing:

  1. Type Variables (TypeVar):
    A TypeVar allows you to define a generic type variable that can be used to create functions or classes that work with multiple types. For example:

    from typing import TypeVar, List
    
    T = TypeVar('T')  # Generic type variable
    
    def get_first_element(lst: List[T]) -> T:
        return lst[0]
    

    Here, T can be any type, and get_first_element can be used with lists of any type.

  2. Generic Classes: You can create classes that operate on multiple types by using TypeVar within the class definition. For example:

    from typing import Generic
    
    T = TypeVar('T')
    
    class Stack(Generic[T]):
        def __init__(self):
            self.items: List[T] = []
    
        def push(self, item: T):
            self.items.append(item)
    
        def pop(self) -> T:
            return self.items.pop()
    

    This Stack class can hold elements of any type, and the type is enforced by TypeVar.

  3. Union and Optional: When a function can accept more than one type, Union can be used to specify multiple allowed types, while Optional is shorthand for Union[type, None]. For example:

    from typing import Union, Optional
    
    def display(value: Union[int, str]):
        print(value)
    
    def get_username(user_id: int) -> Optional[str]:
        # returns a string or None
        return None  # or some string based on logic
    
  4. Parameterized Collections: Generic types can also be used with built-in collections like List, Dict, and Tuple. For example:

    from typing import List, Dict
    
    def process_items(items: List[str]):
        # List containing only strings
        for item in items:
            print(item.upper())
    

Using generics improves code readability and reliability by ensuring types are consistent and well-defined. This is particularly valuable in large codebases and projects where type safety helps avoid errors freddys-menu.com

D
Danyal T1y ago

Great article on generic typing, presented in a simple and user-friendly format. Thank you, Sadra, for preparing this!

11
S

You're welcome. Glad you found it useful. 😃