Essential 100+ Python Interview Questions

In this guide, we’ll explore commonly asked Python interview questions, along with explanations to help you ace your next interview. Let’s dive into Python’s rich ecosystem and its real-world applications!

Table of Contents

python interview question

Python was created by whom and when?

Python was created by Guido van Rossum and released in 1991.

What is the latest Python version?

The latest version of Python is 3.13.0, which was released on October 7, 2024. Always check the latest version at the time of your python interviews.

How do we do the casting of the variables?

x = str(3)    # x will be '3'
y = int(3)    # y will be 3
z = float(3)  # z will be 3.0
Python

Are variables name case sensitive?

Variable names are case-sensitive.

a = 4
A = "Sally"

print(a) #4
print(A) #Sally
Python

Do we need to declare variables?

  • Variables do not need to be declared of any particular type.
  • A variable is created the moment you first assign a value to it, and you can even change the type after it has been set.

Assign values to multiple variables at once

x, y, z = "Orange", "Banana", "Cherry"

print(x) #Orange
print(y) #Banana
print(z) #Cherry
Python

Assign the same value to multiple variables at once

x = y = z = "Orange"


print(id(x)) #4359546952
print(id(y)) #4359546952
print(id(z)) #4359546952

z = "apple"

print(id(x)) #4359546952
print(id(y)) #4359546952
print(id(z)) #4359546248
Python

  • 1st Assignment: three variables x, y, and z refer to the same memory location where "Orange" is stored.
  • 2nd Assignment: When you assign “apple” to z, Python creates a new string object “apple” in memory and assigns z to it

What’s happening

  • Python reuses memory for immutable objects (like strings and small integers).
  • When you assign the same immutable value to multiple variables, they share the same memory address.
  • But if one variable is reassigned, it will get a new memory address while others remain unchanged.

What is String Interning in Python?

String interning is a performance optimization technique where Python stores only one copy of a string and reuses it, instead of creating multiple identical string objects in memory.

a = "hello"
b = "hello"

print(id(a))  # Example: 4359546952
print(id(b))  # Example: 4359546952  (Same memory address as `a`)
print(a is b) # True (Both refer to the same object)
Python

When Does Python Intern Strings?

Python automatically interns:

  • Short strings (usually length ≤ 20 characters).
  • Strings containing only letters, digits, or underscores (e.g., “Python123”, “var_name”).
  • Strings that are assigned at compile-time (i.e., directly written in the code).

Does Python Intern Integers?

Yes! Python also interns integers, but only within a specific range to optimize memory usage.

What is the difference between Python Arrays and lists?

  • Arrays in python can only contain elements of same data types i.e., data type of array should be homogeneous.
  • It is a thin wrapper around C language arrays and consumes far less memory than lists.
  • Lists in python can contain elements of different data types i.e., data type of lists can be heterogeneous. It has the disadvantage of consuming large memory.

When defining an array, you must specify a type code, which determines the type of elements it can store:

  • ‘b’ – signed char (1 byte)
  • ‘B’ – unsigned char (1 byte)
  • ‘h’ – signed short (2 bytes)
  • ‘H’ – unsigned short (2 bytes)
  • ‘i’ – signed int (4 bytes)
  • ‘I’ – unsigned int (4 bytes)
  • ‘f’ – float (4 bytes)
  • ‘d’ – double (8 bytes)

import array

# Create an array of integers
arr = array.array('i', [1, 2, 3, 4, 5])

# Append an element
arr.append(6)

# Remove an element
arr.remove(3)

# Access elements
print(arr[0])  # Output: 1

# Iterate through the array
for num in arr:
    print(num)
Python

Print statement

Basic Usage:

print("Hello,")
print("Backendmesh")

'''
Output
Hello,
Backendmesh
'''
Python

In above example, print statement add newline(\n)

Custom end character

By default, print() adds a newline (\n) at the end. You can override it with end

print("Hello", end=" ")
print("World")

'''
Output
Hello World

'''


print("1", end=" ")
print("2")
print("3",end="->")
print("4")
print("5",end="--")
print("6")

'''
1 2
3->4
5--6

'''
Python

String formatting inside print()

print(f"My name is {name} and I am {age} years old.")
Python

Multiple values (separated by commas):

name = "Alice"
age = 30
print("Name:", name, "Age:", age)

'''
Name: Alice Age: 30
'''
Python

In above example, values are separated by space

Custom separator

By default, print() separates items with a space. You can change this using the sep parameter:

print("2025", "04", "29", sep="-")

'''
Output
2025-04-29
''''
Python

Common String inbuild functions

Case Conversion

s = "hello wOrld"

print(s.lower())       # 'hello world'
print(s.upper())       # 'HELLO WORLD'
print(s.capitalize())  # 'Hello world'
print(s.title())       # 'Hello World'
print(s.swapcase())    # 'HELLO WoRLD'
print(s.casefold())    # 'hello world' (more aggressive than lower) 
Python

casefold() : Designed for aggressive and language-independent case conversion.

Search & Check

s = "hello world"

print(s.find("world"))       # 6
print(s.rfind("l"))          # 9   Returns the highest index of substring or -1
print(s.index("world"))      # 6   Like find() but raises ValueError if not found
print(s.startswith("he"))    # True
print(s.endswith("ld"))      # True
print(s.count("l"))          # 3
Python

Trimming

s = "  hello  "

print(s.strip())        # 'hello'
print(s.lstrip())       # 'hello  ' Removes leading whitespace.
print(s.rstrip())       # '  hello' Removes trailing whitespace.
Python

Replace and Split

s = "apple,banana,grape"

print(s.replace("banana", "kiwi"))   # 'apple,kiwi,grape'
print(s.split(","))                  # ['apple', 'banana', 'grape']
Python

Joins elements with the string as separator

print("->".join(["a", "b", "c"])) # a->b->c
Python

Explain the concept of unpacking a collection

If you have a collection of values in a list, tuple, etc. Python allows you to extract the values into variables. This is called unpacking.

fruits = ["apple", "banana", "cherry"]
x, y, z = fruits

print(x) #Orange
print(y) #Banana
print(z) #Cherry
Python

Read

Sorting

sorted() function

Returns a new sorted list from any iterable.

nums = [4, 2, 9, 1]
sorted_nums = sorted(nums)           # [1, 2, 4, 9]
sorted_nums_desc = sorted(nums, reverse=True)  # [9, 4, 2, 1]
Python

.sort() method

Sorts a list in place and returns None.

nums = [4, 2, 9, 1]
nums.sort()               # modifies nums to [1, 2, 4, 9]
nums.sort(reverse=True)   # modifies nums to [9, 4, 2, 1]
Python

Custom Sorting

It’s important for interview.

Sorting with a custom key in Python allows you to define how the elements should be ordered by providing a function to the key parameter. This is especially useful when sorting complex data like dictionaries, tuples, or objects.

sorted(iterable, key=key, reverse=True/False)
Python

  • iterable Required. The sequence to sort, list, dictionary, tuple etc.
  • key Optional. A Function to execute to decide the order. Default is None
  • reverse Optional. A Boolean. False will sort ascending, True will sort descending. Default is False

Sorting by string length:

words = ['banana', 'pie', 'Washington', 'book']
sorted_words = sorted(words, key=len)
print(sorted_words)  # ['pie', 'book', 'banana', 'Washington']
Python

Sorting a List of Tuples

students = [('Bob', 20),('Alice', 25), ('Charlie', 23)]
sorted_by_name = sorted(students, key=lambda x: x[0])
sorted_by_age = sorted(students, key=lambda x: x[1])


print(sorted_by_name) # [('Alice', 25), ('Bob', 20), ('Charlie', 23)]
print(sorted_by_age) # [('Bob', 20), ('Charlie', 23), ('Alice', 25)]
Python

Working

  • Python calls lambda x: x[1] on each tuple.
  • So it gets: 25, 20, 23
  • It sorts those numbers: 20, 23, 25
  • Then arranges the tuples accordingly.

Sorting a List of Dictionaries

