16. June 2024

Python language

Perfect higher level language for aspiring developers, highly used in data science

Introduction

Python is a high-level, interpreted programming language known for its simplicity and readability. Created by Guido van Rossum and first released in 1991, Python's design philosophy emphasizes code readability and syntax that allows programmers to express concepts in fewer lines of code.

What is Python?

Python is a versatile, general-purpose programming language that supports multiple programming paradigms, including procedural, object-oriented, and functional programming. It is widely used in web development, data science, artificial intelligence, scientific computing, and more. Python's extensive standard library and active community contribute to its popularity and rapid development.

The Concept Behind Python

The core concept behind Python is to provide a language that is easy to read and write, making it accessible for beginners while powerful enough for experienced developers. Python emphasizes code readability, simplicity, and productivity, allowing developers to write clean and maintainable code efficiently.

Python is an interpreted language, meaning that code is executed line by line, which makes debugging easier and accelerates development.

Basic Syntax and Structure

A basic Python program consists of functions and statements.

✔ Data Types

Python provides several built-in data types to handle different kinds of data:

✔ Control Structures

Python provides various control structures to manage the flow of the program:

✔ Operators

Python supports various operators for performing operations on data:

✔ Comments

Python allows comments to be added to code using the # symbol. Comments are ignored by the interpreter and are used to document code or provide explanations. For multiline comments, you can use triple quotes ''' or """ to start and end the comment block.
Examples:


# This is a single-line comment
'''This is a
multiline comment'''

✔ Input and Output

Python provides functions for reading input from the user and displaying output:

✔ Functions

Functions in Python allow for modular programming by breaking down code into reusable blocks. A function definition includes the def keyword, function name, parameters, and body. Functions promote code reuse, readability, and ease of maintenance.

Basic Function

A basic function definition includes the def keyword, followed by the function name, parentheses containing parameters, and a colon. The function body contains the code to be executed.


def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

'''Output:
Hello, Alice!
'''

Returning Values

Functions can return values using the return statement. This allows a function to send a result back to the caller, which can then be used or stored.


def add(a, b):
    return a + b

result = add(3, 5)
print(result)

'''Output:
8
'''

Default Parameters

Functions can have default parameter values, which are used if no argument is provided by the caller.


def greet(name="World"):
    print(f"Hello, {name}!")

greet()
greet("Alice")

'''Output:
Hello, World!
Hello, Alice!
'''

Variable-Length Arguments

Python allows functions to accept a variable number of arguments using *args and **kwargs.

Using *args

The *args parameter allows a function to accept any number of positional arguments, which are accessible as a tuple.


def multiply(*args):
    result = 1
    for num in args:
        result *= num
    return result

print(multiply(1, 2, 3, 4))

'''Output:
24
'''
Using **kwargs

The **kwargs parameter allows a function to accept any number of keyword arguments, which are accessible as a dictionary.


def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="Wonderland")

'''Output:
name: Alice
age: 25
city: Wonderland
'''
Combining *args and **kwargs

Functions can use both *args and **kwargs to accept an arbitrary number of positional and keyword arguments.


def show_details(*args, **kwargs):
    print("Positional arguments:", args)
    print("Keyword arguments:", kwargs)

show_details(1, 2, 3, name="Alice", age=25)

'''Output:
Positional arguments: (1, 2, 3)
Keyword arguments: {'name': 'Alice', 'age': 25}
'''

Lambda Functions

Lambda functions are small anonymous functions defined using the lambda keyword. They can have any number of parameters but only one expression.


add = lambda x, y: x + y
print(add(3, 5))

'''Output:
8
'''

Nested Functions

Functions can be defined within other functions, creating nested functions. The inner functions have access to the variables of the enclosing scope.


def outer_function(text):
    def inner_function():
        print(text)
    inner_function()

outer_function("Hello from inner function")

'''Output:
Hello from inner function
'''

Closures

Closures are functions that retain the values of the variables from their enclosing scope even after the outer function has finished executing.


def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

times3 = make_multiplier(3)
print(times3(10))

'''Output:
30
'''

Recursion

