Python programming language has become a must skill set on most of the companies’ requirements in a software developer. Python programming language has become popular with machine learning community and is a basic need skill for data scientist/machine-learning developer/deep-learning developer.

In this guide, we walk you through most of the upto date possible Python interview questionsExample programs and references are provided whenever possible.

What is Python and what are its key features?

Python is a versatile, high-level programming language renowned for its readability and simplicity. Its key features include dynamic typing, automatic memory management, and a vast standard library that supports various programming paradigms such as procedural, object-oriented, and functional programming. Python’s syntax emphasizes code readability, allowing developers to express concepts in fewer lines compared to languages like C++ or Java, which enhances productivity and maintainability.

How do you start the Python shell?

To start the Python shell, open your terminal or command prompt and type the command python or python3, depending on your installation. This will launch the interactive Python interpreter where you can execute Python commands in real-time. The shell provides immediate feedback, making it useful for testing snippets of code and debugging.

$ python
Python 3.8.5 (default, Jul 28 2020, 12:59:40) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

What are Python decorators and how are they used?

Python decorators are a powerful tool that allow you to modify the behavior of functions or classes. They are higher-order functions that take another function as an argument, add some functionality, and return a new function. Decorators are commonly used for logging, enforcing access controls, instrumentation, and caching, enhancing code modularity and reusability.

@decorator
def function():
    pass

Explain the difference between lists and tuples in Python.

In Python, lists and tuples are both used to store collections of items. The primary difference is that lists are mutable, meaning their contents can be changed after creation, while tuples are immutable and cannot be altered. This immutability makes tuples suitable for fixed data and allows them to be used as keys in dictionaries, unlike lists.

How does Python handle memory management?

Python manages memory automatically through a private heap that stores all objects and data structures. It employs a garbage collector to recycle unused memory, primarily using reference counting to keep track of object references. When an object’s reference count drops to zero, it is eligible for garbage collection. Additionally, Python’s garbage collector can handle cyclic references to ensure efficient memory usage.

What is PEP 8 and why is it important?

PEP 8 is Python’s official style guide that outlines best practices for writing readable and maintainable code. It covers naming conventions, code layout, indentation, and more. Adhering to PEP 8 is important as it promotes consistency across Python codebases, making it easier for developers to collaborate and understand each other’s code.

Can you explain what a lambda function is in Python?

A lambda function in Python is an anonymous, small-scale function defined using the lambda keyword. It can take any number of arguments but can only have one expression. Lambda functions are often used in higher-order functions like map(), filter(), and sorted() for short, throwaway functions without the need to formally define them using def.

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

What are Python’s built-in data types?

Python has several built-in data types, including:

  • Numeric Types: int, float, complex
  • Sequence Types: list, tuple, range
  • Text Type: str
  • Mapping Type: dict
  • Set Types: set, frozenset
  • Boolean Type: bool
  • Binary Types: bytes, bytearray, memoryview

Each type serves different purposes and offers various methods for manipulation and data handling.

How do you handle exceptions in Python?

In Python, exceptions are handled using try-except blocks. You place the code that might raise an exception within the try block and handle specific exceptions in corresponding except blocks. Optionally, you can use else for code that runs if no exception occurs and finally for cleanup actions that should run regardless of whether an exception was raised.

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide by zero.")
else:
    print("Division successful.")
finally:
    print("Execution completed.")

What is the difference between deep copy and shallow copy?

A shallow copy of an object creates a new object but does not recursively copy nested objects; instead, it references them. In contrast, a deep copy creates a new object and recursively copies all nested objects, ensuring complete independence from the original. Shallow copies are faster and use less memory, but deep copies are necessary when full duplication is required.

import copy

original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[0][0] = 'changed'

print(shallow)  # [['changed', 2], [3, 4]]
print(deep)     # [[1, 2], [3, 4]]

Explain the Global Interpreter Lock (GIL) in Python.