products = [
    {'name': 'Laptop', 'price': 1000},
    {'name': 'Phone', 'price': 500},
    {'name': 'Tablet', 'price': 700}
]


sorted_products = sorted(products, key=lambda x: x['price'])

# [{'name': 'Phone', 'price': 500}, {'name': 'Tablet', 'price': 700}, {'name': 'Laptop', 'price': 1000}]

Python

Working

  • lambda x: x[‘price’] extracts the price from each dictionary.
  • So internally, Python compares 500, 1000, 700, and sorts accordingly

How lamda works for sorting?

A lambda helps in sorting by telling Python what to sort by — it acts as a custom rule for comparing elements. In place of lambda we can use our custom function as well

Sorting by custom function

students = [('Alice', 25), ('Bob', 20), ('Charlie', 23)]

def get_age(student):
    return student[1]

sorted_students = sorted(students, key=get_age)
Python

products = [{'name': 'Phone', 'price': 500}, {'name': 'Laptop', 'price': 1000}]

def get_price(product):
    return product['price']

sorted_products = sorted(products, key=get_price)
Python

Concatenate of string and number?

  • For numbers, the + operator works as a mathematical addition operator
  • For string, the + operator works concatenation operator
  • The mix of string and numbers will give an error
x = "Python "
y = "is "
z = "awesome"
print(x + y + z) #Python is awesome


x = 5
y = 10
print(x + y)  #15


x = 5
y = "John"
print(x + y) #error 
Python

What are the different variable scopes?

In Python, we can declare variables in three different scopes: local scope, global, and nonlocal scope.

Local Variables

When we declare variables inside a function, they are only accessible within the function and have a local scope.

def greet():

    # local variable
    message = 'Hello'
    
    print('Local', message) # Hello

greet()


print(message) # this will give error
Python

Global Variables

In Python, a variable declared outside of the function or in a global scope is known as a global variable. This means that a global variable can be accessed inside or outside of the function.

# declare global variable
message = 'Hello'

def greet():
    # declare local variable
    print('Global ', message) #Global Hello

greet()
print('Global ', message) #Global Hello
Python

message = 'Hello'

def greet():
    message = "hi"
    # declare local variable
    print('Local ', message) #Local Hi

greet()
print('Global ', message) #Global Hello
Python

Nonlocal Variables

In Python, the nonlocal keyword is used within nested functions to indicate that a variable is not local to the inner function, but rather belongs to an enclosing function’s scope.

It allows you to modify that variable in the outer function from within the inner function.

With nonlocal

# outside function 
def outer():
    message = 'backend'

    # nested function  
    def inner():
        # declare nonlocal variable
        nonlocal message

        message = 'mesh'
        print("inner:", message) #inner: mesh

    inner()
    print("outer:", message) #outer: mesh

outer()
Python

Without nonlocal

# outside function 
def outer():
    message = 'backend'

    # nested function  
    def inner():
        message = 'mesh'
        print("inner:", message) #inner: mesh

    inner()
    print("outer:", message) #outer: backend

outer()
Python

  • nonlocal keyword in Python is only applicable to enclosed(nested) functions
  • It allows modifying variables from an enclosing (but not global) scope

Tricky Example


message = "King"
# outside function
def outer():
    message = 'backend'

    # nested function
    def inner():
        # declare nonlocal variable
        nonlocal message

        message = 'mesh'
        print("inner:", message) #inner: mesh

    inner()
    print("outer:", message) #outer: mesh

outer()
print(message) #king

#OutPut
inner: mesh
outer: mesh
King
Python

What is the global keyword used for?

Variables that are created outside of a function are known as global variables.

x = "awesome"

def myfunc():
  print("Python is " + x)  #Python is awesome

myfunc()
Python

If you create a variable with the same name inside a function, this variable will be local, and can only be used inside the function. The global variable with the same name will remain as it was, global and with the original value.

x = "awesome"

def myfunc():
  x = "fantastic"
  print("Python is " + x)

myfunc()

print("Python is " + x)


#Python is fantastic
#Python is awesome
Python

The global Keyword

Variable x becomes global because of the global keyboard

def myfunc():
  global x
  x = "fantastic"

myfunc()

print("Python is " + x)

#Python is fantastic
Python

Also, use the global keyword if you want to change a global variable inside a function.

x = "awesome"

def myfunc():
  global x
  x = "fantastic"

myfunc()

print("Python is " + x)

#Python is fantastic
Python

Read about Nonlocal

What is id?

In Python, id is a built-in function used to retrieve the memory address of an object. Each object in Python has a unique identifier during its lifetime, and the id function provides this identifier. It is typically an integer that is unique to the object.

id(object)

x = 42
print(id(x))  # Unique identifier of the integer 42


a=b=c=4
print(id(a)) #9756320
print(id(b)) #9756320
print(id(c)) #9756320
a=2
b=3
print(id(a)) #9756256
print(id(b)) #9756288
print(id(c)) #9756320
Python

Common mistake

#Works
print(id(56)) # 9757984


#Will Not work
id = 42  # Shadows the built-in 'id'
print(id(56))  # TypeError: 'int' object is not callable
Python

In this case, Python cannot call the built-in id function because the local variable id has overshadowed it.

What are isinstance?

isinstance is a built-in Python function used to check whether an object belongs to a specific class or a tuple of classes. It is a reliable way to ensure that an object is of the desired type before performing certain operations on it.

isinstance(object, classinfo)
Python

Parameters

  • object: The object to check.
  • classinfo: A class, type, or a tuple of classes and types to check against.

Return Value

  • True if the object is an instance of the specified class or one of the classes in the tuple.
  • False otherwise.
x = 10
print(isinstance(x, int))  # True (x is an instance of int)
print(isinstance(x, str))  # False (x is not a string)
Python

What is Type?

Returns the type of an object.

print(type(10)) #<class 'int'>
print(type('10')) #<class 'str'>
Python

What is issubclass?

Checks if a class is a subclass of another class.

class Animal: pass
class Dog(Animal): pass

print(issubclass(Dog, Animal))  # True
print(issubclass(Animal, Dog))  # False
Python

What are hasattr, getattr, setattr and delattr?

  • hasattr: Checks if an object has a specific attribute.
  • getattr: Retrieves the value of an attribute from an object. Allows a default value if the attribute does not exist.
  • setattr: Sets an attribute on an object.
  • delattr: Deletes an attribute from an object.
class MyClass:
    def __init__(self):
        self.value = 42

obj = MyClass()
print(hasattr(obj, 'value'))  # True
print(hasattr(obj, 'name'))  # False

print(getattr(obj, 'value'))        # 42
print(getattr(obj, 'name', 0))  # 0 (default value)

setattr(obj, 'name','backendmesh')
print(obj.value)  # backendmesh

delattr(obj, 'value')
# print(obj.value)  # AttributeError: 'MyClass' object has no attribute 'value'
Python

What is __dict__?

  • In Python, dict is a special attribute of objects that stores an object’s attributes (instance variables) in a dictionary format. It is commonly used to inspect an object’s properties dynamically.
  • It’s a dunder(magic method)
class Movie:
    def __init__(self, title, year):
        self.title = title
        self.year = year

movie = Movie("Inception", 2010)
print(movie.__dict__) # {'title': 'Inception', 'year': 2010}
Python

Work on both instance and class Read further

What is vars?

Returns the __dict__ attribute of an object, showing its namespace.

class Movie:
    def __init__(self, title, year):
        self.title = title
        self.year = year

movie = Movie("Inception", 2010)
print(vars(movie))  # Same as movie.__dict__

print(vars(Movie))  # TypeError: vars() argument must have __dict__ attribute
Python

  • Works on instance
  • Unlike dict, vars() does not work directly on a class

With Modules

vars() is useful for inspecting modules dynamically:

import math
print(vars(math))  # Prints all attributes of the math module
Python

With No Arguments

If called without arguments, vars() returns the local symbol table (same as locals()):

x = 10
y = "hello"
print(vars())  # Shows all local variables
Python

Are they vars and __dict__ same?

