Useful Python tips and tricks — #3

Johni Douglas Marangon
5 min read2 days ago

--

In this post, I’ll continue take a closer look at some interesting Python code.

Take a look at the my previous posts:

Boosting Performance with Function Caching in Python

Function caching is a technique that allows you to store the results of expensive or frequently called function calls and return the cached result when the same inputs that occur again. This can dramatically improve the performance of functions that are repeatedly called with the same arguments, reducing unnecessary computations.

Python’s standard library provides a convenient tool for caching through functools.lru_cache. This decorator stores results in a Least Recently Used (LRU) cache, meaning it will retain the most recent results and discard the oldest ones when the cache reaches its limit.

Basic Usage of lru_cache

import time
from functools import lru_cache


@lru_cache(maxsize=12) # Cache up to 12 unique results
def fibonacci(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)


# Running first time with cache
start = time.time()
print(fibonacci(35)) # Takes more than 5 seconds the first time
print(f"Time: {time.time() - start:.2f} seconds")

# Running the cached result
start = time.time()
print(fibonacci(35)) # Returns immediately from cache
print(f"Time: {time.time() - start:.2f} seconds")

How It Works

In the example above, the first time the fibonacci is called with a specific argument (e.g., 12), the result is computed and stored in the cache. Any subsequent calls with the same argument will return the cached result instantly.

The maxsize parameter in lru_cache controls how many different results are stored before the cache starts discarding the oldest results.

This is a great technique to optimize code performance without altering the core logic of your functions!

Managing Configuration with Pydantic Settings in Python

When building Python applications, managing configuration settings (like API keys, environment variables, or database connections) can quickly become complicated. Pydantic’s Settings module provides a clean and efficient way to handle configurations by automatically loading and validating settings from environment variables, .env files, or default values.

The BaseSettings class helps structure configurations into strongly-typed, easily accessible models. It ensures that your settings are well-validated and type-safe, avoiding common errors when dealing with configurations.

Basic Usage of Pydantic Settings

pip install -q pydantic-settings
import os

# Define the environment variables.
# You can also place them in a .env file or by using the export command to
# set them directly.
os.environ['DATABASE_URI'] = "file:memdb1?mode=memory&cache=shared"
os.environ['DEBUG_MODE'] = "True"
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
database_uri: str
debug_mode: bool = False

class Config:
env_file = ".env" # Load settings from an optional .env file


# Instantiate and use the settings
settings = Settings()

print(settings.database_uri)
print(settings.debug_mode)

Pydantic’s Settings module is an excellent tool for managing and validating configurations, ensuring that your application remains robust and easy to maintain. Whether you’re building a small script or a large-scale application, this module helps streamline configuration handling.

Understanding Descriptors in Python

Descriptors are a powerful, lesser-known feature in Python that allow customization of attribute access. Descriptor can be used fot validation, caching, or lazy loading. A descriptor is an object that implements one or more of the methods __get__(), __set__(), and __delete__(), which manage the behavior of attribute access. Descriptors can be used to define properties, manage access control, and enforce constraints on class attributes.

Example of descriptor

Below is an example of a descriptor where both __get__() and __set__() are implemented.

class InchToCmDescriptor:
def __set__(self, instance, value):
if not isinstance(value, (int, float)):
raise ValueError("Height must be a number.")
instance._inches = value / 2.54

def __get__(self, instance, owner):
if instance is None:
return self
return instance._inches * 2.54

def __delete__(self, instance):
del instance._inches

class Height:
height_cm = InchToCmDescriptor()

def __init__(self, inches) -> None:
self._inches = inches

height = Height(70)
print(height.height_cm) # Output: 177.8

height.height_cm = 180
print(height._inches) # Output: 70.86614173228346

This example aboce demonstrates a descriptor that automatically converts height measurements between inches and centimeters.

Descriptors in Python provide a powerful way to manage attribute access and enforce specific behaviors or constraints. In this example, InchToCmDescriptor seamlessly handles the conversion between inches and centimeters, enhancing the Height class’s functionality without complicating its interface. Understanding descriptors can help you write more flexible and maintainable object-oriented code in Python.

Pickling and Unpickling

Pickling is the process of serializing a Python object into a byte stream so it can be saved to a file or transmitted over a network. Unpickling is the reverse process, where the byte stream is converted back into a Python object. These processes are facilitated by Python’s pickle module.

Example of Pickling

import pickle


# Define a class with properties and methods
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def to_upper(self):
return self.name.upper()

# Class instance
data = Person("John", 36)

# Open a file in binary write mode
with open("data.pkl", "wb") as file:
# Serialize the class instance and write it to the file
pickle.dump(data, file)

print("Data has been pickled.")

Here, the dictionary data is converted into a binary format and saved as data.pkl. The pickle.dump() function handles the serialization process.

Example of Unpickling

import pickle


# Open the file in binary read mode
with open("data.pkl", "rb") as file:
# Deserialize the data
loaded_data = pickle.load(file)

print("Unpickled Data:", loaded_data)

# Read properties and call methods from unpickled class instance
print(loaded_data.name)
print(loaded_data.age)
print(loaded_data.to_upper())

In this example, the binary data stored in data.pkl is deserialized back into a Python dictionary using pickle.load(). The binary data is a class instance with properties and methods.

Important Notes:

  • Security: Be cautious when unpickling data from untrusted sources, as it can potentially execute arbitrary code.
  • Efficiency: Pickling is efficient for simple data types but can be slower and less portable compared to other serialization formats like JSON.

Pickling is especially useful when you want to save the state of an object and reload it later without losing any information.

Invert Magic Function

In Python, __invert__ is a special method, also known as the bitwise NOT magic method. It is used to implement the bitwise NOT operator (~) for objects that support it. When you apply the ~ operator to an object, Python automatically calls the __invert__ method, passing the object itself as the argument. The method should return a new object that represents the bitwise inversion of the original one.

Here’s an example:

class InverseStr:
def __init__(self, value: str):
self.value = value

def __str__(self) -> str:
return self.value

def __invert__(self) -> 'InverseStr':
return InverseStr(self.value[::-1])

s = InverseStr('hello world')

print(s) # hello world
print(~s) # dlrow olleh

In this example, the __invert__ method reverses the string when the ~ operator is used. This demonstrates how the method can be customized for objects that do not inherently support the bitwise NOT operation, offering flexibility in handling such operations.

Connect with Me:

For a deeper look into the software engineering industry, I’d be happy to share my thoughts and insights with you.

Consider following me on Medium for regular updates and expert perspectives.

If you found this post helpful or enjoyable, please give me feedback by clapping below.

Happy learning 📢.

--

--