The Global Interpreter Lock (GIL) is a mutex in CPython that ensures only one thread executes Python bytecode at a time. While it simplifies memory management and prevents race conditions, it limits the performance of CPU-bound multi-threaded programs. As a result, for parallel execution in CPU-intensive tasks, multiprocessing or alternative Python implementations without a GIL are often used.

What are Python modules and how do you use them?

Python modules are files containing Python code, such as functions, classes, and variables, that can be imported and used in other Python programs. They help in organizing code into manageable sections and promote code reuse. To use a module, you can use the import statement. For example, import math allows you to access mathematical functions like math.sqrt().

import math

result = math.sqrt(16)
print(result)  # Output: 4.0

How do you create a virtual environment in Python?

A virtual environment in Python is an isolated environment that allows you to manage dependencies for different projects separately. To create one, you can use the venv module. Run the command python -m venv myenv in your terminal, where myenv is the name of your environment. Activate it using source myenv/bin/activate on Unix or myenv\Scripts\activate on Windows.

$ python -m venv myenv
$ source myenv/bin/activate  # On Unix or MacOS
myenv\Scripts\activate     # On Windows

What is list comprehension and how is it used in Python?

List comprehension is a concise way to create lists in Python. It allows you to generate a new list by applying an expression to each item in an existing iterable, optionally filtering items with a condition. This approach is often more readable and efficient than using traditional loops.

# Traditional loop
squares = []
for x in range(10):
    squares.append(x**2)

# List comprehension
squares = [x**2 for x in range(10)]
print(squares)  # Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Explain the difference between __str__() and __repr__() methods in Python.

In Python, __str__() and __repr__() are special methods used to define string representations of objects. __str__() is meant to return a readable and informal string representation, useful for end-users. In contrast, __repr__() is intended to generate an unambiguous representation of the object, ideally one that could be used to recreate the object. If __str__() is not defined, Python falls back to using __repr__().

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

    def __repr__(self):
        return f"Person('{self.name}', {self.age})"

    def __str__(self):
        return f"{self.name}, {self.age} years old"

p = Person("Alice", 30)
print(repr(p))  # Output: Person('Alice', 30)
print(str(p))   # Output: Alice, 30 years old

What are generators in Python and how do they work?

Generators in Python are a type of iterable that allow you to iterate over data without storing the entire sequence in memory. They are created using functions with the yield statement or generator expressions. When a generator’s __next__() method is called, the generator resumes execution until it hits a yield statement, returning the yielded value and maintaining its state for the next iteration.

def countdown(n):
    while n > 0:
        yield n
        n -= 1

# Using the generator
for number in countdown(5):
    print(number)
# Output:
# 5
# 4
# 3
# 2
# 1

How do you handle file operations in Python?

File operations in Python are handled using built-in functions like open(), along with methods such as read(), write(), and close(). It’s recommended to use the with statement to manage file contexts, which ensures that files are properly closed after their suite finishes, even if an error occurs.

# Reading a file
with open('example.txt', 'r') as file:
    content = file.read()
    print(content)

# Writing to a file
with open('example.txt', 'w') as file:
    file.write('Hello, World!')

What is the purpose of the self keyword in Python classes?

The self keyword in Python refers to the instance of the class. It is used to access variables and methods associated with the current object. By convention, self is the first parameter of instance methods, allowing you to differentiate between instance attributes and local variables.

class MyClass:
    def __init__(self, value):
        self.value = value  # 'self.value' refers to the instance variable

    def display(self):
        print(self.value)

obj = MyClass(10)
obj.display()  # Output: 10

Explain the use of *args and **kwargs in Python functions.

*args and **kwargs are used in Python functions to allow variable numbers of arguments. *args is used to pass a non-keyworded, variable-length argument list, while **kwargs allows passing keyworded, variable-length arguments. They provide flexibility in function definitions, enabling functions to accept an arbitrary number of inputs.

def func(*args, **kwargs):
    print("Arguments:", args)
    print("Keyword Arguments:", kwargs)