vars() and __dict__ are similar but not exactly the same. Here’s how they compare:

  • vars() is a function, while __dict__ is an attribute
  • for instance: vars(obj) is a function that returns obj.__dict__ if the object has one.
  • vars() Works on Modules, But __dict__ Doesn’t Always
  • vars() Without Arguments Returns locals()
  • __dict__ is a dunder, while vars is build in function

Takeaway:

  • If you just want an object’s attributes, vars(obj) and obj.__dict__ are the same.

What is dir() frunction

The dir() function in Python is used to return a list of the attributes and methods available in a given object. If called without arguments, it returns the list of names in the current local scope.

Without Arguments:

print(dir())

#['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
Python

This will list the names currently defined in the local scope.

With an Object:

print(dir(str))

#['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
Python

This will list all the attributes and methods of the str class.

Custom class

# Define a custom class
class MyClass:
    class_var = "Hello, World!"
    
    def __init__(self, value):
        self.instance_var = value
    
    def say_hello(self):
        return "Hello from MyClass!"
    
    def display_value(self):
        return f"The value is: {self.instance_var}"

# Create an instance of the class
my_object = MyClass(42)

# Use dir() to inspect the class
print("Attributes and methods in MyClass:")
print(dir(MyClass))

# Use dir() to inspect the object instance
print("\nAttributes and methods in my_object:")
print(dir(my_object))



#output 
Attributes and methods in MyClass:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'class_var', 'display_value', 'say_hello']

Attributes and methods in my_object:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'class_var', 'display_value', 'instance_var', 'say_hello']
Python

Output:
This will display all the attributes and methods available for MyClass and the my_object instance. It will include:

  • Your defined methods (say_hello, display_value)
  • Built-in methods and attributes such as __init__, __class__, and __repr__
  • Variables such as class_var and instance_var (accessible depending on the context).

Vars() vs dir()

dir() Shows More Than Just Instance Attributes

dir(obj) returns all attributes and methods, including:

  • Instance attributes (__dict__)
  • Methods (both user-defined and built-in)
  • Inherited attributes from parent classes
  • Special methods like __init__, __str__, etc.

dir() Works on Built-in Types (Unlike vars())

  • vars() only works on objects with a __dict__ attribute (e.g., instances of user-defined classes).
  • dir() works on all objects, including built-in types.

dir() Can Be Used Without Arguments

  • dir() without arguments lists all variables in the current scope.
  • vars() without arguments behaves like locals(), returning a dictionary of local variables.

locals()

locals() is a built-in function that returns a dictionary of local variables in the current scope.

def my_function():
    a = 10
    b = 20
    print(locals())  # {'a': 10, 'b': 20}

my_function()
Python

globals()

locals() is a built-in function that returns a dictionary of local variables in the global scope.

x = 100

def test():
    y = 200
    print("locals:", locals())  # {'y': 200}
    print("globals:", globals())  # Shows all global variables, including x

test()
Python

What are built-in data types?

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

Note

  • Int, or integer, is a whole number, positive or negative, without decimals, of unlimited length.
  • A frozenset in Python is an immutable version of a regular set

Example:

x = 1j
print(type(x)) #<class 'complex'>

x = range(6)
print(x)  #range(0, 6)
print(type(x)) #<class 'range'>
Python

Range

The range() function defaults to increment the sequence by 1, however, it is possible to specify the increment value by adding a third parameter: range(2, 30, 3):

for x in range(2, 30, 3):
  print(x) 
Python

frozenset

A frozenset in Python is an immutable version of a regular set. Once created, its contents cannot be modified, which makes it hashable and usable as a key in a dictionary or as an element of another set.

x = frozenset({"apple", "banana", "cherry"})
print(x) #frozenset({'cherry', 'banana', 'apple'})
print(type(x)) #<class 'frozenset'>
Python

Multiline Strings

a = """Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua."""
print(a)
Python

Strings are Arrays

Like many other popular programming languages, strings in Python are arrays of bytes representing Unicode characters. However, Python does not have a character data type, a single character is simply a string with a length of 1.

Square brackets can be used to access elements of the string.

a = "Hello, World!"
print(a[1]) #e
Python

Looping Through a String

for x in "banana":
  print(x)
Python

Finding a substring in a string

txt = "The best things in life are free!"
print("free" in txt) #True
Python

Slicing Strings

b = "Hello, World!"
print(b[2:5]) #llo
print(b[:5]) #Hello
print(b[2:]) #llo, World!
print(b[-5:-2]) #orl
Python

Read more

F-Strings

F-String was introduced in Python 3.6 and is now the preferred way of formatting strings. To specify a string as an f-string, simply put an f in front of the string literal, and add curly brackets {} as placeholders for variables and other operations.

age = 36
txt = f"My name is John, I am {age}"
print(txt) #My name is John, I am 36
Python

Boolean Values

In programming, you often need to know if an expression is True or False. You can evaluate any expression in Python, and get one of two answers, True or False.

print(10 > 9) #True
print(10 == 9) #False
print(10 < 9) #False
Python

bool()

The bool() function allows you to evaluate any value, and give you True or False in return,

print(bool("Hello")) #True
print(bool(15)) #True
Python

Most Values are True

  • Almost any value is evaluated as True if it has some sort of content.
  • Any string is True, except empty strings.
  • Any number is True, except 0.
  • Any list, tuple, set, and dictionary are True, except empty ones.

Some Values are False

Values that are evaluated as False,

  • empty values, such as (), [], {}, “”,
  • the number 0
  • The None
  • And of course, the value False evaluates to False.

Give shorthand if …else example

a = 2
b = 330

print("A") if a > b else print("B") #B
Python

This technique is known as Ternary Operators or Conditional Expressions.

Give examples of Positional and Keyword Arguments

Positional Argument: The value is passed to the function based solely on its position in the argument list.

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

result = add("John ", "Doe")  # 'John' goes to 'a', and 'Doe' goes to 'b' (based on position)
print(result)  # Output: John Doe
Python

Keyword Argument: The value is passed using the parameter name explicitly.

result = add(b="Doe", a="John ")  # Passing by parameter name
print(result)  # Output: John Doe
Python

What are Positional-Only Arguments?

Positional-only arguments in Python are function parameters that can only be passed by position, not by keyword. This ensures that certain arguments are specified in a specific order without using their parameter names.

In Python, you define positional-only arguments by placing a / in the function definition. Any arguments defined before the / are positional only.

def function_name(arg1, arg2, /, arg3, arg4):
    # Function body
Python

Arguments before / are positional-only:

  • These arguments cannot be passed using their names.
  • They must be passed in the correct order.

Arguments after / can be passed by position or keyword:

  • You can pass these arguments using their names or by their order.

The / itself is not a parameter but a separator indicating which arguments are positional-only.

def calculate(a, b, /, c, d):
    
    print(a,b,c,d)

calculate(1,2,3,4)       # 1 2 3 4     #Positional for all parameters
calculate(1,2,d=4,c=3)   # 1 2 3 4    


# Invalid calls:
#calculate(a=1, b=2, c=3, d=4)  # TypeError: 'a' and 'b' are positional-only
#calculate(1, b=2, c=3, d=4)    # TypeError: 'b' is positional-only
Python

What are Keyword-only arguments?

Keyword-only arguments are function parameters that can only be passed by their names, not by their position. This ensures clarity and prevents mistakes when calling the function, especially for optional or default parameters.

In Python, you define keyword-only arguments by placing a * in the function definition. Any parameters defined after the * must be passed as keywords.

def function_name(arg1, arg2, *, arg3, arg4):
    pass
Python

arg1 and arg2:

  • These are positional or keyword arguments.
  • They can be passed either by position or by name.

arg3 and arg4

  • These are keyword-only arguments because they come after the *.
  • They must be passed by their names explicitly.
# valid
function_name(1, 2, arg3=3, arg4=4)  # All arguments provided correctly

function_name(arg1=1, arg2=2, arg3=3, arg4=4)  # Passing everything by keyword


# Invalid
function_name(1, 2, 3, 4)  
# TypeError: function_name() takes 2 positional arguments but 4 were given

