DEV Community

Cover image for Most Exciting Python Features from 3.7 to 3.11
Yash Makan
Yash Makan

Posted on

Most Exciting Python Features from 3.7 to 3.11

Introduction

Hey innovators, I am Yash Makan and in today’s blog post we are going to discuss about some of the cool and helpful features that you won’t wanna miss, to add in your coding cookbooks. I’ll be mentioning 10 interesting features based on each latest python version that will make you a python guru. So without any more introduction, let’s dive in.

https://i.giphy.com/media/OuePMznpbHyrw34608/giphy.webp

1. Adding Notes When Catching Exception

This feature of adding notes to python exceptions is added in the version 3.11. Now, developers can add more detailed information while handling exceptions to make it much more easier in the debugging stage. It can be utilized by adding add_note method to the Exception class.



try:
raise TypeError
except Exception as e:
e.add_notes("More Information regarding error")
e.add_notes("Why not add some more info?")

----------------------------------------------------
Trackback (most recent call last):
File "/main.py", 4, in <module>
raise TypeError
TypeError
More Information regarding error
Why not add some more info?

Enter fullscreen mode Exit fullscreen mode



  1. Grouping Asynchronous Tasks

Do you know, from python 3.11, we can group asynchronous tasks together using the TaskGroup class. This class helps in running multiple coroutines and wait for all those coroutines to finish. This can be used in multiple scenarios where multiple coroutines are used. Let’s see an example to better understand.



import asyncio

async def coroutine_1():
await asyncio.sleep(1)
print("Coroutine 1")

async def coroutine_2():
await asyncio.sleep(2)
print("Coroutine 2")

async def main():
async with asyncio.TaskGroup() as task_group:
await task_group.create_task(coroutine_1())
await task_group.create_task(coroutine_2())
print("Both tasks are done now.")

asyncio.run(main())

----------------------------------------------------
Coroutine 1
Coroutine 2
Both tasks are done now.

Enter fullscreen mode Exit fullscreen mode



  1. square² and ∛cube root

Now python 3.11 has introduced a brand new update where developers can square or cube root any number.

I know what you guys are thinking but jokes aside, now we have inbuilt functions in the math module which helps us to do the exact same thing.



>>> import math
>>> math.exp2(4)
16.0
>>> math.cbrt(8)
2.0

Enter fullscreen mode Exit fullscreen mode



  1. Pattern Matching

Have you ever heard of switch case in other languages and always felt that python is missing out on this feature. If so, then worry no more. Python 3.10+, now developers can use match-case which is similar to switch case but is much more better. In the case, you can compare not only literals but also tuples, variables etc. Let’s see an example to better understand.



# point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"x=0,y={y}")
case (x, 0):
print(f"x={x},y=0")
case (x, y):
print(f"x={x},y={y}")
case _:
raise ValueError("Not a point")

Enter fullscreen mode Exit fullscreen mode



  1. Union Alternative

You must have used Union from typing to tell compiler that the argument can either be int or float. Now from python 3.10, there’s a better way to do this. Instead of importing the Union class like from typing import Union You can use the same functionality with the | symbol.



from typing import Union
def square(number: Union[int, float]) -> Union[int, float]:
return number ** 2

vs

def square(number: int | float) -> int | float:
return number ** 2

Enter fullscreen mode Exit fullscreen mode



  1. Dictionary Merge Operator

From python 3.9, merging two dictionaries together is much easier because of the dict merge operator. Earlier, if we want to merge two dictionaries we can do that by dict3 = {**dict1, **dict2} but now we can use the | symbol to merge two dictionaries.



>>> dict1 = {"a": 1, "b": 2}
>>> dict2 = {"b": 3, "c": 4}
>>> {dict1, dict2}
{"a": 1, "b": 3, "c": 4}
>>> dict1 | dict2 # New way
{"a": 1, "b": 3, "c": 4}

Enter fullscreen mode Exit fullscreen mode



  1. Walrus Operator

The walrus operator allows developers, to assign and return value both at the same time. This feature was introduced in python 3.8. Let’s see an example to understand the := operator,



# Before Python 3.8
>>> numbers = [2, 8, 0, 1, 1, 9, 7, 7]
>>> num_length = len(numbers)
>>> num_sum = sum(numbers)
>>> description = {
... "length": num_length,
... "sum": num_sum,
... "mean": num_sum / num_length,
... }
>>> description
{'length': 8, 'sum': 35, 'mean': 4.375}

# With Walrus Operator
>>> numbers = [2, 8, 0, 1, 1, 9, 7, 7]
>>> description = {
... "length": (num_length := len(numbers)), # Here we are initializing and using both at the same time
... "sum": (num_sum := sum(numbers)),
... "mean": num_sum / num_length,
... }
>>> description
{'length': 8, 'sum': 35, 'mean': 4.375}

Enter fullscreen mode Exit fullscreen mode



  1. f-strings support = for self-documenting

In python 3.8, other interesting feature added is the f-string support for self documenting and debugging. Many of the times developers use print(f"check {variable}") to debug if the variable value is updated or not. Now, python 3.8 introduced a much better way to debug this exact same thing.



variable = get_data()
print(f"{variable=}") # variable='Hello World!'

Enter fullscreen mode Exit fullscreen mode



  1. Positional-only Parameters

In Python 3.8, positional-only parameter feature is added. Now, in a def function we can define which of our parameters can be positional only and keyword only parameters. This is done by separating the parameters by a / Let’s see an example,



def foo(a, b, /, **kwargs):
print(a, b, kwargs)

>>> foo(10, 20, c=30, d=40, e=50) # correct as a and b must be a positional argument
10, 20, {'c': 30, 'd': 40,'e': 50}

Enter fullscreen mode Exit fullscreen mode



  1. Dataclasses

In Python 3.7, dataclasses are introduced. This feature will save you from writing ton of boilerplate code and makes your classes neat. This feature is one of may favorite as well. We can simply add the dataclass decorator to our class and it can generate most of the magic methods on it’s own such as __repr__() __eq__() and __hash__() Let’s see an example,



from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
z: float = 0.0

p = Point(1.5, 2.5)
print(p) # Point(x=1.5, y=2.5, z=0.0)

Enter fullscreen mode Exit fullscreen mode




Conclusion

So in today’s article we have covered python’s most exciting features from Python 3.7 to 3.11 . Now Of course, there is a huge list of features that has been introduced which I haven’t mentioned, so let me know in the discussion below which of the features I have missed and that you love the most in python. I hope you liked my blog and if this article adds any value then make sure to spread it with your friends and don’t forget to bookmark. If you have any questions, let me know via Twitter or in the discussion below. If you’d like more content like this, I post on YouTube too. Till then b-bye!!

Top comments (1)

Collapse
 
yawaramin profile image
Yawar Amin • Edited

Thanks for this review. All interesting features. Re dataclasses, check out typing.NamedTuple also:

from typing import NamedTuple

class Point(NamedTuple):
  x: float
  y: float
  z: float = 0

p = Point(1.5, 2.5)
print(p)
Enter fullscreen mode Exit fullscreen mode

I believe NamedTuple is more efficient than dataclasses as it defines __slots__ for the class instance variables. Otherwise it offers equal power and better integration with the typechecker.