A function that calls itself is known as a recursive function. Recursion can be used to solve problems that can be broken down into smaller, repetitive problems.


def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

print(factorial(5))

'''Output:
120
'''

Understanding functions in Python, including the use of *args and **kwargs, allows for writing flexible and reusable code. These concepts are fundamental for effective programming and are widely used in Python development.

✔ Exception Handling

Python provides a way to handle errors and exceptions gracefully using try-except blocks. This helps in managing unexpected events and errors during program execution.
Example:


try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide by zero!")
finally:
    print("This will always execute.")

'''Output:
Cannot divide by zero!
This will always execute.
'''

✔ Decorators

Decorators are a powerful feature in Python that allows the modification of functions or methods using other functions. They provide a way to add functionality to existing functions without modifying their code. Decorators are commonly used for logging, access control, memoization, and more.

Basic Decorator Example

A simple decorator takes a function as input, defines an inner function that adds some behavior, and returns the inner function.


def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

'''Output:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
'''

Decorators with Arguments

Decorators can also take arguments. This requires an additional outer function to accept the arguments and return the actual decorator.


def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                func(*args, **kwargs)
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

'''Output:
Hello, Alice!
Hello, Alice!
Hello, Alice!
'''

Function Argument Passing

Decorators can be used to modify functions that accept arguments. The inner function must accept *args and **kwargs and pass them to the original function.


def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Arguments were: ", args, kwargs)
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def add(a, b):
    return a + b

result = add(5, 3)
print(result)

'''Output:
Arguments were:  (5, 3) {}
8
'''

Decorating Methods

Decorators can also be applied to methods within classes. The self parameter must be preserved in the inner function.


def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Calling method")
        return func(*args, **kwargs)
    return wrapper

class MyClass:
    @my_decorator
    def method(self, x):
        print(f"Method called with argument: {x}")

obj = MyClass()
obj.method(5)

'''Output:
Calling method
Method called with argument: 5
'''

Built-in Decorators

Python provides several built-in decorators like @staticmethod, @classmethod, and @property.


class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

c = Circle(5)
print(c.radius)
c.radius = 10
print(c.radius)

'''Output:
5
10
'''

Chaining Decorators

Multiple decorators can be applied to a single function by stacking them.


def uppercase(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def split_string(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.split()
    return wrapper

@split_string
@uppercase
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))

'''Output:
['HELLO,', 'ALICE']
'''

Decorators are a versatile and powerful tool in Python, enabling you to add functionality to existing code in a clean and readable manner. They are widely used in various Python frameworks and libraries, making them an essential concept to understand.

Classes and Objects

Python supports object-oriented programming (OOP), allowing developers to define their own data types using classes. A class is a blueprint for creating objects, providing initial values for state (member variables or attributes), and implementations of behavior (member functions or methods). An object is an instance of a class.

✔ Defining a Class

A class is defined using the class keyword, followed by the class name and a colon. The class body contains method definitions and variable declarations.


class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"Hello, my name is {self.name} and I am {self.age} years old.")

✔ Creating an Object

An object is created by calling the class name followed by parentheses, which invokes the __init__ method to initialize the object's attributes.


p1 = Person("John", 30)
p1.introduce()

'''Output:
Hello, my name is John and I am 30 years old.
'''

✔ Instance Variables

Instance variables are unique to each instance of a class. They are usually defined within the __init__ method and are accessed using the self keyword.


class Car:
    def __init__(self, make, model):
        self.make = make
        self.model = model

car1 = Car("Toyota", "Corolla")
car2 = Car("Honda", "Civic")
print(car1.make, car1.model)
print(car2.make, car2.model)

'''Output:
Toyota Corolla
Honda Civic
'''

✔ Class Variables

Class variables are shared across all instances of a class. They are defined within the class but outside any instance methods.


class Dog:
    species = "Canis familiaris"

    def __init__(self, name, age):
        self.name = name
        self.age = age

dog1 = Dog("Buddy", 5)
dog2 = Dog("Lucy", 3)
print(dog1.species)
print(dog2.species)

