Python Interview Questions
π‘ Click Show Answer to generate an AI-powered answer instantly.
What is Python and what are its key features?
Python is a high-level, interpreted, general-purpose programming language widely used for web development, data analysis, artificial intelligence, scientific computing, and more. It emphasizes code readability and allows programmers to express concepts in fewer lines of code.
What is Python?
Python, created by Guido van Rossum and first released in 1991, is known for its simplicity and ease of use, making it an excellent language for beginners while also being powerful enough for complex applications. It supports multiple programming paradigms, including object-oriented, imperative, and functional programming.
Key Features of Python
- Simple and Easy to Learn: Python has a straightforward syntax and natural language flow, making it easy to read and write.
- Readability: Its clear and concise syntax enhances code readability and reduces the cost of program maintenance.
- Versatile and General-Purpose: Used in a wide range of applications, including web development (Django, Flask), data science (NumPy, Pandas), machine learning (TensorFlow, scikit-learn), automation, and scripting.
- Extensive Standard Library: Python boasts a large and comprehensive standard library that provides tools for many common programming tasks.
- Cross-Platform Compatibility: Python code can run on various operating systems like Windows, macOS, and Linux without modification.
- Interpreted Language: Code is executed line by line, which makes debugging easier.
- Object-Oriented Programming (OOP): Python supports OOP concepts, allowing for modular and reusable code.
- Dynamically Typed: Variables do not need explicit declaration of their type; the type is inferred at runtime.
Example: Hello World in Python
print('Hello, World!')
# This is a simple Python program.
These features collectively contribute to Python's popularity and versatility, making it a preferred choice for developers across various domains, from small scripts to large-scale enterprise applications.
What are Python data types?
In Python, data types are classifications that specify which type of value a variable has and what type of mathematical, relational, or logical operations can be applied to it without causing an error. Python is dynamically typed, meaning you don't declare the type of a variable when you create it.
What are Data Types?
Data types represent the kind of value stored in a variable. They determine the operations that can be performed on the data and the way it is stored in memory. Python provides several built-in data types to handle various kinds of data efficiently.
Common Python Data Types
Numeric Types
These types represent numerical values. Python supports integers, floating-point numbers, and complex numbers.
- int: Integers (e.g., 10, -500)
- float: Floating-point numbers (e.g., 3.14, -0.001)
- complex: Complex numbers (e.g., 1 + 2j)
Sequence Types
Sequences are ordered collections of items. They allow you to store multiple values in an organized way.
- str: Strings (e.g., "hello", 'Python')
- list: Ordered, mutable collection (e.g., [1, 2, 3], ['a', 'b'])
- tuple: Ordered, immutable collection (e.g., (1, 2, 3), ('x', 'y'))
Set Types
Sets are unordered collections of unique items. They are useful for mathematical set operations.
- set: Unordered, mutable collection of unique items (e.g., {1, 2, 3})
- frozenset: Unordered, immutable collection of unique items
Mapping Type
Mappings store data in key-value pairs, where each key maps to a specific value.
- dict: Unordered, mutable collection of key-value pairs (e.g., {'name': 'Alice', 'age': 30})
Boolean Type
Booleans represent truth values, often used in conditional statements.
- bool: Represents either True or False
None Type
The None type signifies the absence of a value or a null value.
- NoneType: Represents the None object, indicating no value
You can check the type of any variable using the type() function:
x = 10
y = 3.14
z = "hello"
my_list = [1, 2, 3]
my_dict = {"a": 1, "b": 2}
print(type(x)) # <class 'int'>
print(type(y)) # <class 'float'>
print(type(z)) # <class 'str'>
print(type(my_list)) # <class 'list'>
print(type(my_dict)) # <class 'dict'>
print(type(True)) # <class 'bool'>
print(type(None)) # <class 'NoneType'>
Mutability and Immutability
Python data types can also be classified as mutable or immutable. Mutable objects can be changed after they are created, while immutable objects cannot. When an immutable object is 'modified', a new object is actually created.
| Category | Mutable Types | Immutable Types |
|---|---|---|
| Numeric | int, float, complex, bool | |
| Sequence | list | str, tuple |
| Set | set | frozenset |
| Mapping | dict | |
| Other | NoneType |
Difference between list and tuple in Python?
In Python, lists and tuples are both fundamental data structures used to store collections of items. While they share some similarities, such as being ordered sequences, they have critical differences primarily related to their mutability and intended use cases.
Key Differences
The most significant distinction between lists and tuples lies in their mutability. A mutable object can be changed after it is created, while an immutable object cannot. This characteristic influences how they are used and their performance implications.
Mutability
Lists are mutable, meaning you can modify their elements, add new elements, or remove existing ones after the list has been created. Tuples, on the other hand, are immutable. Once a tuple is created, its elements cannot be changed, added, or removed. This immutability makes tuples more predictable and safer for certain operations.
Syntax
Lists are defined by enclosing their elements in square brackets [], while tuples are defined by enclosing their elements in parentheses (). A tuple with a single element requires a trailing comma.
my_list = [1, 2, 3]
my_tuple = (1, 2, 3)
single_element_tuple = (1,)
Operations and Methods
Due to their mutability, lists support a wider range of operations and methods for modification. Tuples have a more limited set of methods, primarily focused on querying elements.
- List methods:
append(),extend(),insert(),remove(),pop(),sort(),reverse(), etc. - Tuple methods:
count(),index().
my_list = [1, 2, 3]
my_list.append(4) # Valid
print(my_list) # Output: [1, 2, 3, 4]
my_tuple = (1, 2, 3)
# my_tuple.append(4) # This would raise an AttributeError
# Example of tuple immutability
# my_tuple[0] = 5 # This would raise a TypeError
Performance
Because tuples are immutable, Python can often optimize their creation and access, making them slightly faster than lists in some scenarios, especially when dealing with fixed collections of data. Tuples can also be used as dictionary keys because they are hashable (immutable), whereas lists cannot.
Summary Table
| Feature | List | Tuple |
|---|---|---|
| Mutability | Mutable (can be changed) | Immutable (cannot be changed) |
| Syntax | Square brackets `[]` | Parentheses `()` |
| Performance | Slightly slower | Slightly faster |
| Methods | Many (append, insert, pop, etc.) | Few (count, index) |
| Use Case | Collections that might change | Fixed collections, dictionary keys, function arguments |
What is a dictionary in Python?
A Python dictionary is an unordered collection of data values, used to store data values like a map. Unlike other data types that hold only a single value as an element, dictionaries hold key:value pairs. Dictionaries are mutable, meaning they can be changed after creation, and are optimized for retrieving values when the key is known.
What is a Dictionary?
Dictionaries are Python's implementation of a hash table type. They consist of a collection of key-value pairs. Each key maps to a specific value, allowing for efficient data retrieval. Keys must be unique and immutable (like strings, numbers, or tuples), while values can be of any data type and can be duplicated.
They are often used to store related pieces of information, such as a person's name, age, and city, where each piece of information has a descriptive label (the key).
Creating Dictionaries
Dictionaries are created by placing a comma-separated list of key:value pairs inside curly braces {}. An empty dictionary can be created with just empty curly braces.
# Empty dictionary
my_dict = {}
print(my_dict)
# Dictionary with integer keys
student = {
101: 'Alice',
102: 'Bob',
103: 'Charlie'
}
print(student)
# Dictionary with mixed keys and values
person = {
'name': 'John Doe',
'age': 30,
'is_student': False,
'courses': ['Math', 'Science']
}
print(person)
Accessing Dictionary Elements
Values can be accessed using their respective keys inside square brackets [] or by using the get() method. The get() method is safer as it returns None (or a default value if specified) if the key is not found, instead of raising a KeyError.
person = {
'name': 'Jane Doe',
'age': 25,
'city': 'New York'
}
# Accessing using square brackets
print(person['name']) # Output: Jane Doe
# Accessing using get() method
print(person.get('age')) # Output: 25
print(person.get('country')) # Output: None
print(person.get('country', 'USA')) # Output: USA (default value)
Modifying and Deleting Elements
Dictionaries are mutable, allowing you to add new key-value pairs, update existing values, and remove elements.
my_settings = {
'theme': 'dark',
'notifications': True
}
# Adding a new key-value pair
my_settings['language'] = 'en'
print(my_settings)
# Updating an existing value
my_settings['theme'] = 'light'
print(my_settings)
# Deleting a key-value pair using del
del my_settings['notifications']
print(my_settings)
# Deleting a key-value pair using pop()
# pop() also returns the value of the removed item
removed_language = my_settings.pop('language')
print(my_settings)
print(removed_language)
# popitem() removes and returns an arbitrary (last inserted in Python 3.7+) key-value pair
last_item = my_settings.popitem()
print(my_settings)
print(last_item)
Common Dictionary Methods
Python dictionaries come with several built-in methods to interact with their contents.
keys(): Returns a new view of the dictionary's keys.values(): Returns a new view of the dictionary's values.items(): Returns a new view of the dictionary's key-value pairs as tuples.update(other): Updates the dictionary with the key-value pairs fromother, overwriting existing keys.clear(): Removes all items from the dictionary.copy(): Returns a shallow copy of the dictionary.
Key Characteristics
- Unordered (pre-Python 3.7): Order of elements is not guaranteed. From Python 3.7 onwards, dictionaries maintain insertion order.
- Mutable: Dictionaries can be changed after they are created (add, remove, or modify elements).
- Key-Value Pairs: Data is stored as pairs where each unique key maps to a value.
- Keys must be Unique: No two keys can be the same within a single dictionary.
- Keys must be Immutable: Keys can be strings, numbers, or tuples (containing only immutable elements), but not lists or other mutable objects.
Understanding Key Immutability
The requirement for keys to be immutable is fundamental to how dictionaries work internally. Dictionaries use a hashing mechanism to store and retrieve data efficiently. Mutable objects can change their hash value, which would break the ability of the dictionary to find the key correctly. Therefore, mutable types like lists, sets, or other dictionaries cannot be used as keys.
What are sets in Python?
What is list comprehension?
List comprehension is a concise and elegant way to create lists in Python. It offers a more compact syntax than traditional for loops and often improves readability and performance.
What is List Comprehension?
List comprehension in Python provides a shorter syntax to create new lists based on existing iterables (like lists, tuples, strings, etc.). It filters and transforms elements in a single line of code, making it more 'Pythonic' than many other approaches.
Basic Syntax
new_list = [expression for item in iterable]
Here, expression is the operation performed on each item, and iterable is the source collection. The result of the expression for each item is collected into new_list.
Equivalent Loop
A list comprehension can often replace a multi-line for loop that appends items to a list.
squares = []
for i in range(1, 6):
squares.append(i**2)
# Equivalent list comprehension:
# squares = [i**2 for i in range(1, 6)]
Why Use List Comprehension?
- Conciseness: Reduces boilerplate code compared to traditional loops.
- Readability: Can be easier to read and understand the intent of the code for simple transformations.
- Performance: Often faster than a traditional for loop for creating lists, as it is optimized internally in CPython.
With Conditional Logic
You can include an if clause to filter items from the iterable.
even_numbers = [x for x in range(10) if x % 2 == 0]
# even_numbers will be [0, 2, 4, 6, 8]
Nested List Comprehensions
List comprehensions can be nested, similar to nested for loops, to work with multi-dimensional data structures like matrices.
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
# flattened will be [1, 2, 3, 4, 5, 6, 7, 8, 9]
Conclusion
List comprehensions are a powerful and idiomatic feature in Python for creating lists efficiently and expressively. Mastering them is key to writing clean and performant Python code for list manipulation.
What is a lambda function?
A lambda function in Python is a small anonymous function defined using the `lambda` keyword. It can take any number of arguments but can only have one expression.
Definition
Lambda functions are also known as anonymous functions because they are not declared with the standard def keyword. They are typically used for short, single-expression functions, often as arguments to higher-order functions like map(), filter(), and sorted().
Syntax
The basic syntax for a lambda function is lambda arguments: expression. The arguments are optional, and the expression is evaluated and returned.
add = lambda a, b: a + b
print(add(5, 3)) # Output: 8
Key Characteristics
- Anonymous: They don't have a name.
- Single Expression: They can only contain one expression, which is implicitly returned.
- Inline: Often used for short, throwaway functions that are not needed elsewhere.
- Can take multiple arguments: Like regular functions, they can accept zero or more arguments.
Common Use Cases
- With
map(): Applying a function to all items in an iterable. - With
filter(): Filtering elements from an iterable based on a condition. - With
sorted(): Defining a custom sort key for sorting collections. - GUI event handlers: For simple callback functions.
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(lambda x: x * x, numbers))
print(squared_numbers) # Output: [1, 4, 9, 16, 25]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # Output: [2, 4]
Limitations
- Single expression only: Cannot contain multiple statements, loops, or complex logic.
- No docstrings: Lack the ability to include a docstring for documentation.
- Readability: Overuse or complex lambda expressions can reduce code readability compared to named functions.
What is the difference between == and is in Python?
In Python, both `==` and `is` are used for comparisons, but they serve fundamentally different purposes. Understanding when to use each is crucial for writing correct and efficient Python code.
Understanding `==` (Equality Operator)
The == operator compares the *values* of two objects. It checks if the objects have equivalent content. This comparison is typically implemented by the __eq__ method of the objects involved.
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = [4, 5, 6]
print(f"list1 == list2: {list1 == list2}") # True (values are equal)
print(f"list1 == list3: {list1 == list3}") # False (values are different)
str1 = "hello"
str2 = "hello"
print(f"str1 == str2: {str1 == str2}") # True
Understanding `is` (Identity Operator)
The is operator compares the *identity* of two objects. It checks if two variables refer to the *exact same object in memory*. This means they must have the same memory address. It is equivalent to comparing the result of the id() function for both objects.
list1 = [1, 2, 3]
list2 = [1, 2, 3] # A new list object is created
list_ref = list1 # list_ref now refers to the same object as list1
print(f"list1 is list2: {list1 is list2}") # False (different objects in memory)
print(f"list1 is list_ref: {list1 is list_ref}") # True (same object in memory)
# For immutable objects like small integers and strings, Python might
# intern them, leading to 'is' returning True for value-equal objects.
a = 10
b = 10
c = 20
print(f"a is b: {a is b}") # True (often for small integers due to interning)
print(f"a is c: {a is c}") # False
str1 = "python"
str2 = "python"
str3 = "Python"
print(f"str1 is str2: {str1 is str2}") # True (often for identical strings due to interning)
print(f"str1 is str3: {str1 is str3}") # False
Key Differences
==compares the *values* of objects.iscompares the *identity* (memory address) of objects.==can be overridden by the__eq__method, defining custom equality logic.iscannot be overridden; it always checks for object identity.- Two objects can have equal values (
==is True) but be different objects in memory (isis False). - If two objects are the same object in memory (
isis True), their values will always be equal (==is also True).
When to Use Which
Use == when you want to compare if two objects have the same content or value. This is the most common comparison operator for most data types. Use is when you specifically need to check if two variables refer to the exact same instance of an object in memory. A common use case for is is checking if a variable is None (e.g., if x is None:), because None is a singleton object.
| Feature | `==` (Equality Operator) | `is` (Identity Operator) |
|---|---|---|
| Purpose | Compares values | Compares object identity (memory address) |
| Behavior | Checks if objects have equivalent content | Checks if variables refer to the exact same object |
| Overridable | Yes, via `__eq__` method | No |
| `None` comparison | Generally `x == None` (though `x is None` is preferred) | Recommended for `None` (e.g., `x is None`) |
| Example Output (`[1,2]` vs `[1,2]`) | True (values are equal) | False (different objects) |
| Example Output (`a = [1,2]`, `b = a`) | `a == b` is True | `a is b` is True |
What is mutable and immutable in Python?
In Python, the distinction between mutable and immutable data types is fundamental to understanding how variables behave and how memory is managed. It determines whether the value of an object can be changed after it has been created.
Understanding Mutability
Mutability refers to the ability of an object to be changed after it is created. If an object is mutable, its state can be altered without creating a new object. If an object is immutable, its state cannot be changed once it's created; any operation that appears to modify it actually creates a new object.
Mutable Types
- List
- Dictionary
- Set
- Byte Array
Mutable objects can be modified in-place. This means that if you have a variable referencing a mutable object, you can change the content of that object directly without assigning a new object to the variable. All references to that object will see the updated value.
my_list = [1, 2, 3]
print(f"Original list: {my_list}")
my_list.append(4)
print(f"Modified list: {my_list}")
# Demonstrating shared reference
another_list = my_list
another_list.append(5)
print(f"Original list after shared modification: {my_list}")
print(f"Another list: {another_list}")
Immutable Types
- Integer
- Float
- String
- Tuple
- Frozenset
- Bytes
Immutable objects cannot be changed after creation. Any operation that seems to modify an immutable object, such as concatenating strings or adding to numbers, actually results in the creation of a *new* object with the updated value, and the variable is then re-assigned to this new object. The original object remains unchanged in memory (until garbage collected).
my_string = "Hello"
print(f"Original string: {my_string}")
print(f"ID of original string: {id(my_string)}")
my_string = my_string + " World"
print(f"Modified string (new object): {my_string}")
print(f"ID of new string: {id(my_string)}")
my_tuple = (1, 2, 3)
# my_tuple.append(4) # This would raise an AttributeError
Why Does it Matter?
Understanding mutability is crucial for several reasons: - Function Arguments: When mutable objects are passed to functions, changes made inside the function persist outside. Immutable objects passed to functions cannot be changed by the function itself (only re-assigned within the function's scope). - Dictionary Keys and Set Elements: Only immutable objects can be used as keys in dictionaries or elements in sets, because their hash value must remain constant throughout their lifetime. - Concurrency: Immutable objects are inherently thread-safe as their state cannot change, simplifying concurrent programming. - Memory Efficiency: Repeated modifications to immutable objects can lead to creation of many new objects, potentially impacting performance and memory usage if not handled carefully.
| Feature | Mutable | Immutable |
|---|---|---|
| Change after creation | Yes | No |
| ID remains same after change | Yes | No (new object created) |
| Used as dict key/set element | No | Yes |
| Examples | List, Dict, Set | Int, Float, String, Tuple |