function_name(1, 2, arg3=3)  
# TypeError: missing a required argument: 'arg4'

function_name(arg1=1, arg2=2, 3, 4)  
# SyntaxError: positional argument follows keyword argument
Python

Combine Positional-Only and Keyword-Only

You can combine the two argument types in the same function. Any argument before the /, is positional-only, and any argument after the *, is keyword-only.

def my_function(a, b, /, *, c, d):
  print(a + b + c + d)

my_function(5, 6, c = 7, d = 8) #26
Python

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

  • is: Checks for object identity. It verifies whether two references point to the same object in memory
  • ==: Checks for equality. It determines whether the values of two objects are the same, regardless of whether they are the same object in memory.
a = [1, 2, 3]
b = a
print(b is a) #True
print(b == a) #True 


# Make a new copy of list `a` via the slice operator, 
# and assign it to variable `b`
b = a[:] 
print(b is a) #False
print(b == a) #True 

# Case 1 
a = 1000
b = 1000
print(id(a)) #23066280615280
print(id(b)) #23066280615280
print(a is b) #True


print(1000 is 10**3) #True ,#Case 1
print(1000 == 10**3) #True 

print("a" is "a") #True
print("aa" is "a" * 2) #True ,#Case 1

list1 = [1, 2, 3]
list2 = [1, 2, 3]
print(list1 == list2)  # True: values are the same
print(list1 is list2)  # False: different objects in memory
Python

Case 1: It works because Python caches small integer objects, which is an implementation detail. For larger integers, this does not work. Read about Python’s interning mechanism

Note: This question is often asked in python interviews

a is None vs a == None

a is None

  • Checks identity, not equality.
  • Preferred way to check if a variable is None.

a == None

  • Checks equality using __eq__ method.
  • Can give unexpected results if a defines a custom __eq__ method.
  • Not recommended for None comparison.

Why a is None is preferred?

In Python, None is a singleton — there is only one instance of None in a program.

Why not a==None?

Because == can be overridden by a class’s __eq()__ method, which may define “equality” in a different way that says a==None as false even if a =None

What is intern mechanism?

In the String Intern mechanism in Python, when we create two strings with the same value – instead of allocating memory for both of them, only one string is committed to memory. The other one just points to that same memory location.

However, The string interning behaviour in Python can differ based on several factors, including the version of Python, the implementation of the interpreter, and the context in which the string is created. As a result, identical string values may not always be interned, and the behaviour can be difficult to predict in certain cases.

One reason why the string interning result can differ for the same string in Python is that Python interns only string literals and not string objects that are created at runtime. This means if a string is executed at compile time are same then the reference is the same. But if the execution is done at run time then the reference(id) is different. Refer

Forcing Distinct Objects – bypass interning

a = int(1000) + 0  # Ensures a new integer object is created
b = int(1000) + 0  # Ensures another new integer object is created

print(a == b)  # True: values are equal
print(a is b)  # False: guaranteed to be distinct objects
Python

Note

  • If you’re unsure whether Python will cache objects (like small integers or strings), you should avoid using is for value comparisons and instead use == to check for equality. This is the most reliable and portable approach.
  • Use ‘is’ only when you explicitly want to check object identity, i.e., whether two variables point to the same object in memory
  • Always use == to compare values unless you explicitly need to check for object identity.
  • When in doubt, avoid writing code that depends on Python’s interning or caching optimizations; such code may behave differently across environments or versions.

What is the purpose of Python’s __init__.py file in packages?

The __init__.py file plays an important role in Python packages. Here’s its purpose:

Indicates a Directory as a Package

  • In earlier versions of Python (before 3.3), the presence of an __init__.py file was required to indicate that a directory is a Python package. Without this file, Python would not treat the directory as a package, and you couldn’t import modules from it.
  • In modern Python versions (3.3 and later), __init__.py is optional, but its use is still common for package initialization or organizational purposes.

Package Initialization

  • When a package is imported (e.g., import mypackage), the code in the __init__.py file is executed. This makes it useful for initializing the package by:
    • Setting up package-level variables.
    • Importing specific modules or submodules.
    • Executing any setup code required when the package is imported.

Namespace Control

It allows control over what is exposed at the package level. You can define the __all__ list in __init__.py to specify which modules or objects are accessible when using from mypackage import *.

__all__ = ["module1", "module2"]
Python

Convenience Imports

  • You can use __init__.py to aggregate imports so that users can access submodules or functions directly from the package without needing to know its internal structure. For example:
# mypackage/__init__.py
from .module1 import func1
from .module2 import func2
Python

With this, users can do:

from mypackage import func1, func2
Python

Custom Behavior

  • It can contain any Python code that you want to execute upon importing the package, such as logging or dynamically modifying the package.

Why Empty __init__.py file

An empty __init__.py simply serves as a marker that tells Python to treat the directory as a package. In modern Python (version 3.3 and later), even this is optional, as Python can infer a directory is a package without the file. However, it’s still a common practice to include an empty init.py file for clarity and backward compatibility.

What is __init()__?

The __init()__ method is a special class method known as the initializer or constructor. It is automatically called when a class object is created. The purpose of this method is to initialize the class’s attributes or perform any required setup for the object.

class ClassName:
    def __init__(self, parameters):
        # Initialization code
Python

What is __del__()

__del__()__ is a special method used to perform cleanup actions when an object is about to be destroyed. This is called a destructor.

class ClassName:
    def __del__(self):
        # Cleanup code here
Python

What is dunder?

The double underscores (__) in Python, often referred to as “dunder” (short for “double underscore”), are used to indicate special methods or magic methods. These methods have specific roles and behaviours defined by Python, and they are not meant to be called directly by the user. Instead, they are invoked automatically in certain situations.

Read Detail

Why Double Underscores?

  • Namespace Avoidance: Double underscores help differentiate special methods from user-defined methods, reducing the risk of accidental name collisions.
  • For example, your custom method init won’t conflict with Python’s __init()__.
  • Readability: Using __ visually sets these methods apart, making code easier to understand.

List Special Methods

Here’s a list of some commonly used double underscore methods:

  • __init__: Constructor for initializing new objects.
  • __del__: Destructor for cleanup when an object is deleted.
  • __str__: String representation of an object (used by str() or print).
  • __repr__: Official string representation for debugging (used by repr()).
  • __len__: Returns the length of an object (used by len()).
  • __add__: Defines the behaviour of the + operator.
  • __getitem__: Enables indexing or slicing (obj[index]).
  • __setitem__: Handles item assignment (obj[index] = value).

Role of __init()__ in inheritance

Case 1: When parent has init() method, but child does’nt have

class Parent:
    def __init__(self):
        print("Parent initialized")
              
class Child(Parent):
    pass      
        
obj = Child() # Parent initialized
Python

Case 2: When both parent and child have the init() method.

If a child class defines its __init__() method, it overrides the parent’s __init__()

class Parent:
    def __init__(self):
        print("Parent initialized")
              
class Child(Parent):
    def __init__(self):
        print("Child initialized")      
        
obj = Child() # Child initialized
Python

Case 3: When both parent and child have the init() method, but we want to use the parent’s init() method

you need to explicitly call its __init__() method within the child class’s __init__() method using super

class Parent:
    def __init__(self):
        print("Parent initialized")
              
class Child(Parent):
    def __init__(self):
        super().__init__()
        print("Child initialized")      
        
obj = Child()

#Parent initialized
#Child initialized
Python

Guess the output

class A:
    def __init__(self):
        print("A initialized")

class B(A):
    def __init__(self):
        super().__init__()
        print("B initialized")

class C(A):
    def __init__(self):
        super().__init__()
        print("C initialized")

class D(B, C):
    def __init__(self):
        super().__init__()
        print("D initialized")


print(D.mro())  
obj = D()
Python

Output

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

A initialized
C initialized
B initialized
D initialized
Python

read about MRO

Self Parameter

The self parameter in Python is a reference to the instance of the class itself. It is used within a class to access the instance’s attributes and methods.

self.attribute_name
self.method_name()
Python

Read more