'''Output:
Canis familiaris
Canis familiaris
'''

✔ Methods

Methods are functions defined within a class that describe the behaviors of an object. They are accessed using the dot notation and can modify the object's state or perform actions.


class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

rect1 = Rectangle(3, 4)
print(rect1.area())

'''Output:
12
'''

✔ Inheritance

Inheritance allows a class to inherit attributes and methods from another class, promoting code reuse. The class being inherited from is called the parent or base class, and the class inheriting is called the child or derived class.


class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.name, dog.speak())
print(cat.name, cat.speak())

'''Output:
Buddy Woof!
Whiskers Meow!
'''

✔ Polymorphism

Polymorphism allows different classes to be treated as instances of the same class through a common interface. This is often achieved by method overriding, where a child class provides a specific implementation of a method that is already defined in its parent class.


class Bird:
    def __init__(self, name):
        self.name = name

    def fly(self):
        return "Flying"

class Sparrow(Bird):
    def fly(self):
        return "Flying short distances"

class Eagle(Bird):
    def fly(self):
        return "Flying high"

birds = [Sparrow("Jack"), Eagle("Baldie")]
for bird in birds:
    print(f"{bird.name}: {bird.fly()}")

'''Output:
Jack: Flying short distances
Baldie: Flying high
'''

✔ Encapsulation

Encapsulation restricts access to certain components of an object and prevents accidental modification. It is implemented by prefixing an attribute with a single or double underscore.


class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    def deposit(self, amount):
        self.__balance += amount

    def withdraw(self, amount):
        if amount <= self.__balance:
            self.__balance -= amount
            return amount
        else:
            return "Insufficient funds"

    def get_balance(self):
        return self.__balance

account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.get_balance())
print(account.withdraw(300))
print(account.get_balance())

'''Output:
1500
300
1200
'''

✔ Abstract Classes

Abstract classes cannot be instantiated and are used to define a common interface for subclasses. They contain abstract methods that must be implemented by the derived classes. Abstract classes are created using the abc module.


from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * (self.radius ** 2)

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side * self.side

shapes = [Circle(5), Square(4)]
for shape in shapes:
    print(f"Area: {shape.area()}")

'''Output:
Area: 78.5
Area: 16
'''

Modules and Packages

Python allows code to be organized into modules and packages, promoting code reuse and modularity. A module is a single file containing Python code, while a package is a collection of modules.
Example of importing a module:


import math
print(math.sqrt(16))

Example of importing a package:


from package_name import module_name

Standard Library

Python's standard library provides a wide range of modules and functions that can be used to perform various tasks without the need for external libraries. Some commonly used functions include:

Generate a sequence of numbers: range()
Get the length of an object: len()
Calculate the sum of a sequence: sum()
Find the minimum value in a sequence: min()
Find the maximum value in a sequence: max()
Get the absolute value of a number: abs()
Round a number to a specified precision: round()
Sort a sequence: sorted()
Reverse a sequence: reversed()
Combine multiple sequences into tuples: zip()
Apply a function to each item in a sequence: map()
Filter items in a sequence based on a condition: filter()
Enumerate items in a sequence: enumerate()
Check if all items in a sequence are true: all()
Check if any item in a sequence is true: any()
Get the type of an object: type()
Check if an object is an instance of a class: isinstance()
Get a list of attributes and methods of an object: dir()
Get help information for an object: help()
Convert a value to a string: str()
Convert a value to an integer: int()
Convert a value to a float: float()
Convert a value to a boolean: bool()
Convert a value to a list: list()
Convert a value to a dictionary: dict()
Convert a value to a set: set()
Convert a value to a tuple: tuple()
Convert values in a sequence s to a specified type t: s.astype(t)

Python's standard library is extensive, offering modules and functions for various tasks, including file I/O, system operations, internet protocols, and data manipulation.
Examples:


# Operating system interfaces
import os

os.getcwd() # Get the current working directory

# Date and time functions
import datetime

datetime.datetime.now() # Get the current date and time

# Time access and conversions
import time

time.time() # Get the current time in seconds since the epoch