func(1, 2, 3, name='Alice', age=30)
# Output:
# Arguments: (1, 2, 3)
# Keyword Arguments: {'name': 'Alice', 'age': 30}

What is the purpose of the __init__() method in Python classes?

The __init__() method in Python classes is known as the initializer or constructor. It is automatically called when a new instance of the class is created. Its primary purpose is to initialize the instance’s attributes with the values provided as arguments during object creation.

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

p = Person("Alice", 30)
print(p.name)  # Output: Alice
print(p.age)   # Output: 30

Explain how inheritance works in Python.

Inheritance in Python allows a class (child or subclass) to inherit attributes and methods from another class (parent or superclass). This promotes code reusability and establishes a hierarchical relationship between classes. A subclass can override or extend the functionality of the superclass, enabling polymorphism.

class Animal:
    def speak(self):
        print("Animal speaks")

class Dog(Animal):
    def speak(self):
        print("Dog barks")

dog = Dog()
dog.speak()  # Output: Dog barks

What are context managers in Python and how do you implement them?

Context managers in Python are used to manage resources, ensuring that setup and teardown actions are properly handled. They are typically implemented using the with statement, which ensures that resources are released after use, even if an error occurs. You can create context managers using the __enter__() and __exit__() methods or by using the contextlib module.

from contextlib import contextmanager

@contextmanager
def open_file(name, mode):
    f = open(name, mode)
    try:
        yield f
    finally:
        f.close()

with open_file('test.txt', 'w') as f:
    f.write('Hello, World!')

How does Python’s garbage collection work?

Python’s garbage collection is responsible for reclaiming memory occupied by objects that are no longer in use. It primarily uses reference counting to keep track of the number of references to each object. When an object’s reference count drops to zero, it is immediately deallocated. Additionally, Python has a cyclic garbage collector that detects and cleans up groups of objects that reference each other, preventing memory leaks caused by circular references.

What is the difference between == and is operators in Python?

In Python, the == operator checks if the values of two objects are equal, meaning they have the same content. On the other hand, the is operator checks if two references point to the same object in memory. This distinction is important when comparing mutable objects or understanding object identity.

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)  # Output: True
print(a is b)  # Output: False
print(a is c)  # Output: True

Explain how to use the property decorator in Python.

The property decorator in Python is used to create managed attributes in classes. It allows you to define methods that can be accessed like attributes, enabling encapsulation and validation. By using @property, you can define a getter method, and with @attribute.setter, you can define a setter method for the attribute.

class Celsius:
    def __init__(self, temperature=0):
        self._temperature = temperature

    @property
    def temperature(self):
        """Get the temperature"""
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        """Set the temperature with validation"""
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        self._temperature = value

c = Celsius()
c.temperature = 25
print(c.temperature)  # Output: 25
# c.temperature = -300  # Raises ValueError

What is the purpose of the lambda function?

Lambda functions in Python are small anonymous functions defined using the lambda keyword. They can take any number of arguments but contain only a single expression. Lambda functions are often used for short, throwaway functions in higher-order functions like map(), filter(), and sorted().

# Lambda function example
add = lambda x, y: x + y
print(add(2, 3))  # Output: 5

# Using lambda with map
squares = list(map(lambda x: x**2, range(5)))
print(squares)  # Output: [0, 1, 4, 9, 16]

How do you perform unit testing in Python?

Unit testing in Python can be performed using the built-in unittest framework or third-party libraries like pytest. Unit tests are written to verify that individual units of code, such as functions and methods, work as intended. These tests help in identifying bugs early and ensure that code changes do not break existing functionality.

import unittest

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

class TestAddFunction(unittest.TestCase):
    def test_add_positive(self):
        self.assertEqual(add(2, 3), 5)

    def test_add_negative(self):
        self.assertEqual(add(-1, -1), -2)

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

What are type hints and how are they used in Python?

Type hints in Python are a feature that allows developers to indicate the expected data types of variables, function parameters, and return values. Introduced in PEP 484, type hints improve code readability, facilitate static type checking using tools like mypy, and enhance editor support with better autocomplete and error detection.