Key Points About Self

  • The first parameter of any instance method in a class must be self.
  • It acts as a handle for the calling object, allowing access to its properties and other methods.
  • Self is not a reserved keyword in Python; it’s just a naming convention. You could name it anything else, but using self is strongly recommended for readability and consistency.

Working

obj.method()
Python

is internally converted to:

Class.method(obj)
Python

Example 1

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

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

# Creating an instance of Person
person1 = Person("Alice", 30)

# Calling an instance method
print(person1.greet())  # Output: Hello, my name is Alice and I am 30 years old.
Python

Example 2: Use backendmesh as reference instead of self

class Animal:
  def __init(backendmesh,name)__:
    backendmesh.name = name
    
  def speak(backendmesh):
    print(backendmesh.name)

cat = Animal("cat")
cat.speak() # cat
Python

Super function

In Python, super() is a built-in function used to call methods from a parent (or superclass) class. It is typically used in object-oriented programming (OOP) to invoke methods that are inherited from a parent class, especially when overriding them in a subclass.

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

class Dog(Animal):
    def speak(self):
        super().speak()  # Calls speak method of Animal class
        print("Dog barks")

dog = Dog()
dog.speak()

#output
Animal speaks
Dog barks
Python

What is a pass statement in Python?

The pass statement is a placeholder that does nothing. It is used when a statement is syntactically required, but no action is necessary or the code is intentionally left blank.

Common Uses of Pass

Placeholder for Future Code: It can be used when writing a function, class, or loop where the implementation will be added later.

def my_function():
    pass  # Implementation will be added later
Python

Empty Class: To define a class without any methods or properties for now.

class MyClass:
    pass
Python

Empty Loops: To include a loop that does nothing (often temporarily during debugging).

for i in range(10):
    pass
Python

Conditionals Without Actions: When you want to skip specific conditions.

x = 5
if x > 10:
    pass  # Do nothing if x is greater than 10
else:
    print("x is less than or equal to 10")
Python

Pass in an Abstract Class

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def make_sound(self):
        pass  # No implementation here; subclasses must define this method

class Dog(Animal):
    def make_sound(self):
        print("Woof!")

class Cat(Animal):
    def make_sound(self):
        print("Meow!")

# Using the classes
dog = Dog()
dog.make_sound()  # Outputs: Woof!

cat = Cat()
cat.make_sound()  # Outputs: Meow!
Python

Iterator vs Iterable

FeatureIterableIterator
DefinitionCan be iterated over.Used to fetch items from an iterable.
MethodsImplements __iter__().Implements __iter__() and __next__().
ExamplesLists, tuples, sets, dictionaries.The object returned by iter() function.
StateStateless.Stateful (remembers its position).

Read More

What is decorator?

In Python, decorators are a powerful and flexible way to modify or extend the behavior of functions or methods, without changing their actual code. A decorator is essentially a function that takes another function as an argument and returns a new function with enhanced functionality.

Read More

What is PIP?

PIP is a package manager for Python packages.

  • Pip uses the Python Package Index (PyPI) as the default repository for fetching packages, but it can also install packages from other sources. 
  • Pip stands for “pip Install Packages” or Preferred Installer Program

Purpose of else in Try-Except

  • The try block lets you test a block of code for errors.
  • The except block lets you handle the error.
  • The else block lets you execute code when there is no error.
  • The finally block lets you execute code, regardless of the result of the try- and except blocks.

Key Differences Between else and finally

  • else: Runs only if the try block completes successfully without exceptions.
  • finally: Always runs, whether an exception occurs or not, and even if the try or except block contains a return, break, or continue.

Tricky question: Guess the Output

def test_function():
    try:
        return "Try block return"
    finally:
        print("Finally block executed.")

result = test_function()
print(result)
Python

Output

Finally block executed.
Try block return
Python

The finally block will execute before the return value is sent back to the caller.

Why should broad exception handling be avoided?

Broad exception handling (e.g., using except Exception: or except: without specifying the exception type) should generally be avoided because:

  • Hides Specific Errors – It catches all exceptions, making it difficult to determine what went wrong. This can lead to unexpected behavior and harder debugging.
  • Catches Unintended Exceptions – It may unintentionally catch exceptions that should be handled elsewhere, like KeyboardInterrupt or SystemExit, preventing proper program termination.
  • Makes Debugging Harder – Without knowing the specific exception type, debugging becomes more challenging since the root cause is obscured.
  • Can Lead to Silent Failures – If the exception is caught and ignored or logged insufficiently, the program may continue running in an incorrect state.
  • Prevents Granular Handling – Different exceptions require different handling. Broad handling prevents applying specific recovery strategies for each exception type.

Better Alternatives

  • Catch specific exceptions (e.g., except ValueError:).
  • Use multiple except blocks for different exception types.
  • Log the exception properly using logging.exception() before re-raising if necessary.
  • If using a broad except, at least log the full error message and traceback.

Bad example

try:
    num = int(input("Enter a number: "))
    result = 10 / num
except Exception:  # Bad practice
    print("An error occurred.")  # No useful error message
Python

Good Example

try:
    num = int(input("Enter a number: "))
    result = 10 / num
    print(f"Result: {result}")
except ValueError:
    print("Invalid input! Please enter a valid number.")  # Handles invalid number input
except ZeroDivisionError:
    print("Division by zero is not allowed.")  # Handles division by zero
except Exception as e:
    print(f"Unexpected error: {e}")  # Logs the unexpected error
Python

By catching specific exceptions, you ensure that the program reacts to errors in a predictable and controlled way, which makes your code easier to maintain and debug.

How would you ensure a Python script is compatible across Python 2 and Python 3?

Ensuring compatibility between Python 2 and Python 3 involves careful design and use of libraries that work across both versions. Here’s a comprehensive guide:

Compatibility Layer

A compatibility layer in the context of Python is a library or framework that helps developers write code that works seamlessly across both Python 2 and Python 3 without requiring separate code bases. These layers provide abstractions and helper functions to deal with differences in syntax, built-in functions, modules, and behaviour between the two Python versions.

Common Compatibility Layers

future Library

The future package allows you to write code in a Python 3 style that also works in Python 2.

from __future__ import print_function, division, absolute_import, unicode_literals
Python

six Library

The six library is lightweight and provides utilities for writing code compatible with Python 2 and Python 3.

from six.moves import queue
q = queue.Queue()
Python

Migration Approach

If possible, migrate code to Python 3 using tools like 2to3 or futurize. This way, you maintain Python 3 compatibility while supporting Python 2.

What is the purpose of ABC?

ABC stands for Abstract Base Class in Python. It provides a way to define abstract classes, which serve as a blueprint for other classes. Using the abc module, ABCs ensure that derived classes implement specific methods, making it a useful tool for enforcing a consistent interface.

Purpose of ABC:

  • Enforce Method Implementation: Abstract base classes allow you to define methods that must be implemented in any subclass. If a subclass fails to implement the abstract methods, Python raises a TypeError.
  • Encourage Code Reusability: By defining common interfaces and behaviour in an abstract class, derived classes can reuse those implementations, reducing redundancy.
  • Provide a Template: ABCs are used to outline the methods and properties a class should have, serving as a guideline for developers.
  • Enable Polymorphism: ABCs ensure that different objects adhering to the same interface can be treated interchangeably in the code.
  • Support for Type Checking: Using ABCs, you can check if an object is an instance of a certain abstract class or implements a specific interface.
from abc import ABC, abstractmethod

# Define an Abstract Base Class
class Animal(ABC):

    @abstractmethod
    def sound(self):
        pass

    @abstractmethod
    def habitat(self):
        pass

# Subclass implementing the abstract methods
class Dog(Animal):
    def sound(self):
        return "Bark"

    def habitat(self):
        return "Domestic"

class Fish(Animal):
    def sound(self):
        return "Blub"

    def habitat(self):
        return "Water"

# Instantiate and use the subclasses
dog = Dog()
fish = Fish()

print(dog.sound())   # Output: Bark
print(fish.habitat()) # Output: Water
Python

  • The Animal class is an abstract base class.
  • Dog and Fish are concrete implementations of Animal, ensuring they define sound and habitat.