# Generate pseudo-random numbers
import random

random.random() # Generate a random float between 0 and 1
random.randint(1, 10) # Generate a random integer between 1 and 10
random.choice(["apple", "banana", "cherry"]) # Choose a random item from a list

# JSON encoder and decoder
import json

data = {"name": "Alice", "age": 25}
json_data = json.dumps(data) # Convert a Python object to a JSON string
json_date.decode(json_data) # Convert a JSON string to a Python object
json.dump(data, open("data.json", "w")) # Write JSON data to a file named "data.json"
json.load(open("data.json")) # Read JSON data from a file named "data.json"

# URL handling modules
import urllib.request

response = urllib.request.urlopen("https://www.google.com") # Open a URL
html = response.read() # Read the HTML content of the page
print(html) # Print the HTML content

# Interface for SQLite databases
import sqlite3

conn = sqlite3.connect("example.db") # Connect to a SQLite database
cursor = conn.cursor() # Create a cursor object

# SMTP protocol client
import smtplib

server = smtplib.SMTP("smtp.gmail.com", 587) # Connect to an SMTP server
server.starttls() # Start a secure connection

# GUI toolkit
import tkinter

window = tkinter.Tk() # Create a GUI window
window.mainloop() # Run the GUI event loop

# CSV file reading and writing
import csv

with open("data.csv", "w") as file:
    writer = csv.writer(file)
    writer.writerow(["Name", "Age"])
    writer.writerow(["Alice", 25])
    writer.writerow(["Bob", 30])
'''Output:
Name,Age
Alice,25
Bob,30
'''

# Subprocess management
import subprocess

subprocess.run(["ls", "-l"]) # Run a shell command

# Thread-based parallelism
import threading

def print_numbers():
    for i in range(5):
        print(i)
thread = threading.Thread(target=print_numbers) # Create a new thread
thread.start() # Start the thread

# Process-based parallelism
import multiprocessing

def print_numbers():
    for i in range(5):
        print(i)
process = multiprocessing.Process(target=print_numbers) # Create a new process
process.start() # Start the process

# Asynchronous I/O
import asyncio

async def print_numbers():
    for i in range(5):
        print(i)
await print_numbers() # Run the asynchronous function

# Logging facility for Python
import logging

logging.basicConfig(level=logging.DEBUG) # Set the logging level
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
logging.error("This is an error message")
logging.critical("This is a critical message")

# Testing framework
import pytest

def test_addition():
    assert 1 + 1 == 2
    assert 2 + 2 == 4
    assert 3 + 3 == 6
pytest.main(["-q", "test_addition.py"]) # Run the test
'''Output:
3 passed in 0.01 seconds
'''

# Numerical computing with arrays
import numpy

a = numpy.array([1, 2, 3])

# Data manipulation and analysis
import pandas

data = {"name": ["Alice", "Bob", "Charlie"], "age": [25, 30, 35]}
df = pandas.DataFrame(data) # Create a DataFrame

# Default dictionary
import defaultdict

d = defaultdict(list) # Create a dictionary with default values as lists
d["a"].append(1) # Append a value to the list associated with key "a"

# Regular expressions
import re

pattern = r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b"
re.match(pattern, "[email protected]") # Match an email address
re.search(pattern, "Contact us at [email protected]") # Search for an email address
re.findall(pattern, "Contact us at [email protected] or [email protected]") # Find all email addresses

# Data visualization
import matplotlib.pyplot as plt

plt.plot([1, 2, 3, 4]) # Plot a line chart
plt.show() # Display the plot

# Bar progress
import tqdm

for i in tqdm.tqdm(range(100)):
    pass

The if __name__ == '__main__': Construct

The if __name__ == '__main__': construct in Python is a common idiom used to execute some code only if the file was run directly, and not imported as a module in another script. This allows you to write reusable modules and also include code that executes when the module is run as a script.

✔ Understanding __name__

Every Python module has a special attribute called __name__. When a module is run directly, the __name__ attribute is set to '__main__'. However, when the module is imported into another script, __name__ is set to the module's name.
Example:


# sample_module.py