def greet(name: str) -> str:
    return f"Hello, {name}!"

def add(a: int, b: int) -> int:
    return a + b

# Using type hints with variables
age: int = 30
name: str = "Alice"

How do you perform string formatting in Python?

String formatting in Python can be done using several methods:

  • Old-style formatting: Using the % operator.
  • str.format(): Using the format() method.
  • f-strings: Introduced in Python 3.6, using the f'' syntax.

Each method allows embedding expressions inside string literals for dynamic content generation.

# Old-style formatting
name = "Alice"
age = 30
print("Hello, %s. You are %d years old." % (name, age))

# str.format()
print("Hello, {}. You are {} years old.".format(name, age))

# f-strings
print(f"Hello, {name}. You are {age} years old.")

What is the difference between append() and extend() methods in Python lists?

The append() method adds its argument as a single element to the end of a list, increasing the list length by one. In contrast, the extend() method iterates over its argument adding each element to the list, effectively concatenating another iterable to the end of the list. Use append() to add a single element and extend() to combine lists.

my_list = [1, 2, 3]
my_list.append([4, 5])
print(my_list)  # Output: [1, 2, 3, [4, 5]]

my_list = [1, 2, 3]
my_list.extend([4, 5])
print(my_list)  # Output: [1, 2, 3, 4, 5]

How does Python's with statement work?

The with statement in Python is used to wrap the execution of a block with methods defined by a context manager. This ensures that resources are properly managed, such as closing files or releasing locks, even if exceptions occur. It simplifies exception handling by encapsulating common preparation and cleanup tasks.

with open('example.txt', 'r') as file:
    content = file.read()
    print(content)
# The file is automatically closed after the block

What is the purpose of the pass statement in Python?

The pass statement in Python is a null operation; nothing happens when it executes. It is used as a placeholder in situations where syntactically some code is required but no action needs to be performed. Common use cases include empty function or class definitions and minimal exception handling.

def my_function():
    pass  # TODO: implement this function later

class MyClass:
    pass  # Placeholder for future attributes and methods

Explain the concept of monkey patching in Python.

Monkey patching in Python refers to the dynamic modification of a class or module at runtime. This allows developers to alter or extend the behavior of existing code without modifying the original source. While powerful, monkey patching should be used cautiously as it can lead to code that is difficult to understand and maintain.

import some_module

def new_function():
    print("This is a monkey patched function.")

some_module.original_function = new_function
some_module.original_function()  # Output: This is a monkey patched function.

What is the use of the nonlocal keyword?

The nonlocal keyword in Python is used inside nested functions to refer to variables defined in the nearest enclosing scope that is not global. It allows you to modify variables in the outer (but non-global) scope, enabling state retention across multiple function calls without using global variables.

def outer():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print(count)
    return inner

counter = outer()
counter()  # Output: 1
counter()  # Output: 2

How can you optimize Python code for performance?

Optimizing Python code for performance can be achieved through various methods:

  • Using built-in functions and libraries, which are implemented in C and are faster.
  • Minimizing the use of global variables.
  • Employing list comprehensions and generator expressions for efficient looping.
  • Profiling code to identify bottlenecks using tools like cProfile.
  • Using just-in-time compilers like PyPy for speed improvements.
  • Reducing the overhead of attribute access by using __slots__ in classes.
Implementing these strategies can lead to significant performance gains.

What are docstrings and how are they used in Python?

Docstrings are string literals that appear right after the definition of a function, method, class, or module. They are used to document the purpose, usage, and behavior of the code. Docstrings can be accessed using the __doc__ attribute and are utilized by documentation tools and IDEs to provide helpful information to developers.

def greet(name):
    """
    Greets the person with the given name.

    Parameters:
    name (str): The name of the person.

    Returns:
    None
    """
    print(f"Hello, {name}!")

print(greet.__doc__)

Explain the use of the property decorator in Python.