What is a metaclass in Python?

A metaclass in Python is a “class of a class” that defines how a class behaves. Just like a class defines the behaviour and properties of objects, a metaclass defines the behaviour and properties of classes. Essentially, metaclasses allow you to control the creation, modification, and behaviour of classes themselves.

By default, Python uses the built-in metaclass type to create classes, but you can define your metaclass by inheriting from type.

When to Use Metaclasses

Metaclasses are typically used when you need to:

  • Enforce coding standards or ensure specific patterns in class definitions.
  • Automatically modify or add methods/attributes to classes when they’re defined.
  • Implement frameworks or libraries where certain rules or behaviours need to be standardized across many classes.

Read more

How to get script execution time?

Easiest Way

time python script.py
Python

Output

real    0m0.187s
user    0m0.022s
sys     0m0.026s
Python

if not a: vs if a is None:

if not a:

  • Checks all falsy values, inclduing
    • None
    • False
    • 0 (int)
    • 0.0(float)
    • “” (empty string)
    • [], {}, set() (empty collections)

if a is None:

Strictly checks if a is None and nothing else.

uses

  • Use if a is None: when you specifically want to check if a is None.
  • Use if not a: when you want to check for any falsy value.

if string is None: # not executed
    print("1") 

if not string:
    print("2") #2

if lst is None: # not executed
    print("1")

if not lst:
    print("2") #2


if a is None:
    print("1") #1

if not a:
    print("2") #2
Python

json.load vs json.loads

The difference between json.load() and json.loads() in Python is:

json.load(file_object)

  • Used for reading JSON data from a file.
  • Takes a file object as an argument.
import json

json_str = '{"name": "Alice", "age": 25}'
data = json.loads(json_str)  # Converts JSON string to Python dictionary
print(data)
Python

json.loads(json_string)

  • Used for parsing a JSON string
  • Takes a string as an argument

import json

with open("data.json", "r") as file:
    data = json.load(file)  # Reads JSON from a file
print(data)
Python

json.dump vs json.dumps

json.dump(obj, file_object)

  • Used to write a Python object to a file in JSON format.
  • Takes a file object as an argument.

import json

data = {"name": "Alice", "age": 25}

with open("output.json", "w") as file:
    json.dump(data, file, indent=4)  # Writes JSON to a file
Python

json.dumps(obj)

  • Used to convert a Python object into a JSON string.
  • Takes a Python object (dict, list, etc.) and returns a JSON-formatted string.

import json

data = {"name": "Alice", "age": 25}

json_str = json.dumps(data, indent=4)  # Converts to a JSON string
print(json_str)
Python

Which is valid Json in Python?

print(json.loads("1")) # 1
print(json.loads("true"))   # Output: True (bool)
print(json.loads("false"))  # Output: False (bool)
print(json.loads("null"))   # Output: None (NoneType)
print(json.loads("3.14"))   # Output: 3.14 (float)
Python

json.loads() is designed to considered following as a valid JSON value.

  • a number (like 1, 2.5, -3) can be a standalone valid JSON value.
  • true, false etc

What are Higher-Order Functions?

Higher-order functions are functions that either:

  • Take one or more functions as arguments
  • Return a function as a result

They are a key feature of functional programming and help make code more modular and reusable.

Passing a Function as an Argument

def apply_function(func, value):
    return func(value)

def square(x):
    return x * x

print(apply_function(square, 5))  # Output: 25
Python

Returning a Function

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

double = multiplier(2)
print(double(5))  # Output: 10

triple = multiplier(3)
print(triple(5))  # Output: 15
Python

Name few built-in higher-order functions?

  • map
  • filter
  • reduce

How can you debug a Python script?

Debugging a Python script effectively involves various tools and techniques. Here are three common methods:

  • Using Built-in Debugging Tools: pdb (Python Debugger)
  • Print Statements
  • IDE Debugging Features
    • Integrated Development Environments (IDEs) like PyCharm, VS Code, or Jupyter Notebooks have built-in debugging tools with graphical interfaces.
    • Features like breakpoints, variable inspection, and step-by-step execution make it easier to locate and resolve issues

You are given a large dataset that doesn’t fit into memory. How would you process it?

Use Chunking with Libraries:

Many libraries support processing large datasets in chunks.

Pandas

import pandas as pd

# Read the dataset in chunks
chunk_size = 100000  # Number of rows per chunk
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    # Process each chunk
    print(chunk.head())  # Example: Print the first few rows of each chunk
Python

Dask: Dask provides a scalable way to process large datasets.

import dask.dataframe as dd

# Load a large CSV file as a Dask DataFrame
df = dd.read_csv('large_file.csv')

# Perform operations (these are lazy and will compute only when needed)
result = df.groupby('column_name').mean()

# Compute the result
result.compute()
Python

Use Generators for Lazy Loading

Generators allow you to load and process data lazily, row by row or in small chunks.

