Author: admin

  • Python – Naming the Threads

    In Python, naming a thread involves assigning a string as an identifier to the thread object. Thread names in Python are primarily used for identification purposes only and do not affect the thread’s behavior or semantics. Multiple threads can share the same name, and names can be specified during the thread’s initialization or changed dynamically.

    Thread naming in Python provides a straightforward way to identify and manage threads within a concurrent program. By assigning meaningful names, users can enhance code clarity and easily debug the complex multi-threaded applications.

    Naming the Threads in Python

    When you create a thread using threading.Thread() class, you can specify its name using the name parameter. If not provided, Python assigns a default name like the following pattern “Thread-N”, where N is a small decimal number. Alternatively, if you specify a target function, the default name format becomes “Thread-N (target_function_name)”.

    Example

    Here is an example demonstrates assigning custom and default names to threads created using threading.Thread() class, and displays how names can reflect target functions.

    from threading import Thread
    import threading
    from time import sleep
    
    defmy_function_1(arg):print("This tread name is", threading.current_thread().name)# Create thread objects
    thread1 = Thread(target=my_function_1, name='My_thread', args=(2,))
    thread2 = Thread(target=my_function_1, args=(3,))print("This tread name is", threading.current_thread().name)# Start the first thread and wait for 0.2 seconds
    thread1.start()
    thread1.join()# Start the second thread and wait for it to complete
    thread2.start()
    thread2.join()

    On executing the above, it will produce the following results −

    This tread name is MainThread
    This tread name is My_thread
    This tread name is Thread-1 (my_function_1)
    

    Dynamically Assigning Names to the Python Threads

    You can assign or change a thread’s name dynamically by directly modifying the name attribute of the thread object.

    Example

    This example shows how to dynamically change thread names by modifying the name attribute of the thread object.

    from threading import Thread
    import threading
    from time import sleep
    
    defmy_function_1(arg):
       threading.current_thread().name ="custom_name"print("This tread name is", threading.current_thread().name)# Create thread objects
    thread1 = Thread(target=my_function_1, name='My_thread', args=(2,))
    thread2 = Thread(target=my_function_1, args=(3,))print("This tread name is", threading.current_thread().name)# Start the first thread and wait for 0.2 seconds
    thread1.start()
    thread1.join()# Start the second thread and wait for it to complete
    thread2.start()
    thread2.join()

    When you execute the above code, it will produce the following results −

    This tread name is MainThread
    This tread name is custom_name
    This tread name is custom_name
    

    Example

    Threads can be initialized with custom names and even renamed after creation. This example demonstrates creating threads with custom names and modifying a thread’s name after creation.

    import threading
    
    defaddition_of_numbers(x, y):print("This Thread name is :", threading.current_thread().name)
       result = x + y
    
    defcube_number(i):
       result = i **3print("This Thread name is :", threading.current_thread().name)defbasic_function():print("This Thread name is :", threading.current_thread().name)# Create threads with custom names
    t1 = threading.Thread(target=addition_of_numbers, name='My_thread', args=(2,4))
    t2 = threading.Thread(target=cube_number, args=(4,))
    t3 = threading.Thread(target=basic_function)# Start and join threads
    t1.start()
    t1.join()
    
    t2.start()
    t2.join()
    
    t3.name ='custom_name'# Assigning name after thread creation
    t3.start()
    t3.join()print(threading.current_thread().name)# Print main thread's name

    Upon execution, the above code will produce the following results −

    This Thread name is : My_thread
    This Thread name is : Thread-1 (cube_number)
    This Thread name is : custom_name
    MainThread
  • Python – Joining the Threads

    In Python, joining the threads means using the join() method to wait for one thread to finish before moving on to others. This is useful in multithreaded programming to make sure some threads are completed before starting or continuing with other threads. By using the join() method, you can make sure that one thread has finished running before another thread or the main program continues. In this tutorial you will get the detailed explain of the join() method with suitable examples.

    Joining the Threads in Python

    To join the threads in Python, you can use the Thread.join() method from the threading module. Which generally is used to block the calling thread until the thread on which join() was called terminates. The termination may be either normal, because of an unhandled exception or until the optional timeout occurs. You can call join() multiple times. However, if you try to join the current thread or attempts to join a thread before starting it with the start() method, will raise the RuntimeError exception.

    Following is the syntax of the Thread.join() method −

    thread.join(timeout)

    Where, the timeout is an optional parameter that takes a floating-point number specifying the maximum wait time in seconds (or fractions thereof). If it is not provided or None, the method will block until the thread terminates.

    This method always returns None. After calling join(), you can use is_alive() to check if the thread is still running. This is useful to determine if the join() call timed out.

    Example

    The following example demonstrates the use of join() in a multithreaded program. It starts two threads (thread1 and thread2). Initially, it blocks the main thread until thread1 finishes executing the my_function_1. After thread1 completes, thread2.start() is called, followed by thread2.join() to ensure that the main thread waits until thread2 finishes executing my_function_2().

    from threading import Thread
    from time import sleep
    
    defmy_function_1(arg):for i inrange(arg):print("Child Thread 1 running", i)
          sleep(0.5)defmy_function_2(arg):for i inrange(arg):print("Child Thread 2 running", i)
          sleep(0.1)# Create thread objects
    thread1 = Thread(target=my_function_1, args=(5,))
    thread2 = Thread(target=my_function_2, args=(3,))# Start the first thread and wait for it to complete
    thread1.start()
    thread1.join()# Start the second thread and wait for it to complete
    thread2.start()
    thread2.join()print("Main thread finished...exiting")

    When the above code is executed, it produces the following result −

    Child Thread 1 running 0
    Child Thread 1 running 1
    Child Thread 1 running 2
    Child Thread 1 running 3
    Child Thread 1 running 4
    Child Thread 2 running 0
    Child Thread 2 running 1
    Child Thread 2 running 2
    Main thread finished...exiting
    

    Example

    Here is another example that demonstrates how the join() method with a timeout allows waiting for a thread to complete for a specified period, then proceeding even if the thread hasn’t finished.

    from threading import Thread
    from time import sleep
    
    defmy_function_1(arg):for i inrange(arg):print("Child Thread 1 running", i)
          sleep(0.5)defmy_function_2(arg):for i inrange(arg):print("Child Thread 2 running", i)
          sleep(0.1)# Create thread objects
    thread1 = Thread(target=my_function_1, args=(5,))
    thread2 = Thread(target=my_function_2, args=(3,))# Start the first thread and wait for 0.2 seconds
    thread1.start()
    thread1.join(timeout=0.2)# Start the second thread and wait for it to complete
    thread2.start()
    thread2.join()print("Main thread finished...exiting")

    When you run the above code, you can see the following output −

    Child Thread 1 running 0
    Child Thread 2 running 0
    Child Thread 2 running 1
    Child Thread 2 running 2
    Child Thread 1 running 1
    Main thread finished...exiting
    Child Thread 1 running 2
    Child Thread 1 running 3
    Child Thread 1 running 4
  • Python – Starting a Thread

    In Python, starting a thread involves using the start() method provided by the Thread class in the threading module. This method initiates the thread’s activity and automatically calls its run() method in a separate thread of execution. Meaning that, when you call start() on each thread object (for example., thread1, thread2, thread3) to initiate their execution.

    Python to launch separate threads that concurrently execute the run() method defined in each Thread instance. And the main thread continues its execution after starting the child threads.

    In this tutorial, you will see a detailed explanation and example of how to use the start() method effectively in multi-threaded programming to understand its behavior in multi-thread applications.

    Starting a Thread in Python

    The start() method is fundamental for beginning the execution of a thread. It sets up the thread’s environment and schedules it to run. Importantly, it should only be called once per Thread object. If this method is called more than once on the same Thread object, it will raise a RuntimeError.

    Here is the syntax for using the start() method on a Thread object −

    threading.thread.start()

    Example

    let’s see the below example, that demonstrates how to start a new thread in Python using the start() method.

    from threading import Thread
    from time import sleep
    
    defmy_function(arg):for i inrange(arg):print("child Thread running", i)
          sleep(0.5)
    thread = Thread(target = my_function, args =(10,))
    thread.start()print("thread finished...exiting")

    When the above code is executed, it produces the following result

    child Thread running 0
    thread finished...exiting
    child Thread running 1
    child Thread running 2
    child Thread running 3
    child Thread running 4
    child Thread running 5
    child Thread running 6
    child Thread running 7
    child Thread running 8
    child Thread running 9
    

    Example

    Here is another example demonstrating the working of the start() method. You can observe that, by not calling the start() method on thread2, it remains inactive and does not begin execution.

    import threading
    import time
    
    classMyThread(threading.Thread):def__init__(self, threadID, name, counter):
          threading.Thread.__init__(self)
          self.threadID = threadID
          self.name = name
          self.counter = counter
    
       defrun(self):print("Starting "+ self.name)
          print_time(self.name, self.counter)print("Exiting "+ self.name)defprint_time(threadName, counter):while counter:
          time.sleep(1)print("%s: %s"%(threadName, time.ctime(time.time())))
          counter -=1# Create new threads
    thread1 = MyThread(1,"Thread-1",1)
    thread2 = MyThread(2,"Thread-2",2)
    thread3 = MyThread(3,"Thread-3",3)# Start new Threads
    thread1.start()
    thread3.start()print("Exiting Main Thread")

    The above code will produce the following output −

    Starting Thread-1
    Starting Thread-3
    Exiting Main Thread
    Thread-1: Mon Jun 24 18:24:59 2024
    Exiting Thread-1
    Thread-3: Mon Jun 24 18:24:59 2024
    Thread-3: Mon Jun 24 18:25:00 2024
    Thread-3: Mon Jun 24 18:25:01 2024
    Exiting Thread-3
  • Python – Creating a Thread

    Creating a thread in Python involves initiating a separate flow of execution within a program, allowing multiple operations to run concurrently. This is particularly useful for performing tasks simultaneously, such as handling various I/O operations in parallel.

    Python provides multiple ways to create and manage threads.

    • Creating a thread using the threading module is generally recommended due to its higher-level interface and additional functionalities.
    • On the other hand, the _thread module offers a simpler, lower-level approach to create and manage threads, which can be useful for straightforward, low-overhead threading tasks.

    In this tutorial, you will learn the basics of creating threads in Python using different approaches. We will cover creating threads using functions, extending the Thread class from the threading module, and utilizing the _thread module.

    Creating Threads with Functions

    You can create threads by using the Thread class from the threading module. In this approach, you can create a thread by simply passing a function to the Thread object. Here are the steps to start a new thread −

    • Define a function that you want the thread to execute.
    • Create a Thread object using the Thread class, passing the target function and its arguments.
    • Call the start method on the Thread object to begin execution.
    • Optionally, call the join method to wait for the thread to complete before proceeding.

    Example

    The following example demonstrates concurrent execution using threads in Python. It creates and starts multiple threads that execute different tasks concurrently by specifying user-defined functions as targets within the Thread class.

    from threading import Thread
    
    defaddition_of_numbers(x, y):
       result = x + y
       print('Addition of {} + {} = {}'.format(x, y, result))defcube_number(i):
       result = i **3print('Cube of {} = {}'.format(i, result))defbasic_function():print("Basic function is running concurrently...")
    
    Thread(target=addition_of_numbers, args=(2,4)).start()  
    Thread(target=cube_number, args=(4,)).start() 
    Thread(target=basic_function).start()

    On executing the above program, it will produces the following result −

    Addition of 2 + 4 = 6
    Cube of 4 = 64
    Basic function is running concurrently...
    

    Creating Threads by Extending the Thread Class

    Another approach to creating a thread is by extending the Thread class. This approach involves defining a new class that inherits from Thread and overriding its __init__ and run methods. Here are the steps to start a new thread −

    • Define a new subclass of the Thread class.
    • Override the __init__ method to add additional arguments.
    • Override the run method to implement the thread’s behavior.

    Example

    This example demonstrates how to create and manage multiple threads using a custom MyThread class that extends the threading.Thread class in Python.

    import threading
    import time
    
    exitFlag =0classmyThread(threading.Thread):def__init__(self, threadID, name, counter):
          threading.Thread.__init__(self)
          self.threadID = threadID
          self.name = name
          self.counter = counter
       defrun(self):print("Starting "+ self.name)
          print_time(self.name,5, self.counter)print("Exiting "+ self.name)defprint_time(threadName, counter, delay):while counter:if exitFlag:
             threadName.exit()
          time.sleep(delay)print("%s: %s"%(threadName, time.ctime(time.time())))
          counter -=1# Create new threads
    thread1 = myThread(1,"Thread-1",1)
    thread2 = myThread(2,"Thread-2",2)# Start new Threads
    thread1.start()
    thread2.start()print("Exiting Main Thread")

    When the above code is executed, it produces the following result −

    Starting Thread-1
    Starting Thread-2
    Exiting Main Thread
    Thread-1: Mon Jun 24 16:38:10 2024
    Thread-2: Mon Jun 24 16:38:11 2024
    Thread-1: Mon Jun 24 16:38:11 2024
    Thread-1: Mon Jun 24 16:38:12 2024
    Thread-2: Mon Jun 24 16:38:13 2024
    Thread-1: Mon Jun 24 16:38:13 2024
    Thread-1: Mon Jun 24 16:38:14 2024
    Exiting Thread-1
    Thread-2: Mon Jun 24 16:38:15 2024
    Thread-2: Mon Jun 24 16:38:17 2024
    Thread-2: Mon Jun 24 16:38:19 2024
    Exiting Thread-2
    

    Creating Threads using start_new_thread() Function

    The start_new_thread() function included in the _thread module is used to create a new thread in the running program. This module offers a low-level approach to threading. It is simpler but does not have some of the advanced features provided by the threading module.

    Here is the syntax of the _thread.start_new_thread() Function

    _thread.start_new_thread ( function, args[, kwargs])

    This function starts a new thread and returns its identifier. The function parameter specifies the function that the new thread will execute. Any arguments required by this function can be passed using args and kwargs.

    Example

    import _thread
    import time
    # Define a function for the threaddefthread_task( threadName, delay):for count inrange(1,6):
          time.sleep(delay)print("Thread name: {} Count: {}".format( threadName, count ))# Create two threads as followstry:
        _thread.start_new_thread( thread_task,("Thread-1",2,))
        _thread.start_new_thread( thread_task,("Thread-2",4,))except:print("Error: unable to start thread")whileTrue:pass
       
    thread_task("test",0.3)

    It will produce the following output −

    Thread name: Thread-1 Count: 1
    Thread name: Thread-2 Count: 1
    Thread name: Thread-1 Count: 2
    Thread name: Thread-1 Count: 3
    Thread name: Thread-2 Count: 2
    Thread name: Thread-1 Count: 4
    Thread name: Thread-1 Count: 5
    Thread name: Thread-2 Count: 3
    Thread name: Thread-2 Count: 4
    Thread name: Thread-2 Count: 5
    Traceback (most recent call last):
     File "C:\Users\user\example.py", line 17, in <module>
      while True:
    KeyboardInterrupt
    

    The program goes in an infinite loop. You will have to press ctrl-c to stop.

  • Python – Thread Life cycle

    A thread object goes through different stages during its life cycle. When a new thread object is created, it must be started, which calls the run() method of thread class. This method contains the logic of the process to be performed by the new thread. The thread completes its task as the run() method is over, and the newly created thread merges with the main thread.

    While a thread is running, it may be paused either for a predefined duration or it may be asked to pause till a certain event occurs. The thread resumes after the specified interval or the process is over.

    thread_life_cycle

    States of a Thread Life Cycle in Python

    Following are the stages of the Python Thread life cycle −

    • Creating a Thread − To create a new thread in Python, you typically use the Thread class from the threading module.
    • Starting a Thread − Once a thread object is created, it must be started by calling its start() method. This initiates the thread’s activity and invokes its run() method in a separate thread.
    • Paused/Blocked State − Threads can be paused or blocked for various reasons, such as waiting for I/O operations to complete or another thread to perform a task. This is typically managed by calling its join() method. This blocks the calling thread until the thread being joined terminates.
    • Synchronizing Threads − Synchronization ensures orderly execution and shared resource management among threads. This can be done by using synchronization primitives like locks, semaphores, or condition variables.
    • Termination − A thread terminates when its run() method completes execution, either by finishing its task or encountering an exception.

    Example: Python Thread Life Cycle Demonstration

    This example demonstrates the thread life cycle in Python by showing thread creation, starting, execution, and synchronization with the main thread.

    import threading
    
    deffunc(x):print('Current Thread Details:', threading.current_thread())for n inrange(x):print('{} Running'.format(threading.current_thread().name), n)print('Internal Thread Finished...')# Create thread objects
    t1 = threading.Thread(target=func, args=(2,))
    t2 = threading.Thread(target=func, args=(3,))# Start the threadsprint('Thread State: CREATED')
    t1.start()
    t2.start()# Wait for threads to complete
    t1.join()
    t2.join()print('Threads State: FINISHED')# Simulate main thread workfor i inrange(3):print('Main Thread Running', i)print("Main Thread Finished...")

    Output

    When the above code is executed, it produces the following output −

    Thread State: CREATED
    Current Thread Details: <Thread(Thread-1 (func), started 140051032258112)>
    Thread-1 (func) Running 0
    Thread-1 (func) Running 1
    Internal Thread Finished...
    Current Thread Details: <Thread(Thread-2 (func), started 140051023865408)>
    Thread-2 (func) Running 0
    Thread-2 (func) Running 1
    Thread-2 (func) Running 2
    Internal Thread Finished...
    Threads State: FINISHED
    Main Thread Running 0
    Main Thread Running 1
    Main Thread Running 2
    Main Thread Finished...
    

    Example: Using a Synchronization Primitive

    Here is another example demonstrates the thread life cycle in Python, including creation, starting, running, and termination states, along with synchronization using a semaphore.

    import threading
    import time
    
    # Create a semaphore 
    semaphore = threading.Semaphore(2)defworker():with semaphore:print('{} has started working'.format(threading.current_thread().name))
          time.sleep(2)print('{} has finished working'.format(threading.current_thread().name))# Create a list to keep track of thread objects
    threads =[]# Create and start 5 threadsfor i inrange(5):
       t = threading.Thread(target=worker, name='Thread-{}'.format(i+1))
       threads.append(t)print('{} has been created'.format(t.name))
       t.start()# Wait for all threads to completefor t in threads:
       t.join()print('{} has terminated'.format(t.name))print('Threads State: All are FINISHED')print("Main Thread Finished...")

    Output

    When the above code is executed, it produces the following output −

    Thread-1 has been created
    Thread-1 has started working
    Thread-2 has been created
    Thread-2 has started working
    Thread-3 has been created
    Thread-4 has been created
    Thread-5 has been created
    Thread-1 has finished working
    Thread-2 has finished working
    Thread-3 has started working
    Thread-1 has terminated
    Thread-2 has terminated
    Thread-4 has started working
    Thread-3 has finished working
    Thread-5 has started working
    Thread-3 has terminated
    Thread-4 has finished working
    Thread-4 has terminated
    Thread-5 has finished working
    Thread-5 has terminated
    Threads State: All are FINISHED
    Main Thread Finished...
  • Python – Multithreading

    In Python, multithreading allows you to run multiple threads concurrently within a single process, which is also known as thread-based parallelism. This means a program can perform multiple tasks at the same time, enhancing its efficiency and responsiveness.

    Multithreading in Python is especially useful for multiple I/O-bound operations, rather than for tasks that require heavy computation.

    Generally, a computer program sequentially executes the instructions, from start to the end. Whereas, Multithreading divides the main task into more than one sub-task and executes them in an overlapping manner.

    Comparison with Processes

    An operating system is capable of handling multiple processes concurrently. It allocates a separate memory space to each process so that one process cannot access or write anything in other’s space.

    On the other hand, a thread can be considered a lightweight sub-process in a single program that shares the memory space allocated to it, facilitating easier communication and data sharing. As they are lightweight and do not require much memory overhead; they are cheaper than processes.

    multithreading

    A process always starts with a single thread (main thread). As and when required, a new thread can be started and sub task is delegated to it. Now the two threads are working in an overlapping manner. When the task assigned to the secondary thread is over, it merges with the main thread.

    A thread has a beginning, an execution sequence, and a conclusion. It has an instruction pointer that keeps track of where it is currently running within its context.

    • It can be pre-empted (interrupted)
    • It can temporarily be put on hold (also known as sleeping) while other threads are running – this is called yielding.

    Thread Handling Modules in Python

    Python’s standard library provides two main modules for managing threads: _thread and threading.

    The _thread Module

    The _thread module, also known as the low-level thread module, has been a part of Python’s standard library since version 2. It offers a basic API for thread management, supporting concurrent execution of threads within a shared global data space. The module includes simple locks (mutexes) for synchronization purposes.

    The threading Module

    The threading module, introduced in Python 2.4, builds upon _thread to provide a higher-level and more comprehensive threading API. It offers powerful tools for managing threads, making it easier to work with threads in Python applications.

    Key Features of the threading Module

    The threading module exposes all the methods of the thread module and provides some additional methods −

    • threading.activeCount() Returns the number of thread objects that are active.
    • threading.currentThread() Returns the number of thread objects in the caller’s thread control.
    • threading.enumerate() Returns a list of all thread objects that are currently active.

    In addition to the methods, the threading module has the Thread class that implements threading. The methods provided by the Thread class are as follows −

    • run() The run() method is the entry point for a thread.
    • start() The start() method starts a thread by calling the run method.
    • join([time]) The join() waits for threads to terminate.
    • isAlive() The isAlive() method checks whether a thread is still executing.
    • getName() The getName() method returns the name of a thread.
    • setName() The setName() method sets the name of a thread.

    Starting a New Thread

    To create and start a new thread in Python, you can use either the low-level _thread module or the higher-level threading module. The threading module is generally recommended due to its additional features and ease of use. Below, you can see both approaches.

    Starting a New Thread Using the _thread Module

    The start_new_thread() method of the _thread module provides a basic way to create and start new threads. This method provides a fast and efficient way to create new threads in both Linux and Windows. Following is the syntax of the method −

    thread.start_new_thread(function, args[, kwargs])

    This method call returns immediately, and the new thread starts executing the specified function with the given arguments. When the function returns, the thread terminates.

    Example

    This example demonstrates how to use the _thread module to create and run threads. Each thread runs the print_name function with different arguments. The time.sleep(0.5) call ensures that the main program waits for the threads to complete their execution before exiting.

    import _thread
    import time
    
    defprint_name(name,*arg):print(name,*arg)
    
    name="Tutorialspoint..."
    _thread.start_new_thread(print_name,(name,1))
    _thread.start_new_thread(print_name,(name,1,2))
    
    time.sleep(0.5)

    When the above code is executed, it produces the following result −

    Tutorialspoint... 1
    Tutorialspoint... 1 2
    

    Although it is very effective for low-level threading, but the _thread module is limited compared to the threading module, which offers more features and higher-level thread management.

    Starting a New Thread Using the Threading Module

    The threading module provides the Thread class, which is used to create and manage threads.

    Here are a few steps to start a new thread using the threading module −

    • Create a function that you want the thread to execute.
    • Then create a Thread object using the Thread class by passing the target function and its arguments.
    • Call the start method on the Thread object to begin execution.
    • Optionally, call the join method to wait for the thread to complete before proceeding.

    Example

    The following example demonstrates how to create and start threads using the threading module. It runs a function print_name that prints a name along with some arguments. This example creates two threads, starts them using the start() method, and waits for them to complete using the join method.

    import threading
    import time
    
    defprint_name(name,*args):print(name,*args)
    
    name ="Tutorialspoint..."# Create and start threads
    thread1 = threading.Thread(target=print_name, args=(name,1))
    thread2 = threading.Thread(target=print_name, args=(name,1,2))
    
    thread1.start()
    thread2.start()# Wait for threads to complete
    thread1.join()
    thread2.join()print("Threads are finished...exiting")

    When the above code is executed, it produces the following result −

    Tutorialspoint... 1
    Tutorialspoint... 1 2
    Threads are finished...exiting
    

    Synchronizing Threads

    The threading module provided with Python includes a simple-to-implement locking mechanism that allows you to synchronize threads. A new lock is created by calling the Lock() method, which returns the new lock.

    The acquire(blocking) method of the new lock object is used to force threads to run synchronously. The optional blocking parameter enables you to control whether the thread waits to acquire the lock.

    If blocking is set to 0, the thread returns immediately with a 0 value if the lock cannot be acquired and with a 1 if the lock was acquired. If blocking is set to 1, the thread blocks and wait for the lock to be released.

    The release() method of the new lock object is used to release the lock when it is no longer required.

    Example

    import threading
    import time
    
    classmyThread(threading.Thread):def__init__(self, threadID, name, counter):
          threading.Thread.__init__(self)
          self.threadID = threadID
          self.name = name
          self.counter = counter
       defrun(self):print("Starting "+ self.name)# Get lock to synchronize threads
          threadLock.acquire()
          print_time(self.name, self.counter,3)# Free lock to release next thread
          threadLock.release()defprint_time(threadName, delay, counter):while counter:
          time.sleep(delay)print("%s: %s"%(threadName, time.ctime(time.time())))
          counter -=1
    
    threadLock = threading.Lock()
    threads =[]# Create new threads
    thread1 = myThread(1,"Thread-1",1)
    thread2 = myThread(2,"Thread-2",2)# Start new Threads
    thread1.start()
    thread2.start()# Add threads to thread list
    threads.append(thread1)
    threads.append(thread2)# Wait for all threads to completefor t in threads:
        t.join()print("Exiting Main Thread")

    When the above code is executed, it produces the following result −

    Starting Thread-1
    Starting Thread-2
    Thread-1: Thu Mar 21 09:11:28 2013
    Thread-1: Thu Mar 21 09:11:29 2013
    Thread-1: Thu Mar 21 09:11:30 2013
    Thread-2: Thu Mar 21 09:11:32 2013
    Thread-2: Thu Mar 21 09:11:34 2013
    Thread-2: Thu Mar 21 09:11:36 2013
    Exiting Main Thread
    

    Multithreaded Priority Queue

    The Queue module allows you to create a new queue object that can hold a specific number of items. There are following methods to control the Queue −

    • get() − The get() removes and returns an item from the queue.
    • put() − The put adds item to a queue.
    • qsize() − The qsize() returns the number of items that are currently in the queue.
    • empty() − The empty( ) returns True if queue is empty; otherwise, False.
    • full() − the full() returns True if queue is full; otherwise, False.

    Example

    import queue
    import threading
    import time
    
    exitFlag =0classmyThread(threading.Thread):def__init__(self, threadID, name, q):
          threading.Thread.__init__(self)
          self.threadID = threadID
          self.name = name
          self.q = q
       defrun(self):print("Starting "+ self.name)
          process_data(self.name, self.q)print("Exiting "+ self.name)defprocess_data(threadName, q):whilenot exitFlag:
          queueLock.acquire()ifnot workQueue.empty():
             data = q.get()
             queueLock.release()print("%s processing %s"%(threadName, data))else:
             queueLock.release()
             time.sleep(1)
    
    threadList =["Thread-1","Thread-2","Thread-3"]
    nameList =["One","Two","Three","Four","Five"]
    queueLock = threading.Lock()
    workQueue = queue.Queue(10)
    threads =[]
    threadID =1# Create new threadsfor tName in threadList:
       thread = myThread(threadID, tName, workQueue)
       thread.start()
       threads.append(thread)
       threadID +=1# Fill the queue
    queueLock.acquire()for word in nameList:
       workQueue.put(word)
    queueLock.release()# Wait for queue to emptywhilenot workQueue.empty():pass# Notify threads it's time to exit
    exitFlag =1# Wait for all threads to completefor t in threads:
       t.join()print("Exiting Main Thread")

    When the above code is executed, it produces the following result −

    Starting Thread-1
    Starting Thread-2
    Starting Thread-3
    Thread-1 processing One
    Thread-2 processing Two
    Thread-3 processing Three
    Thread-1 processing Four
    Thread-2 processing Five
    Exiting Thread-3
    Exiting Thread-1
    Exiting Thread-2
    Exiting Main Thread
  • Python – Debugger (PDB)

    Python provides a built-in module called pdb for debugging Python programs. The pdb module can be used to set breakpoints, move through code, check value of variables, and evaluate expressions. In this chapter, we will explain how to use the pdb module to debug Python programs.

    Following topics will be covered in this chapter −

    The pdb Module in Python

    The pdb module is a built-in Python module that provides an interactive debugging environment for Python programs. Internally, it is defined as a class called Pdb, which is a subclass of the bdb.Bdb class. The main advantage of pdb debugger it will run completely in the terminal, meaning you don’t need to install any additional software or IDE to use it.

    To start debugging with pdb, first import the module in your Python script −

    import pdb
    

    Then, you can set breakpoints in your code using the pdb.set_trace() function. When the program execution reaches a breakpoint, it will pause and enter the pdb debugging terminal.

    Basic Commands in pdb Terminal

    After entering the debugging terminal, you can use various commands to control the execution of your program. Here are some of the most commonly used pdb commands −

    • n (next) − Execute the next line of code.
    • s (step) − Step into a function call.
    • c (continue) − Continue execution until the next breakpoint.
    • l (list) − List the current location in the code.
    • p (print) − Print the value of a variable.
    • q (quit) − Quit the debugger and exit the program.
    • b (break) − Set a breakpoint at a specified line number or function.

    For a complete list of pdb commands, you can use the h command within the pdb interactive environment. It will look like this −

    pdb help command

    Example of Using pdb Module

    The following example shows how pdb can be used to stop the execution of a program at certain points and inspect the values of variables.

    import pdb
    
    defadd(a, b):return a + b
    
    defsubtract(a, b):return a - b
    
    pdb.set_trace()# Set a breakpoint here
    result = add(20,30)print("Addition Result:", result)
    
    pdb.set_trace()# Set another breakpoint here
    result = subtract(20,10)print("Subtraction Result:", result)

    When the program execution reaches the pdb.set_trace() line, it will pause and enter the pdb terminal. So, the output of the program will be −

    > /tmp/ipython-input-1706780559.py(11)<cell line: 0>()
          9 x = 10
         10 y = 20
    ---> 11 pdb.set_trace()  # Set a breakpoint here
         12 result = add(x, y)
         13 print("Addition Result:", result)
    
    ipdb> c
    Addition Result: 30
    
    ipdb> c
    Subtraction Result: 10
    

    Explanation − In the output, you can see that at this terminal, we pressed the c command to continue execution until the next breakpoint. So we get addition result and program paused. Again we pressed c to continue execution and print the subtraction result.

    Checking Variable Type at Runtime

    To check the type of a variable at runtime while using pdb, you can use the whatis command at the pdb prompt. The example below demonstrates how to use the whatis command −

    import pdb
    
    a =10
    b =20.5
    c ="Hello, World!"
    
    pdb.set_trace()# Set a breakpoint here

    The output of the program will be −

    > /tmp/ipython-input-1057349253.py(7)<cell line: 0>()
          3 a = 10
          4 b = 20.5
          5 c = "Hello, World!"
          6 
    ----> 7 pdb.set_trace()  # Set a breakpoint here
    
    ipdb> whatis a
    <class 'int'>
    ipdb> whatis b
    <class 'float'>
    ipdb> whatis c
    <class 'str'>
    

    Post-Mortem Debugging with pdb Module

    The post-mortem debugging refers to the process of analyzing a program after it has crashed or reached an exception state. The pdb module can be used to perform post-mortem debugging using the pdb.post_mortem() function. You can call this function inside any exception handler at any point in your code.

    The pdb.post_mortem() function takes the traceback object as an argument, you can get it using the sys.exc_info() function. Here is an example that demonstrates how to use the pdb.post_mortem() function for post-mortem debugging −

    import pdb
    import sys
    
    defcalculate_inverse(x):print(f"Calculating inverse for {x}:")
        result =1/ x
        print(result)try:
        calculate_inverse(10)
        calculate_inverse(0)# This will raise errorexcept Exception:# This block is executed only after an exception (crash) occurs.print("\n An exception occurred! Starting post-mortem debugging... ")# Get the traceback object of the exception
        extype, exvalue, tb = sys.exc_info()# Launch the post-mortem debugger
        pdb.post_mortem(tb)

    In this example, the calculate_inverse() function is used to calculate the inverse of zero. This will create a ZeroDivisionError exception. When the exception occurs, the program will enter the except block. Here we call the pdb.post_mortem(tb) function to start the post-mortem debugging session. The output of the program will be −

    Calculating inverse for 10:
    0.1
    Calculating inverse for 0:
    
     An exception occurred! Starting post-mortem debugging... 
    > /tmp/ipython-input-1016125975.py(6)calculate_inverse()
          4 def calculate_inverse(x):
          5     print(f"Calculating inverse for {x}:")
    ----> 6     result = 1 / x
          7     print(result)
          8 
    
    ipdb> p x
    0
    

    Explanation − In the output, you can see that the program has paused at the line where the exception occurred. We printed the value of the variable x using the p command. We will get clear idea that the program crashed because we tried to divide by zero.

    Managing Breakpoints in pdb Module

    We already saw how to set breakpoints using the pdb.set_trace() function. While working on large applications, we often set multiple breakpoints in different parts of the code. So it is important properly manage these breakpoints. Here are few commands that you can use to manage breakpoints in pdb −

    • break − The break command is used to set a breakpoint at any line number from the pdb prompt. For example, break main.py:9 will set a breakpoint at line number 9 in the main.py file.
    • disable − The disable command is used to disable a specific breakpoint by its number. For example, disable 1 will disable the breakpoint with number 1.
    • enable − The enable command is used to enable the breakpoint that was disabled by disable command. For example, enable 1 will enable the breakpoint with number 1.
    • clear − The clear command is used to remove a breakpoint by its number. For example, clear 1 will remove the breakpoint with number 1.

    Here is an example that demonstrates how to manage breakpoints in pdb −

    import pdb
    defmultiply(a, b):return a * b
    defdivide(a, b):return a / b
    
    pdb.set_trace()# Set a breakpoint here
    result1 = multiply(10,20)
    
    result2 = divide(10,0)print("Multiplication Result:", result1)print("Division Result:", result2)

    In the code, we set a breakpoint using the pdb.set_trace() function. When the program reaches this line, it will pause and enter the pdb session. The output of the program will be −

    None
    > /tmp/ipython-input-2031724579.py(7)<cell line: 0>()
          5     return a / b
          6 
    ----> 7 pdb.set_trace()             # Set a breakpoint here
    
    ipdb> break main.py:8 
    Breakpoint 1 at /tmp/ipython-input-2031724579.py:8
    ipdb> break
    Num Type         Disp Enb Where
    1   breakpoint   keep yes at /tmp/ipython-input-2031724579.py:8
    ipdb> disable 1
    Disabled breakpoint 1 at /tmp/ipython-input-2031724579.py:8
    ipdb> enable 1
    Enabled breakpoint 1 at /tmp/ipython-input-2031724579.py:8
    

    Explanation − In the output, you can see that we used the break command at terminal to set a breakpoint at line number 8. Then we displayed the list of breakpoints using the break command without any arguments. After that, we disabled the breakpoint using the disable command and then enabled it again using the enable command.

    Conclusion

    In this chapter, we learned how to use the pdb module to debug Python programs. We learned the basic commands in pdb, how to set and manage breakpoints, how to check variable types at runtime, and how to perform post-mortem debugging. The debugging tool is very useful for identifying and fixing issues in large code bases. With proper practice, you can easily find and fix bugs in your Python programs.

  • Python – Built-in Exceptions

    Built-in exceptions are pre-defined error classes in Python that handle errors and exceptional conditions in programs. They are derived from the base class “BaseException” and are part of the standard library.

    Standard Built-in Exceptions in Python

    Here is a list of Standard Exceptions available in Python −

    Sr.No.Exception Name & Description
    1ExceptionBase class for all exceptions
    2StopIterationRaised when the next() method of an iterator does not point to any object.
    3SystemExitRaised by the sys.exit() function.
    4StandardErrorBase class for all built-in exceptions except StopIteration and SystemExit.
    5ArithmeticErrorBase class for all errors that occur for numeric calculation.
    6OverflowErrorRaised when a calculation exceeds maximum limit for a numeric type.
    7FloatingPointErrorRaised when a floating point calculation fails.
    8ZeroDivisonErrorRaised when division or modulo by zero takes place for all numeric types.
    9AssertionErrorRaised in case of failure of the Assert statement.
    10AttributeErrorRaised in case of failure of attribute reference or assignment.
    11EOFErrorRaised when there is no input from either the raw_input() or input() function and the end of file is reached.
    12ImportErrorRaised when an import statement fails.
    13KeyboardInterruptRaised when the user interrupts program execution, usually by pressing Ctrl+C.
    14LookupErrorBase class for all lookup errors.
    15IndexErrorRaised when an index is not found in a sequence.
    16KeyErrorRaised when the specified key is not found in the dictionary.
    17NameErrorRaised when an identifier is not found in the local or global namespace.
    18UnboundLocalErrorRaised when trying to access a local variable in a function or method but no value has been assigned to it.
    19EnvironmentErrorBase class for all exceptions that occur outside the Python environment.
    20IOErrorRaised when an input/ output operation fails, such as the print statement or the open() function when trying to open a file that does not exist.
    21OSErrorRaised for operating system-related errors.
    22SyntaxErrorRaised when there is an error in Python syntax.
    23IndentationErrorRaised when indentation is not specified properly.
    24SystemErrorRaised when the interpreter finds an internal problem, but when this error is encountered the Python interpreter does not exit.
    25SystemExitRaised when Python interpreter is quit by using the sys.exit() function. If not handled in the code, causes the interpreter to exit.
    26TypeErrorRaised when an operation or function is attempted that is invalid for the specified data type.
    27ValueErrorRaised when the built-in function for a data type has the valid type of arguments, but the arguments have invalid values specified.
    28RuntimeErrorRaised when a generated error does not fall into any category.
    29NotImplementedErrorRaised when an abstract method that needs to be implemented in an inherited class is not actually implemented.

    Here are some examples of standard exceptions −

    IndexError

    It is shown when trying to access item at invalid index.

    numbers=[10,20,30,40]for n inrange(5):print(numbers[n])

    It will produce the following output −

    10
    20
    30
    40
    Traceback (most recent call last):
    
       print (numbers[n])
    IndexError: list index out of range
    

    ModuleNotFoundError

    This is displayed when module could not be found.

    import notamodule
    Traceback (most recent call last):import notamodule
    ModuleNotFoundError: No module named 'notamodule'

    KeyError

    It occurs as dictionary key is not found.

    D1={'1':"aa",'2':"bb",'3':"cc"}print( D1['4'])
    Traceback (most recent call last):
    
       D1['4']
    KeyError:'4'

    ImportError

    It is shown when specified function is not available for import.

    from math import cube
    Traceback (most recent call last):from math import cube
    ImportError: cannot import name 'cube'

    StopIteration

    This error appears when next() function is called after iterator stream exhausts.

    .it=iter([1,2,3])next(it)next(it)next(it)next(it)
    Traceback (most recent call last):next(it)
    StopIteration
    

    TypeError

    This is shown when operator or function is applied to an object of inappropriate type.

    print('2'+2)
    Traceback (most recent call last):'2'+2
    TypeError: must be str,notint

    ValueError

    It is displayed when function’s argument is of inappropriate type.

    print(int('xyz'))
    Traceback (most recent call last):int('xyz')
    ValueError: invalid literal forint()with base 10:'xyz'

    NameError

    This is encountered when object could not be found.

    print(age)
    Traceback (most recent call last):
    
       age
    NameError: name 'age'isnot defined
    

    ZeroDivisionError

    It is shown when second operator in division is zero.

    x=100/0
    Traceback (most recent call last):
    
       x=100/0
    ZeroDivisionError: division by zero
    

    KeyboardInterrupt

    When user hits the interrupt key normally Control-C during execution of program.

    name=input('enter your name')
    enter your name^c
    Traceback (most recent call last):
    
       name=input('enter your name')
    KeyboardInterrupt
    

    Hierarchy of Built-in Exceptions

    The exceptions in Python are organized in a hierarchical structure, with “BaseException” at the top. Here is a simplified hierarchy −

    • BaseException
      • SystemExit
      • KeyboardInterrupt
    • Exception
      • ArithmeticError
        • FloatingPointError
        • OverflowError
        • ZeroDivisionError
      • AttributeError
      • EOFError
      • ImportError
      • LookupError
        • IndexError
        • KeyError
      • MemoryError
      • NameError
        • UnboundLocalError
      • OSError
        • FileNotFoundError
      • TypeError
      • ValueError
      • —(Many others)—

    How to Use Built-in Exceptions

    As we already know that built-in exceptions in Python are pre-defined classes that handle specific error conditions. Now, here is a detailed guide on how to use them effectively in your Python programs −

    Handling Exceptions with try-except Blocks

    The primary way to handle exceptions in Python is using “try-except” blocks. This allows you to catch and respond to exceptions that may occur during the execution of your code.

    Example

    In the following example, the code that may raise an exception is placed inside the “try” block. The “except” block catches the specified exception “ZeroDivisionError” and handles it

    try:
       result =1/0except ZeroDivisionError as e:print(f"Caught an exception: {e}")

    Following is the output obtained −

    Caught an exception: division by zero
    

    Handling Multiple Exceptions

    You can handle multiple exceptions by specifying them in a tuple within the “except” block as shown in the example below −

    try:
       result =int('abc')except(ValueError, TypeError)as e:print(f"Caught a ValueError or TypeError: {e}")

    Output of the above code is as shown below −

    Caught a ValueError or TypeError: invalid literal for int() with base 10: 'abc'
    

    Using “else” and “finally” Blocks

    The “else” block is executed if the code block in the “try” clause does not raise an exception −

    try:
       number =int(input("Enter a number: "))except ValueError as e:print(f"Invalid input: {e}")else:print(f"You entered: {number}")

    Output of the above code varies as per the input given −

    Enter a number: bn
    Invalid input: invalid literal for int() with base 10: 'bn'
    

    The “finally” block is always executed, regardless of whether an exception occurred or not. It’s typically used for clean-up actions, such as closing files or releasing resources −

    try:file=open('example.txt','r')
       content =file.read()except FileNotFoundError as e:print(f"File not found: {e}")finally:file.close()print("File closed.")

    Following is the output of the above code −

    File closed.
    

    Explicitly Raising Built-in Exceptions

    In Python, you can raise built-in exceptions to indicate errors or exceptional conditions in your code. This allows you to handle specific error scenarios and provide informative error messages to users or developers debugging your application.

    Syntax

    Following is the basic syntax for raising built-in exception −

    raise ExceptionClassName("Error message")

    Example

    In this example, the “divide” function attempts to divide two numbers “a” and “b”. If “b” is zero, it raises a “ZeroDivisionError” with a custom message −

    defdivide(a, b):if b ==0:raise ZeroDivisionError("Cannot divide by zero")return a / b
    
    try:
       result = divide(10,0)except ZeroDivisionError as e:print(f"Error: {e}")

    The output obtained is as shown below −

    Error: Cannot divide by zero
  • Python – Warnings

    In Python, a warning is a message that indicates that something unexpected happened while running your code. Unlike an error, a warning will not stop the program from running. Warnings are used to alert the user about potential issues or deprecated features in the code.

    For example, if you are using a deprecated module in your code, you will get a warning message saying that the module is deprecated and will be removed in future versions of Python. But your code may still work as expected.

    Display a Warning Message

    To display a warning message in Python, you can use the warn() function from the warnings module. Here is an example −

    import warnings
    
    # Normal messageprint("TutorialsPoint")# Warning message
    warnings.warn("You got a warning!")

    The output of the above code will be:

    TutorialsPoint
    
    Warnings/Errors:
    /home/cg/root/d732ac89/main.py:7: UserWarning: You got a warning!
      warnings.warn("You got a warning!")
    

    Types of Warnings Classes

    The warning in python is handled by a set of classes based upon the exception handling mechanism. The most common types of warnings are −

    1. UserWarning Class

    The UserWarning is the default warning class in Python. If you have any warning that does not fall into any of the other categories, it will be classified as a UserWarning. To specify a UserWarning, use UserWarning as the second argument to the warn() function. Here is an example −

    import warnings
    
    # Warning message
    warnings.warn("You got a warning!", UserWarning)

    2. DeprecationWarning Class

    The DeprecationWarning is used to indicate that a feature or module is deprecated and will be removed in future versions of Python. To specify a DeprecationWarning, use DeprecationWarning as the second argument to the warn() function. Here is an example −

    import warnings
    
    # Warning message
    warnings.warn("This feature is deprecated!", DeprecationWarning)

    3. SyntaxWarning Class

    The SyntaxWarning is used to indicate that there is a syntax-related issue in the code, but it is not severe enough to raise a SyntaxError. To specify a SyntaxWarning, use SyntaxWarning as the second argument to the warn() function. Here is an example −

    import warnings
    
    # Warning message
    warnings.warn("This is a syntax warning!", SyntaxWarning)# Function with potential syntax issuedeffunc(x):return x is10# using "is" instead of "=="

    4. RuntimeWarning Class

    The RuntimeWarning is a base class used to indicate that there is a doubtful issue related to runtime. To specify a RuntimeWarning, use RuntimeWarning as the second argument to the warn() function. Here is an example −

    import warnings
    
    # Warning message
    warnings.warn("This is a runtime warning!", RuntimeWarning)

    5. ImportWarning Class

    The ImportWarning is used to indicate that there is an issue related to the import of a module. To specify an ImportWarning, use ImportWarning as the second argument to the warn() function. Here is an example −

    import warnings
    
    # Warning message
    warnings.warn("This is an import warning!", ImportWarning)

    Warning Filters

    Warning filters are used to control the behavior of warning messages in Python. For example, if you feel that a warning is severe, you can convert it into an error so that program will stop running. Or, if you feel that a warning is not important, you can ignore it.

    The filterwarnings() function from the warnings module can be used to specify warning filters. It can take following parameters:

    • default − This is the default behavior. It displays the first occurrence of each warning for each location where the warning is issued.
    • always − This option will always display the warning message, even if it has been displayed before.
    • ignore − This option is used to prevent the display of warning messages.
    • error − This option converts the warning into an exception, which will stop the program from running.
    • module − This option will print the first occurrence of matching warnings for each module where the warning is issued.
    • once − This option will print the first occurrence of matching warnings, regardless of the location.

    Example: Turn Warning to an Error

    Here is a simple python code that shows how to turn warnings into errors −

    import warnings
    
    # Turn warning into an error
    warnings.filterwarnings("error", category=UserWarning)try:
        warnings.warn("This is a warning!", UserWarning)except UserWarning as e:print("Caught as error:", e)

    The output of the above code will be −

    Caught as error: This is a warning!
    

    Example: Ignore Warnings

    Here is a simple python code that shows how to ignore warnings −

    import warnings
    
    # Ignore all warnings
    warnings.filterwarnings("ignore")print("Warning message: ")
    warnings.warn("This warning will be ignored!")

    The output of the above code will be:

    Warning message: 
    

    Function Available in Warning Module

    The warnings module has couple of functions that you can use to manage warnings in your Python code. Here are some of the most commonly used functions −

    1. warnings.warn()

    This function is used to issue a warning message. You can specify the warning message and the category of the warning. The syntax of the function is −

    warnings.warn(message, category=None, stacklevel=1, 
                  source=None,*, skip_file_prefixes=())

    Here, message is the warning message you want to display, and category is the type of warning (e.g., UserWarning, DeprecationWarning, etc.).

    The stacklevel argument can be used by wrapper functions written in Python to adjust the stack level of the warning. The skip_file_prefixes keyword argument can be used to indicate which stack frames are ignored when counting stack levels.

    2. warnings.showwarning()

    The showwarning() function is used to write warning messages to a file. Here is the syntax of the function:

    warnings.showwarning(message, category, filename, lineno,file=None, line=None)

    Here, message is the warning message, category is the type of warning, filename is the name of the file where the warning should be saved, lineno is the line number where the warning occurred.

    3. warnings.warn_explicit()

    The warn_explicit() function is used to send a warning message with more control over the warning’s context. It can be used to explicitly pass the message, category, filename and line number, and optionally the module name and the registry. Here is the syntax of the function −

    warnings.warn_explicit(message, category, filename, lineno, module=None, 
                            registry=None, module_globals=None, source=None)

    The parameters have same meaning as in the previous functions.

    4. warnings.filterwarnings()

    The filterwarnings() function is used to set the warning filters. we have already disscussed how to use this function to ignore, always display, or convert warnings into errors. Here is the proper syntax of the function −

    warnings.filterwarnings(action, message='', category=Warning, module='', lineno=0, append=False)

    The parameter “action” can take any of the following values: “default”, “always”, “ignore”, “error”, “module”, or “once”. The other parameters are optional.

    5. warnings.simplefilter()

    The simplefilter() function is a simpler interface to the filterwarnings() function. It is used to set a filter for a specific category of warnings. Here is the syntax of the function −

    warnings.simplefilter(action, category=Warning, lineno=0, append=False)

    The parameters have same meaning as in the previous functions.

    6. warnings.resetwarnings()

    The resetwarnings() function is used to reset the warning filters to their default state. This can be useful if you want to clear any custom warning filters you have set. Here is the syntax of the function −

    warnings.resetwarnings()

    This function does not take any parameters.

    Conclusion

    In this chapter, we learned about warnings in Python. Warnings are different from errors because they will not stop the program from running. It is just used to alert the programmer about potential issues in the code. The warnings module provides a way to issue warning messages and control their behavior.

  • Python – Assertions

    Assertions in Python

    Assertions in Python are statements that assert or assume a condition to be true. If the condition turns out to be false, Python raises an AssertionError exception. They are used to detect programming errors that should never occur if the code is correct.

    • The easiest way to think of an assertion is to liken it to a raise-if statement (or to be more accurate, a raise-if-not statement). An expression is tested, and if the result comes up false, an exception is raised.
    • Assertions are carried out by the assert statement, the newest keyword to Python, introduced in version 1.5.
    • Programmers often place assertions at the start of a function to check for valid input, and after a function call to check for valid output.

    The assert Statement

    In Python, assertions use the assert keyword followed by an expression. If the expression evaluates to False, an AssertionError is raised. Following is the syntax of assertion −

    assert condition, message
    

    Where,

    • condition − A boolean expression that should be true.
    • message (optional) − An optional message to be displayed if the assertion fails.

    Using Assertions

    Assertions are generally used during development and testing phases to check conditions that should always hold true.

    Example

    In the following example, we are using assertions to ensure that the variable “num” falls within the valid range of “0” to “100”. If the assertion fails, Python raises an “AssertionError”, preventing further execution of the subsequent print statement −

    print('Enter marks out of 100:')
    num =75assert num >=0and num <=100print('Marks obtained:', num)
    
    num =125assert num >=0and num <=100print('Marks obtained:', num)# This line won't be reached if assertion fails

    Following is the output of the above code −

    Enter marks out of 100:
    Marks obtained: 75
    Traceback (most recent call last):
      File "/home/cg/root/66723bd115007/main.py", line 7, in <module>
        assert num >= 0 and num <= 100
    AssertionError
    

    Custom Error Messages

    To display a custom error message when an assertion fails, include a string after the expression in the assert statement −

    assert num >=0and num <=100,"Only numbers in the range 0-100 are accepted"

    Handling AssertionError

    Assertions can be caught and handled like any other exception using a try-except block. If they are not handled, they will terminate the program and produce a traceback −

    try:
       num =int(input('Enter a number: '))assert num >=0,"Only non-negative numbers are accepted"print(num)except AssertionError as msg:print(msg)

    It will produce the following output −

    Enter a number: -87
    Only non-negative numbers are accepted
    

    Assertions vs. Exceptions

    Assertions are used to check internal state and invariants that should always be true. Whereas, exceptions helps in handling runtime errors and exceptional conditions that may occur during normal execution.

    Assertions are disabled by default in Python’s optimized mode (-O or python -O script.py). Therefore, they should not be used to enforce constraints that are required for the correct functioning of the program in production environments.