Blog

article4 min read

10 Things You Didn't Know You Could Do in Python3

Lesser-known Python features that quietly make production code cleaner.

First published on
Medium · Python in Plain English
Also on
DEV

Python code on a monitor, seen through a pair of glasses Photo by Alex Chumak on Unsplash

Python is the third most commonly-used programming language. It has introduced many new features, simplifying and enhancing the development process. Yet, many developers remain unaware of the full potential that Python 3 has to offer.

This article will uncover ten lesser-known and underutilized capabilities in Python (v3.5 to v3.11). These hidden gems can improve your coding efficiency and productivity. So, without further ado, let’s dive into these fascinating Python 3 features.

1. Walrus Operator

It’s a new syntax introduced in Python 3.11 that assigns values to variables as part of a larger expression.

Old way

a = "This is a sample string text"

if len(a) > 10:
    print("Length of string a = ", len(a))

New way

a = "This is a sample string text"

if (len_a := len(a)) > 10:  # Note the walrus operator will compute value of len(a) and assign it to variable len_a
    print("Length of string a = ", len_a)

2. Structural Pattern Matching

You might remember reading that Python doesn’t support switch statements because if-else can achieve the same result. You are in for a treat. Python3.10 introduced something similar.

Old way

def http_error(status):
    if status == 400:
        return "Bad request"
    elif status == 404:
        return "Not found"
    elif status == 418:
        return "I'm a teapot"
    else:
        return "Something's wrong with the internet"

New way

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:  # This is a wildcard operator
            return "Something's wrong with the internet"

3. Merging Dictionaries

Python3.9 introduced a new way of merging or updating dictionaries. It complements the existing dict.update and {**d1, **d2} methods of merging dictionaries.

Old way

x = {"key1": "value1 from x", "key2": "value2 from x"}
y = {"key2": "value2 from y", "key3": "value3 from y"}

print({**x, **y})
print({**y, **x})  # Note here keys of y would be given preference over keys of x

New way

x = {"key1": "value1 from x", "key2": "value2 from x"}
y = {"key2": "value2 from y", "key3": "value3 from y"}

# Note the usage of OR (|) operator
print(x | y)
print(y | x)  # Note here keys of y would be given preference over keys of x

4. Removing Prefix or Suffix

Python3.9 introduced dedicated methods to get rid of prefixes or suffixes in strings.

Old way

x = "prefixstring"
y = "stringsuffix"

print(x.split("prefix")[-1])
print(y.split("suffix")[0])

New way

x = "prefixstring"
y = "stringsuffix"

print(x.removeprefix("prefix"))
print(y.removesuffix("suffix"))

5. Positional Only Parameters

Python provides to define positional and keyword arguments for a function. But you can pass them interchangeably. Python3.8 introduced a way to enforce that you cannot pass positional arguments as keyword arguments.

Old way

def fun(pos_arg1, pos_arg2, key_arg_1 = None):
    print("positional arguments: ", pos_arg1, pos_arg2)
    print("Keyword arguments: ", key_arg_1)

# It will work
fun(1, 2, 3)  # Passing keyword argument as positional argument

# It will also work
fun(pos_arg1=1, pos_arg2=2, key_arg_1=3)  # Passing positional argument as keyword argument

New way

def fun(pos_arg1, pos_arg2, /, key_arg_1 = None):
    print("positional arguments: ", pos_arg1, pos_arg2)
    print("Keyword arguments: ", key_arg_1)

# It will work
fun(1, 2, 3)  # Passing keyword argument as positional argument

# It won't work
fun(1, pos_arg2=2, key_arg_1=3)  # Passing one positional argument as keyword argument
fun(pos_arg1=1, pos_arg2=2, key_arg_1=3)  # Passing both positional arguments as keyword argument

6. Time with nanoseconds Precision

Did you ever need nanosecond precision in getting time to compare code performances? Python3.7 introduced new methods in the time library.

Old way

import time

start = time.time()
end = time.time()

print(end - start)
# Provides sub-second precision, though that precision varies by platform
# >> 2.7179718017578125e-05 Notice the precision is in e-05

New way

import time

start = time.time_ns()
end = time.time_ns()

print(end - start)
# Provides nanosecond precision
# >> 47000 Notice the figure is in nanoseconds

7. f-Strings

Python3.6 introduced f-strings to provide an easy way of formatting strings.

Old way

a = 1

print("Value of a = {}".format(a))

New way

a = 1

print(f"Value of a = {a}")

8. Underscores in Numeric Literals

Python 3.6 introduced allowing adding underscores in numeric literals for better readability.

Old way

a = 1000000000000000  # try counting the number of 0's

print(type(a))
# >> <class 'int'>

New way

a = 1_000_000_000_000_000

print(type(a))
# >> <class 'int'>

9. Matrix Multiplication Operator

Python3.5 introduced a dedicated @ infix operator for matrix multiplication.

Old way

import numpy

x = numpy.ones(3)
m = numpy.eye(3)

print(x.dot(m))

New way

import numpy  # NumPy 1.10 has support for the new operator:

x = numpy.ones(3)
m = numpy.eye(3)

print(x @ m)  # @ is the new matrix matrix-multiplication operator

10. Approximate Equality

Python3.5 introduced dedicated methods to tell whether two values are approximately equal or “close” to each other.

Old way

a = 5.0
b = 4.99998

print(abs(a - b) <= 1e-5)

New way

import math

a = 5.0
b = 4.99998

print(math.isclose(a, b, rel_tol=1e-5))

Resources

  1. Python3.11 Change-log
  2. Python3.10 Change-log
  3. Python3.9 Change-log
  4. Python3.8 Change-log
  5. Python3.7 Change-log
  6. Python3.6 Change-log
  7. Python3.5 Change-log