def read_large_file(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line  # Yield one line at a time

# Process the file
for line in read_large_file('large_file.txt'):
    print(line)  # Example: Print each line
Python

Use Database Queries

Instead of loading the entire dataset into memory, query only the required data from a database.

Utilize File Formats Designed for Large Data

File formats like Parquet or HDF5 are optimized for large datasets and allow partial reads.

Parallel or Distributed Processing

Divide the dataset and process parts in parallel using tools like multiprocessing, Ray, or Dask.

Stream Data with Libraries

For specific formats like JSON, use libraries that support streaming.

What is PEP?

  • Python Enhancement Proposals
  • PEPs are design documents that provide information about new features, improvements, and best practices in Python. They serve as proposals for changes or additions to the language, libraries, or processes.

Some key types of PEPs:

  • PEP 8 – Style guide for writing Python code
  • PEP 20 – The Zen of Python (guiding principles)
  • PEP 257 – Docstring conventions
  • PEP 484 – Type hinting

What is literals?

Literals can be defined as a data which is given in a variable or constant.

What is docstring in Python?

A docstring (short for documentation string) is a special type of comment in Python that describes what a function, class, or module does. It is enclosed in triple quotes (“”” “”” or ”’ ”’) and is typically the first statement inside a function, class, or module.

def add_numbers(a: int, b: int) -> int:
    """
    Adds two numbers and returns the result.

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

    Returns:
    int: The sum of the two numbers.
    """
    return a + b
Python

What are protected and private attributes in Python?

In Python, protected and private attributes are used to control access to class members (variables and methods). However, Python does not enforce strict access control like some other languages (e.g., Java, C++). Instead, it relies on naming conventions to indicate the intended level of access.

Protected Attributes (single_underscore)

  • A protected attribute is indicated by a single underscore ().
  • It signals that the attribute should not be accessed directly outside the class but can still be accessed if necessary.
  • It is only a convention, and Python does not enforce this restriction.
class Person:
    def __init__(self, name, age):
        self.name = name           # Public attribute
        self._age = age            # Protected attribute

    def _get_age(self):            # Protected method
        return self._age

p = Person("John", 30)
print(p.name)      # John (Public, can be accessed)
print(p._age)      # 30 (Protected, but still accessible)
print(p._get_age())  # 30 (Protected method, still accessible)
Python

The _age attribute and _get_age() method should not be accessed directly, but Python does not prevent it.

Private Attributes (double_underscore)

  • A private attribute is indicated by a double underscore ().
  • Python applies name mangling, changing __attribute to _ClassName__attribute, making it harder (but not impossible) to access directly.
  • This is used to avoid accidental modifications by subclasses.
class Person:
    def __init__(self, name, age):
        self.name = name           # Public attribute
        self.__age = age           # Private attribute

    def get_age(self):             # Public method to access private attribute
        return self.__age

p = Person("Alice", 25)
print(p.name)       # Alice
# print(p.__age)    # AttributeError: 'Person' object has no attribute '__age'

# Accessing private attribute using name mangling
print(p._Person__age)  # 25 (Not recommended, but possible)
Python

__age is not directly accessible, but it can still be accessed using _ClassName__attribute

What are modules and packages in Python?

Modules

A module is a single Python file (.py) that contains Python code such as functions, classes, and variables.

import math_operations

result = math_operations.add(5, 3)
print(result)  # Output: 8
Python

Packages

A package is a collection of related Python modules organized in a directory. A package must contain an init.py file (empty or with initialization code) to be recognized as a package.

my_package/
│── __init__.py
│── math_operations.py
│── string_operations.py 
Python

What is an Interpreted language?

  • An Interpreted language executes its statements line by line.
  • Languages such as Python, Javascript, R, PHP, and Ruby are prime examples of Interpreted languages.
  • Programs written in an interpreted language runs directly from the source code, with no intermediary compilation step.

What are Python namespaces?

In Python, namespaces are used to manage the names of variables, functions, and objects to avoid conflicts. A namespace is essentially a container that maps names (keys) to objects (values).

Types of Python Namespaces

Built-in Namespace :

  • Contains built-in functions and exceptions, like print(), len(), and Exception.
  • Available everywhere in Python.

Global Namespace :

  • Defined at the top level of a script or module.
  • Includes variables and functions defined outside any function or class.

Local Namespace:

  • Created inside a function and holds variables defined within that function.
  • It only exists while the function runs.

Enclosing (Nonlocal) Namespace (Closures):

  • Exists when a nested function is used.
  • The inner function can access variables from the outer function’s scope (but not modify them unless nonlocal is used).

Namespace Scope

Python follows the LEGB rule to resolve variable names:

Local → Enclosing → Global → Built-in
Python

x = "global"

def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print(x)  # "local" is printed first
    inner()
    print(x)  # "enclosing" is printed next

outer()
print(x)  # "global" is printed last
Python

Why Are Namespaces Important?

  • Prevents naming conflicts.
  • Improves code organization and modularity.
  • Helps Python efficiently manage memory.

How is memory managed in Python?

Memory management in Python is handled automatically using reference counting and garbage collection. Here’s a breakdown of how it works:

Reference Counting

  • Every object in Python has an associated reference count, which keeps track of how many variables or data structures refer to it.
  • When the reference count drops to zero, the memory occupied by the object is automatically deallocated.
import sys

a = [1, 2, 3]
print(sys.getrefcount(a))  # Reference count includes temporary references
b = a  # Now two references exist
print(sys.getrefcount(a))  # Increased by 1
del a
print(sys.getrefcount(b))  # Decreased by 1, but still > 1 because of 'b'


OutPut
2
3
2
Python

Explanation:

First sys.getrefcount(a)

  • When a is created, it has one reference.
  • Passing it to sys.getrefcount(a) temporarily creates another reference.
  • So, the count is 2.

After b = a, we now have two permanent references (a and b).

  • Calling sys.getrefcount(a) again will include:
    • a
    • b
    • The temporary reference from sys.getrefcount(a)
  • So, the count becomes 3.

After del a, the reference from a is removed, leaving:

  • b
  • The temporary reference from sys.getrefcount(b)
  • So, the count is back to 2.

However, if you run this in an interactive environment (like Jupyter Notebook or an IDE with debugging features), the reference count might be slightly different due to internal optimizations.

Garbage Collection (GC)

  • Python has a built-in garbage collector to handle cyclic references (e.g., objects referring to each other).
  • It uses Generational Garbage Collection, which divides objects into three generations:
    • Gen 0 (youngest)
    • Gen 1 (middle-aged)
    • Gen 2 (oldest)
  • The garbage collector runs periodically to clean up unreachable objects in these generations.

You can manually invoke it using:

import gc
gc.collect()
Python

Memory Allocation

  • Python uses, PyMalloc (Python’s memory manager) to allocate memory efficiently.
  • Small Object Allocator for objects smaller than 512 bytes.
  • Heap Memory for larger objects.

Objects with Special Memory Handling

  • Immutable types (int, str, tuple, etc.): Python caches small integers (-5 to 256) and short strings to reuse memory.
  • Large Objects: Managed directly using the system’s memory allocator.

    Avoiding Memory Leaks

    • Circular references should be minimized (use weak references with weakref module).
    • Large lists or dictionaries should be cleared explicitly if not needed.
    • Use context managers (with statement) for handling files and resources properly.

    Python support passed by value or pass by reference?

    In Python, everything is passed by object reference, but the behavior depends on whether the object is mutable or immutable.

    Read Details

    What is the difference between .py and .pyc files?

    The difference between .py and .pyc files lies in their purpose and usage in Python:

    .py Files (Python Source Code)

    • These contain human-readable Python source code.
    • They can be directly written, edited, and executed.
    • When you run a .py file, Python compiles it into bytecode before executing it.

    .pyc Files (Compiled Python Bytecode)

    • These are compiled versions of .py files, containing bytecode.
    • They are not human-readable.
    • Python automatically generates them when a module is imported to speed up execution in future runs.
    • They are typically stored in a pycache directory with a filename format like module.cpython-xx.pyc, where xx represents the Python version.

    Key Differences

    Feature.py (Source Code).pyc (Compiled Bytecode)
    ReadabilityHuman-readableMachine-readable (bytecode)
    ExecutionRuns directlyNeeds a Python interpreter to execute
    GenerationWritten by the developerAuto-generated by Python on import
    SpeedNeeds to be compiled before executionExecutes faster as it skips compilation

    When Do You See .pyc Files?

    When you import a module (import module_name), Python compiles it and stores the .pyc file in pycache. If the source code (.py) changes, Python will regenerate the .pyc file.

    What are docstrings ?

    Docstrings are multi-line string literals used to document Python modules, classes, functions, and methods. They typically follow the “””triple-double-quote””” format and provide information on what the code does, expected parameters, return values, and examples of usage.

    class Car:
        """Represents a car with a brand and speed.
    
        Attributes:
            brand (str): The brand of the car.
            speed (int): The speed of the car in km/h.
        """
    
        def __init__(self, brand: str, speed: int):
            """Initializes a Car object.
    
            Args:
                brand (str): The brand of the car.
                speed (int): The speed of the car in km/h.
            """
            self.brand = brand
            self.speed = speed
    Python

    What is the use of help()?

    The help() function in Python is used to get documentation about modules, functions, classes, or objects. It provides details such as the docstring, method signatures, and descriptions, making it useful for understanding how to use a particular component.

    help(len) #This shows documentation related to len.
    help(str) #This shows documentation related to str.
    Python

    Custom class and function

    class Person:
        """A class representing a person."""
    
        def __init__(self, name, age):
            """Initialize the person with a name and age."""
            self.name = name
            self.age = age
    
        def greet(self):
            """Greet the user."""
            return f"Hi, I'm {self.name}!"
    
    help(Person)
    Python

    Output

    Help on class Person in module __main__:
    
    class Person(builtins.object)
     |  A class representing a person.
     |  
     |  Methods defined here:
     |  
     |  __init__(self, name, age)
     |      Initialize the person with a name and age.
     |  
     |  greet(self)
     |      Greet the user.
    
    Python

    Takeaway

    • help() reads docstrings, so always include “””Triple quoted strings””” in your functions, classes, and modules.
    • It’s a great way to document your code and make it easier to understand for yourself and others.

    What is PYTHONPATH in Python?

    PYTHONPATH is an environment variable in Python that specifies additional directories where Python should look for modules and packages when importing them.

    What is pickling and unpickling?

    Picking

    Pickling is the process of converting a Python object into a byte stream, so it can be stored in a file or sent over a network. This allows you to save and reload Python objects later. The pickle module in Python is used for this purpose.

    import pickle
    
    data = {"name": "Alice", "age": 25, "city": "New York"}
    
    # Pickling (Serializing)
    with open("data.pkl", "wb") as file:
        pickle.dump(data, file)
    Python

    Unpickling

    Unpickling is the reverse process—converting a byte stream back into a Python object.

    # Unpickling (Deserializing)
    with open("data.pkl", "rb") as file:
        loaded_data = pickle.load(file)
    
    print(loaded_data)
    Python

    Use Cases

    • Saving program states.
    • Sending Python objects over a network.
    • Storing complex data structures (e.g., dictionaries, lists) in a file.

    Limitations

    • The pickled data is Python-specific and not human-readable.
    • It is not secure if loading untrusted data (can execute arbitrary code).
    • Pickled files may not be compatible across different Python versions.

    if security is a concern, JSON is a safer alternative to pickle because it only supports basic data types (strings, numbers, lists, dictionaries, etc.) and does not execute arbitrary code when deserialized.

    Key Differences Between Pickle and JSON

    FeaturePickleJSON
    FormatBinaryText (Readable)
    SecurityNot Safe (Can execute code)Safe
    Cross-Language SupportPython-specificWorks across different languages
    Supports Custom ObjectsYesNo (Only basic types)
    SpeedFasterSlower

    Pickle vs JSON

    Both pickle and JSON are used for serialization in Python, but they serve different purposes and have distinct trade-offs.

    FeaturePickleJSON
    FormatBinaryText (Human-readable)
    SecurityNot secure (Can execute arbitrary code)Secure (No code execution)
    Cross-Language SupportPython-specificSupported in many languages (JavaScript, Java, etc.)
    SpeedFaster (Optimized for Python)Slower (Parses text)
    File SizeSmaller (Compact binary format)Larger (Text-based)
    Supports Custom ObjectsYes (Can serialize any Python object)No (Only basic data types: dict, list, int, str, etc.)
    ReadabilityNot human-readableHuman-readable
    Use CasePython-only applications, quick object storageWeb APIs, cross-platform data sharing

    When to Use Pickle?

    • You need to serialize complex Python objects (e.g., classes, tuples, sets).
    • You are working only in Python (not sharing data with other languages).
    • Performance is critical, and speed matters.
    • Avoid if security is a concern—loading a malicious pickle file can execute arbitrary code.

    When to Use JSON?

    • You need human-readable data.
    • You are working with web APIs (REST, JavaScript, etc.).
    • Security and cross-language compatibility are required.

    Takeaway

    • Pickle: Best for Python-specific object storage (fast but unsafe).
    • JSON: Best for data exchange (slightly slower but universal & safe).

    For most cases, JSON is the better choice unless you specifically need to store Python objects without cross-language support.

    What is the difference between xrange and range ?

    In Python 2, there was a difference between xrange and range:

    • range(): Returns a list containing all the numbers in the specified range. It consumes more memory for large ranges.
    • xrange(): Returns an iterator that generates numbers lazily, meaning it doesn’t create a full list in memory, making it more memory efficient.

    Python 3:

    • xrange() was removed.
    • range() behaves like xrange() from Python 2, meaning it returns a lazy sequence generator instead of a list.

    Type of inheritance work in python?

    • Single Inheritance : A child class inherits from a single parent class.
    • Multiple Inheritance :A child class inherits from more than one parent class.
    • Multilevel Inheritance :A child class inherits from a parent class, which itself inherits from another parent.
    • Hierarchical Inheritance : Multiple child classes inherit from the same parent class.
    • Hybrid Inheritance (Combination of multiple types): A mix of more than one type of inheritance.

    Read More

    What is Monkey patching?

    Monkey patching is a technique used in dynamic languages like Python to modify or extend code at runtime. It allows you to change the behavior of modules, classes, or functions without modifying the original source code.

    Monkey Patching a Function

    Replacing an existing function with a new one at runtime.

    def greet():
        return "Hello!"
    
    print(greet())  # Output: Hello!
    
    # Monkey patching greet function
    def new_greet():
        return "Hi there!"
    
    greet = new_greet  # Apply monkey patch
    
    print(greet())  # Output: Hi there!
    Python

    Monkey Patching a Class Method

    Modifying a method of a class dynamically.

    class Person:
        def say_hello(self):
            return "Hello!"
    
    p = Person()
    print(p.say_hello())  # Output: Hello!
    
    # Monkey patching the method
    def new_say_hello(self):
        return "Hi there!"
    
    Person.say_hello = new_say_hello  # Apply monkey patch
    
    print(p.say_hello())  # Output: Hi there!
    Python

    Monkey Patching an Instance Method

    Changing behavior for one object only.

    class Dog:
        def bark(self):
            return "Woof!"
    
    dog1 = Dog()
    dog2 = Dog()
    
    print(dog1.bark())  # Output: Woof!
    print(dog2.bark())  # Output: Woof!
    
    # Monkey patching only for dog1
    def quiet_bark(self):
        return "woof..."
    
    dog1.bark = quiet_bark.__get__(dog1)  # Apply monkey patch
    
    print(dog1.bark())  # Output: woof...
    print(dog2.bark())  # Output: Woof!  (remains unchanged)
    Python

    With Statement in Python

    The with statement in Python is used for resource management and exception handling, ensuring that resources like files or network connections are properly cleaned up after use. It is commonly used with context managers.

    Syntax

    with expression as variable:
        # Code block
    Python

    Common Use Cases

    • File Handling: with open()
    • Database Connections: with sqlite3.connect()
    • Thread Locks: with threading.Lock()
    • Temporary Files: with tempfile.TemporaryFile()
    • Managing Network Connections: with socket.create_connection()

    What is Context manager?

    Context managers in Python are constructs that allow you to properly manage resources, ensuring that they are acquired and released correctly. They are primarily used with the with statement to handle resources like file handling, database connections, threading locks, etc.

    Why Use Context Managers?

    • Automatic resource management – Ensures resources like files, network connections, and database sessions are properly closed after use.
    • Cleaner code – Reduces boilerplate code for try…finally blocks.
    • Exception safety – Ensures resource cleanup even if an error occurs.

    Using Built-in Context Managers

    Many built-in Python objects provide context managers. For example: File Handling

    with open("example.txt", "w") as file:
        file.write("Hello, World!")
    # File automatically closes here
    Python

    Creating Custom Context Managers

    You can create your own context manager in two ways:

    A. Using a Class with __enter__ and __exit__ Methods

    class MyContext:
        def __enter__(self):
            print("Entering the context")
            return self  # The return value is assigned to 'as' variable in 'with'
        
        def __exit__(self, exc_type, exc_value, traceback):
            print("Exiting the context")
            # If exception occurs, it will still call this method
    
    # Usage
    with MyContext():
        print("Inside the context block")
    Python

    Output

    Entering the context
    Inside the context block
    Exiting the context
    Python

    __enter__()

    __enter__() is the first method that gets called when you enter a with block using a context manager.

    __exit__()

    __exit__() is always called — whether:

    • the with block completes normally, or
    • an exception is raised inside the with block.

    Using contextlib and @contextmanager Decorator

    A more concise way is using the contextlib.contextmanager decorator:

    from contextlib import contextmanager
    
    @contextmanager
    def my_context():
        print("Entering the context")
        yield  # This is where the block inside 'with' runs
        print("Exiting the context")
    
    # Usage
    with my_context():
        print("Inside the context block")
    Python

    Output

    Entering the context
    Inside the context block
    Exiting the context
    Python

    When to Use Context Managers?

    • File handling (open)
    • Database connections
    • Thread locks (threading.Lock())
    • Network connections (e.g., requests.Session())
    • Temporary resource allocation (e.g., temporary files)

    Database Connection Example

    import sqlite3
    
    @contextmanager
    def database_connection(db_name):
        conn = sqlite3.connect(db_name)
        cursor = conn.cursor()
        try:
            yield cursor  # Provide the cursor object
            conn.commit()  # Commit if no error occurs
        except Exception:
            conn.rollback()  # Rollback if an error occurs
            raise
        finally:
            conn.close()
    
    # Usage
    with database_connection("test.db") as db:
        db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
        db.execute("INSERT INTO users (name) VALUES ('Alice')")
    Python

    Conclusion

    Learning the basics of Python is the first step toward unlocking its vast potential. From understanding variables and control flow to working with data structures and functions, these fundamental concepts are essential for solving real-world problems with Python. With practice, you can confidently advance to more complex topics like object-oriented programming, file handling, and working with external libraries. Embrace the learning process, experiment with code, and enjoy the simplicity and power that Python offers.

    Resources