🐍

Python

Created
TypeLanguage
LanguagePython
Last Edit

Installation & Setup

Check Python Versions

ls /usr/local/bin/python* 
ls /Library/Frameworks/Python.framework/Versions/ 

Array Functions

Iterate Array

pets = ['cat', 'dog', 'fish']
for f in pets:
    print f

String Functions

Convert string to char array

s = "III"
components = list(s)
print(components)

my_string = "hello"
char_array = [char for char in my_string]
print(char_array)

Create file with newline

 printf '%s\n' 'frappe' 'msigma-erpnext' > apps.txt

Versions & Shells

Pyenv - Python Versions

If you use a tool like pyenv to manage different Python versions, you can set the experimental virtualenvs.prefer-active-python option to true. Poetry will then try to find the current python of your shell.

For instance, if your project requires a newer Python than is available with your system, a standard workflow would be:

pyenv install 3.9.8
pyenv local 3.9.8  # Activate Python 3.9 for the current project
poetry install

Create virtual environment with specific version of python

virtualenv --python="/usr/bin/python2.6" "/path/to/new/virtualenv/
python3.6 -m venv "my_env_name"

Pyenv Zsh Path Setup

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init -)"' >> ~/.zshrc

XMLSEC Error in MAC

  1. Install all necessary packages for xmlsec
    brew install libxml2 libxmlsec1 pkg-config
  1. Try
    pip install xmlsec
  1. If error persists
    libxmlsec1.rb
    class Libxmlsec1 < Formula
      desc "XML security library"
      homepage "https://www.aleksey.com/xmlsec/"
      url "https://www.aleksey.com/xmlsec/download/xmlsec1-1.2.37.tar.gz"
      sha256 "5f8dfbcb6d1e56bddd0b5ec2e00a3d0ca5342a9f57c24dffde5c796b2be2871c"
      license "MIT"
    
      livecheck do
        url "https://www.aleksey.com/xmlsec/download/"
        regex(/href=.*?xmlsec1[._-]v?(\d+(?:\.\d+)+)\.t/i)
      end
    
      bottle do
        sha256 cellar: :any,                 arm64_ventura:  "26d6ebddf4e97431819583ad699228360886d81786b332084693d0ad34aa2c72"
        sha256 cellar: :any,                 arm64_monterey: "66646e0a3c47fe21b5d6257d2940c1cbaddd68fd71845ae21eb34275b2913db4"
        sha256 cellar: :any,                 arm64_big_sur:  "6520bff7f714071fc7a5925dc2335c5482ce59383386500e1f51680bf3e69850"
        sha256 cellar: :any,                 ventura:        "15faa359429f324b4d18e49c70b0832cf93eb052ad0ef74ccddf1a2db0a4aad5"
        sha256 cellar: :any,                 monterey:       "dfc4528593b38556559a49053f7b5e3a46ae07d844ad3412a65c22214624a932"
        sha256 cellar: :any,                 big_sur:        "d428a24cc5c8165e84718292cd4a7a21519b1ce1f46c82ffff0bc27216b8a573"
        sha256 cellar: :any,                 catalina:       "b67b572409b3d79387f621c9f28338d0ec99342477f50643ff3a6032b58133c6"
        sha256 cellar: :any_skip_relocation, x86_64_linux:   "a52005111565d460c6774d5c5be9c8a0db05e0a06dc8715b7c1f59ab4a66fcb0"
      end
    
      depends_on "pkg-config" => :build
      depends_on "gnutls" # Yes, it wants both ssl/tls variations
      depends_on "libgcrypt"
      depends_on "libxml2"
      depends_on "openssl@1.1"
      uses_from_macos "libxslt"
    
      on_macos do
        depends_on xcode: :build
      end
    
      # Add HOMEBREW_PREFIX/lib to dl load path
      patch :DATA
    
      # Fix -flat_namespace being used on Big Sur and later.
      patch do
        url "https://raw.githubusercontent.com/Homebrew/formula-patches/03cf8088210822aa2c1ab544ed58ea04c897d9c4/libtool/configure-big_sur.diff"
        sha256 "35acd6aebc19843f1a2b3a63e880baceb0f5278ab1ace661e57a502d9d78c93c"
      end
    
      def install
        args = ["--disable-dependency-tracking",
                "--prefix=#{prefix}",
                "--disable-crypto-dl",
                "--disable-apps-crypto-dl",
                "--with-nss=no",
                "--with-nspr=no",
                "--enable-mscrypto=no",
                "--enable-mscng=no",
                "--with-openssl=#{Formula["openssl@1.1"].opt_prefix}"]
    
        system "./configure", *args
        system "make", "install"
      end
    
      test do
        system "#{bin}/xmlsec1", "--version"
        system "#{bin}/xmlsec1-config", "--version"
      end
    end
    
    __END__
    diff --git a/src/dl.c b/src/dl.c
    index 6e8a56a..0e7f06b 100644
    --- a/src/dl.c
    +++ b/src/dl.c
    @@ -141,6 +141,7 @@ xmlSecCryptoDLLibraryCreate(const xmlChar* name) {
         }
    
     #ifdef XMLSEC_DL_LIBLTDL
    +    lt_dlsetsearchpath("HOMEBREW_PREFIX/lib");
         lib->handle = lt_dlopenext((char*)lib->filename);
         if(lib->handle == NULL) {
             xmlSecError(XMLSEC_ERRORS_HERE,
brew edit libxmlsec1
brew install /opt/homebrew/opt/libxmlsec1/.brew/libxmlsec1.rb
brew unlink libxmlsec1

Pipx

Poetry

Poetry is a tool for dependency management and packaging in Python.

Utilizing Poetry for Managing Python Project Requirements
Hi friends, and welcome to another edition of this guide.
https://awstip.com/utilizing-poetry-for-managing-python-project-requirements-b911245d3aa2

.gitignore

poetry.lock

Steps

Usage: Using Poetry to create a Python project
$ poetry new sample-project
$ cd sample-project

This will create the following files and folders:

sample-project
├── README.rst
├── pyproject.toml
├── sample_project
│   └── __init__.py
└── tests
    ├── __init__.py
    └── test_sample_project.py

Dependencies are managed inside the pyproject.toml file:

[tool.poetry]name = "sample-project"
version = "0.1.0"
description = ""
authors = ["John Doe <john@doe.com>"]

[tool.poetry.dependencies]python = "^3.10"

[tool.poetry.dev-dependencies]pytest = "^5.2"

[build-system]requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
For more on pyproject.toml, the new Python package configuration file that treats "every project as a package", check out the What the heck is pyproject.toml? article.

To add new a dependency, simply run:

$ poetry add [--dev] <package name>
The --dev flag indicates that the dependency is meant to be used in development mode only. Development dependencies are not installed by default.

For example:

$ poetry add flask

This downloads and installs Flask from PyPI inside the virtual environment managed by Poetry, adds it along with all sub-dependencies to the poetry.lock file, and automatically adds it (a top-level dependency) to pyproject.toml:

[tool.poetry.dependencies]python = "^3.10"
Flask = "^2.0.3"

Take note of the version constraint"^2.0.3".

To run a command inside the virtual environment, prefix the command with poetry run. For example, to run tests with pytest:

$ poetry run python -m pytest

poetry run <command> will run commands inside the virtual environment. It doesn't activate the virtual environment, though. To activate Poetry's virtual environment you need to run poetry shell. To deactivate it, you can simply run the exit command. Consequently, you can activate your virtual environment before working on the project and deactivate once you're done or you can use poetry run <command> throughout development.

Finally, Poetry works well with pyenv. Review Managing environments from the official docs for more on this.

Removing dependencies

To remove a package from the installed dependencies, run poetry remove <package_name>

 poetry remove fastapi
Update dependencies

poetry update updates all dependencies and pin exact versions into poetry.lock

Usage: Using poetry in existing project

Poetry can be used to initialize an existing project using this command

cd pre-existing-project
poetry init
Add dependencies

The pyproject.toml file automatically created for the project has a section called [tool.poetry.dependencies] as explained earlier. This is where the developer can specify the package names and version constraints.

[tool.poetry.dependencies]
python = "^3.10"

poetry add command can be used to automatically install a new package.

Remember to run the command inside the project directory

cd py-project
poetry add requests

This automatically includes the newly installed package as part of the dependencies.

[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.31.0"

Sometimes, you may need to install packages that are only used in development mode. To achieve this, add the -D flag during installation.

poetry add -D factory_boy

A new section called [tool.poetry.group.dev.dependencies] is added to the pyproject.toml file, which specifies the development packages.

[tool.poetry.group.dev.dependencies]
factory-boy = "^3.2.1"
Installing dependencies:

poetry install checks for the py-project.toml file, resolves the specified dependencies, and installs them.

Say after creating a poetry project, I then manually added to a new package i.e. fastapi = “0.97.0” to the [tool.poetry.dependencies] section as shown below.

[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.97.0"

Running poetry install works fine after this manual update if no poetry.lock file is present because poetry automatically resolves all dependencies listed in the pyproject.toml file and downloads the latest version of their files. It writes all the packages and their exact versions that it downloaded to the poetry.lock file, locking the project to those specific versions.

If poetry.lock is already present and the dependencies is updated manually, then you run poetry install it throws an error similar to this:

Installing dependencies from lock file
Warning: poetry.lock is not consistent with pyproject.toml. You may be getting improper dependencies. Run `poetry lock [--no-update]` to fix it.

Because py-project depends on fastapi(0.97.0) which doesn't match any versions, version solving failed.

This error evolved because poetry install depends on the poetry.lock file to install and resolve packages but if found out that the new manually input package (‘fastapi’ in this case) could not be found in the poetry.lock file.

To resolve this, you need to run poetry lock which checks the dependencies in the pyproject.toml and lock it into the poetry.lock file

This command also automatically update existing packages and resolve them to fit the version of the newly added package. If you want the existing dependencies untouched, you need to run poetry lock --no-update so that it only resolves and install the new package.

Removing dependencies

To remove a package from the installed dependencies, run poetry remove <package_name>

 poetry remove fastapi

To list all available packages, run poetry show

Check out the documentation to view the list of available commands.

Update dependencies

poetry update updates all dependencies and pin exact versions into poetry.lock

To update few specific packages, run poetry update <package_1> <package_2>

poetry update requests fastapi
Run scripts

If you need to execute a specific command or script within the project’s virtual environment, you can utilize the “poetry run” command. For instance, if you want to run an automated test using pytest, you can employ the following command:

poetry run pytest
Using existing requirements.txt file

If you already have a project with dependencies listed in the requirements.txt file, you can use the command:

xargs poetry add < requirements.txt
Export command

The export command generates a requirements.txt file from the poetry.lock file.

poetry export -f requirements.txt --output requirements.txt

Usage: Convert Requirements To Poetry
poetry add $(cat requirements.txt)

Display Env Info

poetry env info

Non Package

Poetry can be operated in two different modes. The default mode is the package mode, which is the right mode if you want to package your project into an sdist or a wheel and perhaps publish it to a package index. In this mode, some metadata such as name and version, which are required for packaging, are mandatory. Further, the project itself will be installed in editable mode when running poetry install.

If you want to use Poetry only for dependency management but not for packaging, you can use the non-package mode:

[tool.poetry]
package-mode = false

In this mode, metadata such as name and version are optional. Therefore, it is not possible to build a distribution or publish the project to a package index. Further, when running poetry install, Poetry does not try to install the project itself, but only its dependencies (same as poetry install --no-root).

Switching between environments

Sometimes this might not be feasible for your system, especially Windows where pyenv is not available, or you simply prefer to have a more explicit control over your environment. For this specific purpose, you can use the env use command to tell Poetry which Python version to use for the current project.

poetry env use /full/path/to/python

If you have the python executable in your PATH you can use it:

poetry env use python3.7

You can even just use the minor Python version in this case:

poetry env use 3.7

If you want to disable the explicitly activated virtual environment, you can use the special system Python version to retrieve the default behavior:

poetry env use system

Base64

String to Base64

import base64
import json

string_og = {"key":"Hello world"}
string = json.dumps(string_og)

string_bytes = string.encode('utf-8')

# Encode bytes to base64
base64_encoded = base64.b64encode(string_bytes)
	
# Convert base64 bytes to a string
base64_string = base64_encoded.decode('utf-8')
print(base64_string)

Base64 to String

import base64
import json

base64_bytes = base64_string.encode('utf-8')

# Decode base64 bytes
decoded_bytes = base64.b64decode(base64_bytes)

# Convert bytes to string
original_string = decoded_bytes.decode('utf-8')
print(original_string)

OOP

  1. Class and Object:
    • Real-world example: Cars
      • Class: Car Model (e.g., Toyota Camry): BLUEPRINT
      • Object: Your specific Toyota Camry with its unique VIN, color, and mileage.
  1. Attributes:
    • Real-world example: Human
      • Attributes: Name, Age, Height, Weight
      • Each person (object) has specific values for these attributes.
  1. Methods:
    • Real-world example: Smartphones
      • Methods: Make a call, Send a text, Take a photo
      • The actions you perform with your smartphone are analogous to methods.
  1. Inheritance:
    • Real-world example: Vehicles
      • Base Class: Vehicle
      • Subclasses: Car, Motorcycle, Truck
      • Subclasses inherit common properties and behaviors from the base class.
  1. Encapsulation:
    • Real-world example: Coffee Machine
      • The inner workings of a coffee machine are encapsulated. Users interact with it through well-defined interfaces (e.g., buttons, settings), but they don't need to understand the internal processes.
  1. Polymorphism:
    • Real-world example: Animals
      • Base Class: Animal
      • Derived Classes: Dog, Cat, Bird
      • Different animals can make different sounds, demonstrating polymorphism.

Python is an object-oriented and interpreted programming language, and it supports OOP principles. Here are the key concepts of OOP in Python:

  1. Class and Object:
    • Class: A class is a blueprint for creating objects. It defines the attributes and methods that the objects will have.
    • Object: An object is an instance of a class. It is a concrete realization of the class blueprint.
    
    class Car:
        def __init__(self, make, model):
            self.make = make
            self.model = model
    
    my_car = Car("Toyota", "Camry")
    
    
  1. Attributes:
    • Attributes are data members that store information about the object.
    pythonCopy code
    class Car:
        def __init__(self, make, model):
            self.make = make
            self.model = model
    
    my_car = Car("Toyota", "Camry")
    print(my_car.make)  # Output: Toyota
    
    
  1. Methods:
    • Methods are functions defined within a class. They operate on the object and can access or modify its attributes.
    
    class Car:
        def __init__(self, make, model):
            self.make = make
            self.model = model
    
        def display_info(self):
            print(f"{self.make} {self.model}")
    
    my_car = Car("Toyota", "Camry")
    my_car.display_info()  # Output: Toyota Camry
    
    

The __init__() Function :

All classes have a function called __init__(), which is always executed when the class is being initiated.

Use the __init__() function to assign values to object properties, or other operations that are necessary to do when the object is being created.

The __init__() function is called automatically every time the class is being used to create a new object.

The self parameter is a reference to the current instance of the class, and is used to access variables that belong to the class. It does not have to be named self , you can call it whatever you like, but it has to be the first parameter of any function in the class.

  1. Inheritance:
    • Inheritance allows a class (subclass/derived class) to inherit the properties and methods of another class (superclass/base class). It promotes code reusability.
    
    class ElectricCar(Car):
        def __init__(self, make, model, battery_capacity):
            super().__init__(make, model)
            self.battery_capacity = battery_capacity
    
    my_electric_car = ElectricCar("Tesla", "Model S", 75)
    my_electric_car.display_info()  # Output: Tesla Model S
    
    
  1. Encapsulation:
    • Encapsulation restricts access to some of the object's components and prevents the accidental modification of data. It is achieved through the use of private and protected attributes and methods.
    
    class Student:
        def __init__(self, name, age):
            self._name = name  # Protected attribute
            self.__age = age   # Private attribute
    
        def get_age(self):
            return self.__age
    
    student = Student("John", 20)
    print(student._name)     # Accessing protected attribute
    print(student.get_age())  # Accessing private attribute through a method
    
    

    Inheritance allows us to define a class that inherits all the methods and properties from another class.

    Parent class is the class being inherited from, also called base class.

    Child class is the class that inherits from another class, also called derived class.

    Create a Parent Class

    class Person:

    def __init__(self, fname, lname):

    self.firstname = fname

    self.lastname = lname

    def printname(self):

    print(self.firstname, self.lastname)

    #Use the Person class to create an object, and then execute the printname method:

    x = Person("John", "Doe")

    x.printname()

    Create a Child Class

    To create a class that inherits the functionality from another class, send the parent class as a parameter when creating the child class.

    class Student(Person):

    def __init__(self, fname, lname, year):

    super().__init__(fname, lname)

    self.graduationyear = year

    def welcome(self):

    print("Welcome", self.firstname, self.lastname, "to the class of", self.graduationyear)

    By using the super() function, you do not have to use the name of the parent element, it will automatically inherit the methods and properties from its parent.

    Types of Inheritance :

    Single inheritance : A class inherits from only one base class

    Multiple inheritance : A class can inherit from multiple base classes. Allows a derived class to inherit attributes and methods from more than one class.

    Multilevel inheritance : A class inherits from another class, and then a new class inherits from the derived class. It forms a chain of inheritance.

    Exercise :

    Create a BankAccount class with the following features:

    A constructor (__init__) that initializes the account holder's name and initial balance.

    Methods (deposit and withdraw) to deposit and withdraw money from the account.

    A method (get_balance) to retrieve the current balance.

    Ensure that the account cannot be overdrawn (balance cannot go below zero).

    Provide a simple usage example to demonstrate the functionality of the BankAccount class.

    Write code for the BankAccount class and the example usage.

    class BankAccount:
        def __init__(self, account_holder, initial_balance=0):
            self.account_holder = account_holder
            self.balance = initial_balance
    
        def deposit(self, amount):
            if amount > 0:
                self.balance += amount
                return f"Deposit of Rs{amount} successful. Current balance: Rs{self.balance}"
            else:
                return "Invalid deposit amount."
    
        def withdraw(self, amount):
            if amount > 0:
                if amount <= self.balance:
                    self.balance -= amount
                    return f"Withdrawal of Rs{amount} successful. Current balance: Rs{self.balance}"
                else:
                    return "Insufficient funds. Withdrawal canceled."
            else:
                return "Invalid withdrawal amount."
    
        def get_balance(self):
            return f"Current balance for {self.account_holder}: Rs{self.balance}"
    
    # Example usage
    account1 = BankAccount("Alice", 1000)
    print(account1.get_balance())              
    print(account1.deposit(500))              
    print(account1.withdraw(200))              
    print(account1.withdraw(1500))            
    print(account1.get_balance())
  1. Polymorphism:
    • Polymorphism allows objects of different classes to be treated as objects of a common base class. It can be achieved through method overloading or method overriding.
    
    class Shape:
        def area(self):
            pass
    
    class Circle(Shape):
        def __init__(self, radius):
            self.radius = radius
    
        def area(self):
            return 3.14 * self.radius**2
    
    class Rectangle(Shape):
        def __init__(self, length, width):
            self.length = length
            self.width = width
    
        def area(self):
            return self.length * self.width
    
    def calculate_area(shape):
        return shape.area()
    
    circle = Circle(5)
    rectangle = Rectangle(4, 6)
    
    print("Circle Area:", calculate_area(circle))      
    print("Rectangle Area:", calculate_area(rectangle))

    Exercise

    Implement animal base class and dog and cat as child classes with polymorphism

    
    class Animal:
        def sound(self):
            pass
    
    class Dog(Animal):
        def sound(self):
            return "Woof"
    
    class Cat(Animal):
        def sound(self):
            return "Meow"
    
    def make_sound(animal):
        print(animal.sound())
    
    dog = Dog()
    cat = Cat()
    
    make_sound(dog)  # Output: Woof
    make_sound(cat)  # Output: Meow
    
    

These are the fundamental concepts of OOP in Python. They provide a way to structure code, promote reusability, and enhance code organization.

Exercises

  1. Class and Object:
    • Task: Create a class called Product with attributes like product ID, name, and price. Create two instances of the class representing different products.
  1. Attributes:
    • Task: Extend the Product class to include attributes like manufacturer, category, and stock quantity. Display the information of a product, including all attributes.
  1. Methods:
    • Task: Design a class called ShoppingCart that has methods for adding a product to the cart, removing a product, and calculating the total cost of items in the cart.
  1. Inheritance:
    • Task: Create a base class Animal with methods like eat and sleep. Derive two subclasses, Bird and Mammal, from the base class and implement specific behavior for each.
  1. Encapsulation:
    • Task: Implement a class Person with private attributes for name, age, and address. Include methods to get and set these attributes while ensuring proper encapsulation.
  1. Polymorphism:
    • Task: Model a class hierarchy for a transportation system. Create a base class Transportation with a method move. Derive two classes, Car and Bicycle, and override the move method to represent different modes of transportation.

Modify and Delete object properties :

We can modify and delete object properties like this :

person1.age =27

print(person1.age)

person1.display_info()

del person1.age 

Numpy

NumPy is a powerful numerical computing library for Python that provides support for large, multi-dimensional arrays and matrices, along with a collection of mathematical functions to operate on these arrays. It is a fundamental package for scientific computing in Python. Some key features of NumPy include:

  1. Multi-dimensional Arrays: NumPy provides a powerful ndarray object that represents multi-dimensional arrays or matrices. These arrays can be of any size and can have multiple dimensions.
  1. Mathematical Functions: NumPy includes a wide range of mathematical functions that operate on entire arrays without the need for explicit looping. These functions are optimized for performance and efficiency.
  1. Broadcasting: NumPy allows operations between arrays of different shapes and sizes through a feature called broadcasting. This enables efficient element-wise operations on arrays with different shapes.
  1. Linear Algebra Operations: NumPy provides a comprehensive set of linear algebra operations, such as matrix multiplication, eigenvalue decomposition, and singular value decomposition.
  1. Random Number Generation: NumPy includes functions for generating random numbers with various probability distributions, which is useful for simulations and statistical analysis.
  1. Indexing and Slicing: NumPy arrays support advanced indexing and slicing operations, making it easy to extract and manipulate data.
  1. Integration with other Libraries: NumPy is often used in conjunction with other libraries in the scientific Python ecosystem, such as SciPy (scientific computing library), Matplotlib (plotting library), and scikit-learn (machine learning library).

NumPy is widely used in various scientific and data analysis applications due to its efficiency, ease of use, and extensive functionality.

pip install numpy
import numpy as np

Exercises

Reverse an array

Create an array from 1 to 40 (inclusive) using Numpy and reverse the array

  • solution
    # Importing the NumPy library with an alias 'np'
    import numpy as np
    
    # Creating an array 'x' using arange() function with values from 12 to 37 (inclusive)
    x = np.arange(1, 40)
    
    # Printing the original array 'x' containing values from 12 to 37
    print("Original array:")
    print(x)
    
    # Reversing the elements in the array 'x' and printing the reversed array
    print("Reverse array:")
    x = x[::-1]
    print(x)

Conversion

Write a NumPy program to convert Fahrenheit degrees into Centigrade degrees. Fahrenheit values are stored in a NumPy array.

Sample Array [0, 12, 45.21, 34, 99.91]

Expected Output:

Values in Centigrade degrees:

[-17.78 -11.11 7.34 1.11 37.73 0. ]

  • Solution
    # Importing the NumPy library with an alias 'np'
    import numpy as np
    
    # List of Fahrenheit values
    fvalues = [0, 12, 45.21, 34, 99.91, 32]
    
    # Creating a NumPy array from the Fahrenheit values list
    F = np.array(fvalues)
    
    # Printing the Fahrenheit values
    print("Values in Fahrenheit degrees:")
    print(F)
    
    # Converting Fahrenheit values to Centigrade degrees and printing the result
    print("Values in  Centigrade degrees:")
    print(np.round((5 * F / 9 - 5 * 32 / 9), 2))

    np.round((5F/9 - 532/9), 2): Applies the formula to convert Fahrenheit to Celsius for each value in the NumPy array ‘F’. The np.round() function is used to round the resulting values to 2 decimal places.

Pandas

Pandas is an open-source data manipulation and analysis library for Python. It provides data structures for efficiently storing and manipulating large datasets, as well as tools for working with structured data. Pandas is built on top of the NumPy library and is a key component of the Python data science ecosystem.

Key features of Pandas include:

  1. DataFrame: The central data structure in Pandas is the DataFrame, a two-dimensional table with labeled rows and columns. It is similar to a spreadsheet or SQL table and provides a powerful and flexible way to work with structured data. Pandas is designed for working with heterogeneous, tabular data.
  1. Series: Pandas also includes a one-dimensional labeled array called Series, which can be thought of as a single column of a DataFrame. Series are often used to represent a single variable or feature.
  1. Data Cleaning and Preprocessing: Pandas provides a wide range of functions for cleaning and preprocessing data, including handling missing values, filtering, sorting, and transforming data.
  1. Data Alignment: Pandas automatically aligns data based on label indices, making it easy to perform operations on datasets with different structures.
  1. GroupBy: Pandas supports the "group by" operation, allowing users to split data into groups based on some criteria and then apply a function to each group independently.
  1. Merging and Joining: Pandas provides tools for combining datasets through merging and joining operations, similar to SQL.
  1. Time Series Data: Pandas has robust support for working with time series data, including date and time functionality, resampling, and moving window statistics.
  1. IO Tools: Pandas can read and write data in various formats, including CSV, Excel, SQL databases, and more.
pip install pandas
pip install pyarrow
	
import pandas as pd
import pandas as pd
df = pd.read_csv('data.csv')
print(df.to_string())

Exercises

Write a Pandas program to display the dimensions or shape of the World alcohol consumption dataset. Also extract the column names from the dataset.

Test Data:

https://www.w3resource.com/python-exercises/pandas/filter/world_alcohol.php

   Year       WHO region                Country Beverage Types  Display Value
0  1986  Western Pacific               Viet Nam           Wine           0.00
1  1986         Americas                Uruguay          Other           0.50
2  1985           Africa           Cte d'Ivoire           Wine           1.62
3  1986         Americas               Colombia           Beer           4.27
4  1987         Americas  Saint Kitts and Nevis           Beer           1.98

Numpy vs Pandas

Use Cases:

Indexing:

  • NumPy: Employs integer-based indexing for arrays, and it relies on the position of elements within the array.
  • Pandas: Uses labeled indexing, allowing for both integer and label-based indexing. This makes it easier to reference and manipulate data based on column names or row indices.

Performance:

  • NumPy: Optimized for numerical operations and is generally faster for mathematical computations than Pandas.
  • Pandas: While it may not be as fast as NumPy for numerical operations, it excels in terms of data manipulation and analysis.

Matplotlib

Matplotlib is a 2D plotting library for Python that facilitates the creation of static, animated, and interactive visualizations in Python.

  1. Flexible Plotting: Matplotlib provides a wide range of plot types, including line plots, scatter plots, bar plots, histograms, pie charts, and more. It allows users to customize the appearance of plots to meet specific requirements.
  1. Publication-Quality Graphics: Matplotlib produces high-quality, publication-ready graphics suitable for use in scientific publications, presentations, and reports.
  1. Support for Multiple Output Formats: Matplotlib can generate plots in various formats, including PNG, PDF, SVG, and more. This flexibility allows users to integrate plots seamlessly into different types of documents.
  1. Integration with Jupyter Notebooks: Matplotlib is well-integrated with Jupyter notebooks, providing interactive plotting capabilities within the notebook environment.
  1. Matplotlib Pyplot Interface: Matplotlib has a MATLAB-like plotting interface called Pyplot, which simplifies the creation of common plots with a minimal amount of code.
pip install matplotlib
import matplotlib as plt

Logging

A Comprehensive Guide to Logging in Python | Better Stack Community
Python provides a built-in logging module in its standard library that provides comprehensive logging capabilities for Python programs
https://betterstack.com/community/guides/logging/how-to-start-logging-with-python/
import logging

logging.basicConfig(level=logging.INFO) # Default min level = WARNING

logging.debug("A debug message")
logging.info("An info message")
logging.warning("A warning message")
logging.error("An error message")
logging.critical("A critical message")
TRACE:root:A trace message
DEBUG:root:A debug message
INFO:root:An info message
WARNING:root:A warning message
ERROR:root:An error message
CRITICAL:root:A critical message

Custom Log Level
import logging


# Adopted from https://stackoverflow.com/a/35804945/1691778
# Adds a new logging method to the logging module
def addLoggingLevel(levelName, levelNum, methodName=None):
    if not methodName:
        methodName = levelName.lower()

    if hasattr(logging, levelName):
        raise AttributeError("{} already defined in logging module".format(levelName))
    if hasattr(logging, methodName):
        raise AttributeError("{} already defined in logging module".format(methodName))
    if hasattr(logging.getLoggerClass(), methodName):
        raise AttributeError("{} already defined in logger class".format(methodName))

    def logForLevel(self, message, *args, **kwargs):
        if self.isEnabledFor(levelNum):
            self._log(levelNum, message, args, **kwargs)

    def logToRoot(message, *args, **kwargs):
        logging.log(levelNum, message, *args, **kwargs)

    logging.addLevelName(levelNum, levelName)
    setattr(logging, levelName, levelNum)
    setattr(logging.getLoggerClass(), methodName, logForLevel)
    setattr(logging, methodName, logToRoot)

# Create the TRACE level
addLoggingLevel("TRACE", logging.DEBUG - 5)
logging.basicConfig(level=logging.TRACE)

# Use the TRACE level
logging.trace("A trace message")
logging.debug("A debug message")
logging.info("An info message")
logging.warning("A warning message")
logging.error("An error message")
logging.critical("A critical message")

Custom Log Formatting

logging.basicConfig(
    format="%(levelname)s | %(asctime)s | %(message)s",
    datefmt="%Y-%m-%dT%H:%M:%SZ",
)
logging.warning("Something bad is going to happen")
WARNING | 2023-02-05T11:45:31Z | Something bad is going to happen
logging.basicConfig(
    format="%(name)s: %(asctime)s | %(levelname)s | %(filename)s:%(lineno)s | %(process)d >>> %(message)s",
    datefmt="%Y-%m-%dT%H:%M:%SZ",
)
logging.warning("system disk is 85% full")
logging.error("unexpected error")
root: 2023-02-05T14:11:56Z | WARNING | example.py:8 | 428223 >>> system disk is 85% full
root: 2023-02-05T14:11:56Z | ERROR | example.py:9 | 428223 >>> unexpected error

Custom Loggers

💡

It's best to avoid using the root logger and instead create a separate logger for each module in your application.

import logging

logger = logging.getLogger("example")

logger.info("An info")
logger.warning("A warning")
A warning

Create logger with module name

logger = logging.getLogger(__name__)

Handlers & Formatters

import sys
import logging

logger = logging.getLogger("example")

# Stream Handler
stdout = logging.StreamHandler(stream=sys.stdout) 
stdout.setLevel(logging.INFO)

# Formatter
fmt = logging.Formatter(
    "%(name)s: %(asctime)s | %(levelname)s | %(filename)s:%(lineno)s | %(process)d >>> %(message)s"
) 

stdout.setFormatter(fmt)
logger.addHandler(stdout)

logger.setLevel(logging.INFO)

logger.info("An info")
logger.warning("A warning")
💡

Handler and Logger can have different log levels

example: 2023-02-05 21:21:46,634 | INFO | example.py:17 | 653630 >>> An info
example: 2023-02-05 21:21:46,634 | WARNING | example.py:18 | 653630 >>> A warning

Logging To File

import sys
import logging

logger = logging.getLogger(__name__)

fileHandler = logging.FileHandler("logs.txt")

fmt = logging.Formatter(
    "%(name)s: %(asctime)s | %(levelname)s | %(filename)s:%(lineno)s | %(process)d >>> %(message)s"
) 
fileHandler.setFormatter(fmt)

logger.addHandler(fileHandler)
logger.setLevel(logging.DEBUG)
logger.debug("A debug message")
logger.error("An error message")

Automatic Log File Rotation

from logging.handlers import RotatingFileHandler

fileHandler = RotatingFileHandler("logs.txt", backupCount=5, maxBytes=5000000)

With this setting in place, the logs.txt file will be created and written to as before until it reaches 5 megabytes. It will subsequently be renamed to logs.txt.1 and a new logs.txt file will be created once again. When the new file gets to 5 MB, it will be renamed to logs.txt.1 and the previous logs.txt.1 file will be renamed to logs.txt.2. This process continues until we get to logs.txt.5. At that point, the oldest file (logs.txt.5) gets deleted to make way for the newer logs.