def main():
    print("This is the main function.")

if __name__ == '__main__':
    main()

When you run sample_module.py directly, the output will be:


$ python sample_module.py
This is the main function.

When you import sample_module in another script, the main() function will not be executed:


# another_script.py

import sample_module

# No output, because main() is not called

✔ Benefits of Using if __name__ == '__main__':

Using this construct provides several benefits:

✔ Practical Use Cases

Here are some common practical use cases for using if __name__ == '__main__'::

Running Tests

You can include tests or demonstrations of how to use the functions in the module.


# sample_module.py

def add(a, b):
    return a + b

def main():
    # Test the add function
    print("Testing add function")
    print(add(2, 3))  # Output: 5

if __name__ == '__main__':
    main()

Command-Line Interfaces (CLI)

You can create scripts that can be run from the command line to perform tasks.


# cli_script.py

import argparse

def main():
    parser = argparse.ArgumentParser(description="Process some integers.")
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                        help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                        const=sum, default=max,
                        help='sum the integers (default: find the max)')

    args = parser.parse_args()
    print(args.accumulate(args.integers))

if __name__ == '__main__':
    main()

When run from the command line, this script can accept arguments and options:


$ python cli_script.py 1 2 3 4 --sum
10

Using the if __name__ == '__main__': construct is a good practice that enhances the reusability, readability, and maintainability of your Python code. It clearly delineates the script-specific code from the module-level code, making your scripts more robust and versatile.

Python Environments

Python environments are crucial for managing dependencies and ensuring that projects remain isolated from each other. This is especially important when working on multiple projects that require different versions of libraries or even different versions of Python.

✔ Virtual Environments

A virtual environment is a self-contained directory that contains a Python installation for a particular version of Python, plus a number of additional packages.

Creating a Virtual Environment

Python's venv module is used to create virtual environments. This module is included in Python 3.3 and later.


# Create a virtual environment
python -m venv myenv

# Activate the virtual environment (Windows)
myenv\Scripts\activate

# Activate the virtual environment (macOS/Linux)
source myenv/bin/activate

Installing Packages

Once the virtual environment is activated, you can install packages using pip. These packages will be installed only within the virtual environment.


pip install requests

Deactivating a Virtual Environment

To deactivate the virtual environment and return to the global Python environment, use the following command:


deactivate

Deleting a Virtual Environment

To delete a virtual environment, simply remove its directory.


rm -rf myenv

Anaconda Environments

Anaconda is a distribution of Python and R for scientific computing and data science. It includes the conda package manager, which can create and manage virtual environments.

Creating an Anaconda Environment

To create an environment with a specific version of Python, use the following command:


conda create --name myenv python=3.8

Activating an Anaconda Environment

To activate the newly created environment, use:


conda activate myenv

Installing Packages

You can install packages in the Anaconda environment using conda install:


conda install numpy

Deactivating an Anaconda Environment

To deactivate the environment, use:


conda deactivate

Listing Environments

To list all available Anaconda environments, use:


conda env list

Removing an Anaconda Environment

To remove an environment, use:


conda remove --name myenv --all

Managing Dependencies

Managing dependencies effectively is critical for reproducibility and collaboration. Using a requirements.txt file or environment.yml file helps in this process.

Using requirements.txt

To freeze the current environment's packages into a requirements.txt file, use:


pip freeze > requirements.txt

To install packages from a requirements.txt file, use:


pip install -r requirements.txt

Using environment.yml

To create an environment file for conda, use:


conda env export > environment.yml

To create an environment from an environment.yml file, use:


conda env create -f environment.yml

Docker for Python Environments

Docker is a tool that allows you to create, deploy, and run applications in containers. Containers can bundle an application and its dependencies in a single package.

Creating a Dockerfile

A Dockerfile is a script that contains instructions on how to build a Docker image.


# Sample Dockerfile for a Python application
FROM python:3.8-slim

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

COPY . .

CMD ["python", "app.py"]

Building and Running a Docker Image

To build a Docker image from the Dockerfile, use:


docker build -t my-python-app .

To run the Docker container, use:


docker run -it --rm my-python-app

Understanding and managing Python environments is essential for maintaining clean, reproducible, and isolated project setups. Whether using virtual environments, Anaconda, or Docker, these tools help manage dependencies and simplify the development workflow.

Spyder IDE

Spyder (Scientific Python Development Environment) is an open-source integrated development environment (IDE) designed for scientific programming in Python. It combines advanced editing, interactive testing, debugging, and introspection features, making it an excellent choice for data science, engineering, and research applications.

Spyder includes a powerful editor with syntax highlighting, an interactive IPython console, a variable explorer, and integration with popular scientific libraries such as NumPy, Pandas, and Matplotlib. This makes it a versatile tool for both beginners and experienced developers working on scientific projects.

Best Practices in Python Programming

Adhering to best practices in Python programming helps ensure that your code is clean, readable, maintainable, and efficient. Here are some widely recognized best practices that you should follow when writing Python code.

✔ Follow PEP 8

PEP 8 is the style guide for Python code. It covers various aspects of writing clean and readable code, including naming conventions, code layout, indentation, and more. Following PEP 8 helps maintain consistency across codebases.


# Example of PEP 8 compliant code
def my_function(a, b):
    if a == b:
        print("a and b are equal")
    else:
        print("a and b are not equal")

✔ Use Meaningful Names

Choose descriptive names for variables, functions, classes, and modules. This makes your code more understandable and maintainable.


# Good naming
def calculate_area(radius):
    return 3.14 * radius ** 2

# Bad naming
def func(x):
    return 3.14 * x ** 2

✔ Write Modular Code

Break your code into smaller, reusable functions and modules. This makes it easier to test, debug, and maintain.


# Modular code
def read_file(file_path):
    with open(file_path, 'r') as file:
        return file.read()

def process_data(data):
    # Process the data
    pass

def main():
    data = read_file('data.txt')
    process_data(data)

if __name__ == '__main__':
    main()

✔ Use Docstrings

Document your code with docstrings to explain what your functions and classes do. This helps other developers (and your future self) understand the purpose and usage of your code.


def add(a, b):
    """
    Add two numbers and return the result.

    Parameters:
    a (int or float): The first number.
    b (int or float): The second number.

    Returns:
    int or float: The sum of a and b.
    """
    return a + b

✔ Handle Exceptions Properly

Use try-except blocks to handle exceptions gracefully. This ensures that your program can handle unexpected errors without crashing.


try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide by zero!")

✔ Write Tests

Write unit tests for your code to ensure that it works as expected. This helps catch bugs early and makes it easier to refactor code safely.


import unittest

def add(a, b):
    return a + b

class TestMathFunctions(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)
        self.assertEqual(add(-1, 1), 0)

if __name__ == '__main__':
    unittest.main()

✔ Use Virtual Environments

Use virtual environments to manage dependencies and avoid conflicts between different projects. This ensures that each project has its own isolated environment.


# Create a virtual environment
python -m venv myenv

# Activate the virtual environment (Windows)
myenv\Scripts\activate

# Activate the virtual environment (macOS/Linux)
source myenv/bin/activate

# Install packages
pip install requests

✔ Keep Your Code DRY

DRY stands for "Don't Repeat Yourself." Avoid duplicating code by creating reusable functions and modules. This makes your code more maintainable and reduces the risk of errors.


# Reusable function
def print_greeting(name):
    print(f"Hello, {name}!")

print_greeting("Alice")
print_greeting("Bob")

✔ Leverage Built-in Libraries

Python comes with a rich set of built-in libraries. Before writing your own implementation, check if there's a built-in module that fits your needs.


import os

# Get the current working directory
cwd = os.getcwd()
print(f"Current working directory: {cwd}")

Following these best practices will help you write Python code that is clean, efficient, and maintainable. They are essential habits for any Python developer aiming to produce high-quality software.

References

  1. (Official Website) Python | Website
  2. (Repository) PyPi | Website
  3. (Tutorials) Real Python | Website
  4. (Distribution) Anaconda | Website
  5. (IDE) Spyder IDE | Website