The property decorator in Python is used to create managed attributes in classes. It allows you to define methods that can be accessed like attributes, enabling encapsulation and validation. By using @property, you can define a getter method, and with @attribute.setter, you can define a setter method for the attribute.

class Celsius:
    def __init__(self, temperature=0):
        self._temperature = temperature
    @property
    def temperature(self):
        """Get the temperature"""
        return self._temperature
    @temperature.setter
    def temperature(self, value):
        """Set the temperature with validation"""
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        self._temperature = value
c = Celsius()
c.temperature = 25
print(c.temperature)  # Output: 25
# c.temperature = -300  # Raises ValueError

What is the yield from statement?

The yield from statement in Python is used within a generator to delegate part of its operations to another generator. It allows a generator to yield all values from another iterable or generator, simplifying the code and improving readability when composing generators.

def generator1():
    yield from range(3)

def generator2():
    yield from generator1()
    yield from ['a', 'b']

for value in generator2():
    print(value)
# Output:
# 0
# 1
# 2
# a
# b

Describe how Python's set data type works.

In Python, a set is an unordered collection of unique elements. Sets are mutable, allowing for the addition and removal of items, but the elements themselves must be immutable. Sets are optimized for membership testing and eliminating duplicate entries. Common operations include union, intersection, difference, and symmetric difference.

my_set = {1, 2, 3, 3}
print(my_set)  # Output: {1, 2, 3}

my_set.add(4)
print(my_set)  # Output: {1, 2, 3, 4}

another_set = {3, 4, 5}
print(my_set & another_set)  # Output: {3, 4}
print(my_set | another_set)  # Output: {1, 2, 3, 4, 5}

What is the difference between deepcopy and copy?

In Python, the copy module provides two methods: copy() and deepcopy(). The copy() method creates a shallow copy of an object, which means it copies the object structure but not the nested objects; the nested objects are still referenced. In contrast, deepcopy() creates a deep copy, recursively copying all nested objects, resulting in a completely independent clone of the original object.

import copy

original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[0][0] = 'changed'

print(shallow)  # Output: [['changed', 2], [3, 4]]
print(deep)     # Output: [[1, 2], [3, 4]]

How do you handle multiple exceptions in a single except block?

In Python, you can handle multiple exceptions in a single except block by specifying a tuple of exception types. This allows you to catch different exceptions with the same handling code, reducing redundancy and improving readability.

try:
    result = 10 / 0
except (ZeroDivisionError, TypeError) as e:
    print(f"An error occurred: {e}")

What is a namespace in Python?

A namespace in Python is a container that holds a set of identifiers (names) and ensures that all the names within it are unique. Namespaces are implemented as dictionaries and are used to prevent naming conflicts by encapsulating variable and function names within different scopes, such as global, local, and built-in namespaces.

# Example of namespaces
def foo():
    x = 10  # Local namespace
    print(x)

x = 20  # Global namespace
foo()
print(x)

Explain the use of the assert statement.

The assert statement is used for debugging purposes to test if a condition is true. If the condition evaluates to false, an AssertionError is raised, optionally with a specified error message. Assertions help in identifying bugs by ensuring that certain conditions hold true during execution.

def divide(a, b):
    assert b != 0, "Denominator cannot be zero."
    return a / b

print(divide(10, 2))  # Output: 5.0
# print(divide(10, 0))  # Raises AssertionError: Denominator cannot be zero.

What is the difference between staticmethod and classmethod?

In Python, both staticmethod and classmethod are decorators used to define methods inside a class that are not tied to a specific instance. staticmethod defines a method that does not receive an implicit first argument, neither self nor cls, and behaves like a regular function within the class namespace. classmethod, on the other hand, receives the class itself as the first argument, conventionally named cls, allowing the method to access and modify class state.

class MyClass:
    @staticmethod
    def static_method():
        print("This is a static method.")
    @classmethod
    def class_method(cls):
        print(f"This is a class method of {cls}.")
MyClass.static_method()  # Output: This is a static method.
MyClass.class_method()   # Output: This is a class method of <class '__main__.MyClass'>.