Key Python Concepts Explained
Key Python Concepts Explained
u ry
r.s
de
co
Basic Python Concepts:
[Link] 2 and Python 3
:@
1
Feature Python 2 Python 3
a
integers
ry
All new development is for Python
Libraries Some libraries support Python 2 only
3
u
Example:
r.s
# Python 2 (Not supported anymore)
de
# Python 3
co
print("Hello, World") # Requires parentheses
:@
2. What Are Python Decorators and How Do They Work?
A decorator is a function that modifies the behavior of another function without changing
its structure. It is often used for logging, authentication, and timing functions.
m
defdecorator_function(original_function):
ra
def wrapper_function():
ag
print(f"Executing {original_function.__name__}")
return original_function()
st
return wrapper_function
In
def say_hello():
print("Hello!")
2
say_hello()
Output:
Executing say_hello
Hello!
a
3. Difference Between a List and a Tuple
ry
Feature List (list) Tuple (tuple)
u
Mutability Mutable (can be changed) Immutable (cannot be changed)
r.s
Performance Slower due to dynamic resizing Faster due to fixed size
Use Case
de
When data needs modification When data should remain constant
Example:
co
# List (Mutable)
my_list = [1, 2, 3]
:@
my_list[0] = 10 # Allowed
print(my_list) # [10, 2, 3]
am
# Tuple (Immutable)
my_tuple = (1, 2, 3)
gr
# my_tuple[0] = 10 # Error: TypeError: 'tuple' object does not support item assignment
Lists are preferred when frequent modifications are needed, whereas tuples are used for
a
fixed data.
st
3
A lambda function is an anonymous function in Python that can have multiple arguments
but only one expression.
Syntax:
a
Example:
ry
# Regular Function
def square(x):
u
return x * x
r.s
de
print(square(5)) # Output: 25
# Lambda Equivalent
co
square_lambda = lambda x: x * x
print(square_lambda(5)) # Output: 25
:@
Lambda functions are often used in map(), filter(), and sorted() operations.
Normal Function?
st
A generator is a special type of iterator that produces values lazily, meaning it generates
In
values on the fly instead of storing them in memory. It is defined using the yield keyword.
Key Differences Between Generators and Normal Functions
4
Feature Normal Function Generator Function
a
Return
Uses return Uses yield
ry
Statement
u
# Normal function (stores all values)
r.s
defsquare_list(n):
result = []
for i in range(n):
[Link](i * i) de
co
return result
:@
defsquare_generator(n):
foriin range(n):
yield i * i
a gr
gen=square_generator(5)
st
print(next(gen)) # Output: 0
print(next(gen)) # Output: 1
In
print(next(gen)) # Output: 4
5
Using yield allows generators to be memory efficient when dealing with large datasets.
a
Python uses automatic memory management and relies on a garbage collector to free
up unused memory. The key components of Python’s memory management system are:
ry
Key Aspects of Python Memory Management
u
1. Reference Counting:
r.s
o Every object in Python has a reference count (i.e., the number of variables
pointing to it).
o When the reference count drops to zero, Python automatically deletes the
de
object.
Example:
co
import sys
:@
a = [1, 2, 3]
print([Link](a)) # Output: 2 (one reference from 'a' and another from function call)
print([Link](a)) # Output: 3
gr
print([Link](b)) # Output: 2
a
2. Garbage Collection:
st
Example:
import gc
6
Using yield allows generators to be memory efficient when dealing with large datasets.
a
Python uses automatic memory management and relies on a garbage collector to free
up unused memory. The key components of Python’s memory management system are:
ry
Key Aspects of Python Memory Management
u
1. Reference Counting:
r.s
o Every object in Python has a reference count (i.e., the number of variables
pointing to it).
o When the reference count drops to zero, Python automatically deletes the
de
object.
Example:
co
import sys
:@
a = [1, 2, 3]
print([Link](a)) # Output: 2 (one reference from 'a' and another from function call)
print([Link](a)) # Output: 3
gr
print([Link](b)) # Output: 2
a
2. Garbage Collection:
st
Example:
import gc
7
[Link]() # Force garbage collection
a
Use generators instead of lists to handle large datasets efficiently.
ry
•
u
r.s
7. What Is the Difference Between deepcopy() and copy() in
Python?
Example:
import copy
gr
# Shallow Copy
In
shallow_copy = [Link](original_list)
8
# Deep Copy
deep_copy = [Link](original_list)
a
ry
# Modify the nested list
original_list[0][0] = 99
u
r.s
print(shallow_copy) # [[99, 2, 3], [4, 5, 6]] (affected)
de
When to Use?
• Use shallow copy when the object has immutable elements or when deep copy
isn't necessary.
co
•
Use deep copy when working with nested data structures that require full
duplication.
:@
Module:
• A module is a single Python file (.py) that contains functions, classes, and variables.
python
CopyEdit
import math_utils
9
print(math_utils.add(3, 5)) # Output: 8
Package:
a
•
It helps in structuring large codebases.
ry
Example of a Package:
u
markdown
CopyEdit
r.s
my_package/
│── __init__.py
de
│── [Link]
│── [Link]
co
Using a Package:
The with statement is used for resource management (e.g., file handling, database
connections) and ensures that resources are automatically closed after execution.
In
10
• Prevents Memory Leaks: Avoids forgetting to release resources.
a
file = open("[Link]", "w")
ry
try:
[Link]("Hello, World!")
u
finally:
r.s
[Link]() # Must be manually closed
de
with open("[Link]", "w") as file:
11
Feature Dictionary (dict) List (list)
Order (Python
Maintains insertion order Maintains order
3.7+)
a
Keys must be unique and
Key/Index Type Indices are integers starting from 0
ry
immutable
u
Example of a Dictionary
r.s
employee = {
"name": "Alice",
de
"age": 30,
Example of a List
When to Use?
gr
• Use a dictionary when you need quick lookups based on unique identifiers (keys).
• Use a list when order matters and indexed access is required.
a
st
s = "Goldman"
reversed_s = s[::-1]
12
print(reversed_s) # Output: "namdloG"
s = "Sachs"
a
reversed_s = "".join(reversed(s))
ry
print(reversed_s) # Output: "shcaS"
3. Using a Loop
u
s = "Python"
r.s
reversed_s = ""
for char in s:
de
reversed_s = char + reversed_s # Adding characters in reverse order
defreverse_string(s):
:@
iflen(s) == 0:
return s
Best Approach?
gr
• String slicing ([::-1]) is the most efficient method because it is optimized at the C
level.
a
•
reversed() with join() is also efficient but slightly less readable.
st
• Loops and recursion are less efficient and mainly used for demonstration
purposes.
In
13
Python provides two comparison operators:
==
Compares values x == y checks if values of x and y are the same
a
(Equality)
ry
Compares memory x is y checks if x and y refer to the same object in
is (Identity)
locations memory
u
Example 1: Using == (Value Comparison)
r.s
a = [1, 2, 3]
b = [1, 2, 3]
de
print(a == b) # True (because values are the same) print(a is b) #
co
False (because they are different objects in memory) Example 2:
print(x is y) # True (Python optimizes immutable objects like strings and caches them)
[Link](40)
print(list2) # Output: [10, 20, 30, 40] (because both refer to the same object)
Key Takeaways
14
• Use == when comparing values.
• Use is when checking whether two variables reference the same object.
a
13. How Would You Remove Duplicates from a List in Python?
ry
There are multiple ways to remove duplicates:
u
nums = [4, 2, 7, 4, 2, 8]
r.s
unique_nums = list(set(nums))
de
• ⚠ Limitation: Order is not preserved.
2. Using [Link]() (Preserves Order)
co
nums = [4, 2, 7, 4, 2, 8]
unique_nums = list([Link](nums))
:@
nums = [4, 2, 7, 4, 2, 8]
unique_nums = []
gr
unique_nums.append(num)
st
nums = [4, 2, 7, 4, 2, 8]
15
seen = set()
unique_nums = [num for num in nums if num not in seen and not [Link](num)]
a
• Advantage: Preserves order and is efficient.
ry
Best Approach?
u
• Use [Link]() or list comprehension for order preservation.
r.s
14. How Do You Implement a Stack or Queue in Python?
de
A stack follows the Last-In-First-Out (LIFO) principle, while a queue follows the First-In-
First-Out (FIFO) principle. Both can be implemented in Python using lists or the
[Link] module.
co
StackImplementation (LIFO)
classStack:
def__init__(self):
am
[Link] = []
defpush(self, item):
gr
defpop(self):
st
ifnot self.is_empty():
In
16
defpeek(self):
ifnot self.is_empty():
a
return "Stack is empty"
ry
defis_empty(self):
u
return len([Link]) == 0
r.s
#Usage s=Stack()
de
[Link](10) [Link](20)
print([Link]()) # Output: 20 co
print([Link]()) # Output: 10
:@
• Push: O(1)
• Pop: O(1)
am
class Queue:
st
def __init__(self):
[Link] = deque()
In
17
defdequeue(self):
ifnot self.is_empty():
a
return [Link]() # Remove from the front
ry
return "Queue is empty"
u
defis_empty(self):
r.s
return len([Link]) == 0
de
#Usage q=Queue()
[Link](10) [Link](20) co
print([Link]()) # Output: 10
:@
• Enqueue: O(1)
• Dequeue: O(1)
am
Key Takeaway:
pop(0)).
a
15. How Would You Find the Most Common Element in a List?
st
18
nums = [1, 3, 3, 3, 2, 2, 4]
counter = Counter(nums)
a
print(most_common) # Output: 3
ry
• Counter(nums).most_common(1) returns a list of tuples [(3, 3)], so we extract the
first element.
u
2. Using max() with count() (Inefficient for Large Lists)
nums = [1, 3, 3, 3, 2, 2, 4]
r.s
most_common = max(set(nums), key=[Link])
print(most_common) # Output: 3
de
• Time Complexity: O(n²) because count() iterates over the list multiple times.
freq = {}
:@
print(most_common) # Output: 3
Key Takeaway:
st
19
16. What Is the Time Complexity of the Built-in sorted() Function
in Python?
Thebuilt-in sorted() function in PythonusesTimsort,ahybridsortingalgorithmcombining
Merge Sort and Insertion Sort.
a
Time Complexity of sorted()
ry
Case Complexity
u
Best Case (Nearly Sorted) O(n)
r.s
Average Case O(n log n)
de
Example Usage
arr = [4, 2, 7, 1, 9]
co
sorted_arr = sorted(arr) # Returns a new sorted list
Insertion Sort improves performance for nearly sorted data (O(n) in the best case).
Custom Sorting
Key Takeaway:
In
20
17. How Would You Implement a Binary Search Algorithm in
Python?
BinarySearchisanefficientsearch [Link]
divide-and-conquerapproach.
a
BinarySearchAlgorithm(Iterative)
ry
defbinary_search(arr,target):
u
left,right = 0, len(arr) -1
r.s
whileleft <= right:
de
mid=(left+right)//2# Find the middle index
if arr[mid] == target:
co
returnmid#Targetfound
else:
am
return-1#Targetnotfound
a gr
# Example
st
target = 7
In
BinarySearchAlgorithm(Recursive)
21
if left> right:
a
mid=(left+right) // 2
ry
ifarr[mid]==target:
u
return mid
r.s
elifarr[mid]<target:
de
else:
arr=[1,3,5,7,9, 11]
target = 5
Time Complexity
Case Complexity
gr
Key Takeaway:
In
22
18. What Is the Purpose of the collections Module in Python?
The collectionsmodule provides specializedcontainerdatatypesbeyondPython’sbuilt-in
types (list, tuple, dict, set). These are optimized for performance and memory efficiency.
Key Data Structures in collections
a
ry
1. Counter – Counts elements in an iterable.
u
3. OrderedDict – A dictionary that maintains the order of keys.
r.s
4. deque – A double-ended queue for fast append/pop.
de
Example 1: Counter – Counting Elements co
from collections import Counter
:@
nums = [1, 2, 2, 3, 3, 3, 4]
counter = Counter(nums)
23
word_count[word] += 1 # No need to check if key exists
a
• Use Case: Grouping data without handling missing keys.
ry
Example 3: OrderedDict – Preserving Insertion Order
u
from collections import OrderedDict
r.s
ordered_dict = OrderedDict()
de
ordered_dict["a"] = 1
ordered_dict["b"] = 2 co
ordered_dict["c"] = 3
:@
dq = deque([1, 2, 3])
a
24
• Use Case: Implementing queues/stacks efficiently.
a
from collections import namedtuple
ry
Person = namedtuple("Person", ["name", "age"])
u
p = Person("Alice", 30)
r.s
print([Link]) # Output: Alice
de
print([Link]) # Output: 30
Basic Syntax:
gr
•
condition (optional) → A filter for elements.
In
25
print(squares) # Output: [0, 1, 4, 9, 16]
Equivalent to:
squares = []
a
for x in range(5):
ry
[Link](x**2)
u
Example 2: Filtering Even Numbers
r.s
nums = [1, 2, 3, 4, 5, 6]
de
print(evens) # Output: [2, 4, 6]
print(matrix)
nums = [1, 2, 3, 4, 5]
a
26
flat_list = [num for sublist in nested_list for num in sublist]
a
Key Takeaway:
List comprehensions are faster and more readable than traditional loops.
u ry
Conclusion
r.s
• collections module provides efficient data structures for counting, ordering,
queuing, and structuring data.
• List comprehensions simplify list creation and filtering, making code more
de
Pythonic.
properties and behaviors (methods) from another class. This promotes code reusability
and modularity.
Types of Inheritance in Python
am
class Animal:
def speak(self):
27
class Dog(Animal): # Dog inherits from Animal
def speak(self):
a
return "Bark"
ry
dog= Dog()
u
print([Link]()) # Output: Bark
r.s
• The Dog class overrides the speak() method from Animal.
de
Example 2: Multiple Inheritance
class Engine: co
def start(self):
class Wheels:
def roll(self):
am
pass
a
st
my_car = Car()
28
Example 3: Multilevel Inheritance
class Animal:
def breathe(self):
a
return "Breathing"
ry
class Mammal(Animal):
u
def has_fur(self):
r.s
return "I have fur"
de
class Dog(Mammal):
def bark(self): co
return "Bark"
:@
dog= Dog()
Key Takeaway:
gr
Inheritance eliminates code duplication and follows the DRY principle (Don't Repeat
Yourself).
a
st
The __init__ method is a constructor in Python that initializes an object when a class is
instantiated. It allows passing values at the time of object creation.
29
Example: Using __init__ for Initialization
classPerson:
a
[Link] = name # Instance variable
ry
[Link] = age
u
defgreet(self):
r.s
return f"Hi, I'm {[Link]} and I'm {[Link]} years old."
de
p1=Person("Alice", 25)
classCar:
am
def__init__(self, brand="Toyota"):
[Link] = brand
gr
car1=Car("BMW")
a
30
Key Takeaway:
__init__ automates object initialization, improving efficiency and code clarity.
a
ry
Python classes have two types of variables:
u
2. Class Variables – Defined inside the class but shared among all objects.
r.s
Example: Difference Between Instance and Class Variables
de
class Employee:
[Link] = salary
am
31
[Link] = 75000 # Changing instance variable (only for emp1)
80000
a
• company is a class variable → shared across all instances.
ry
• name and salary are instance variables → unique for each object.
u
Key Differences
r.s
Feature Class Variable Instance Variable
de
Shared? Yes (same across all instances) No (unique per instance)
Key Takeaway:
Conclusion
Class variables are shared across instances, while instance variables are unique
per object.
st
Polymorphism means "many forms" and refers to the ability of different objects to be
treated as instances of the same class through a shared interface. In Python,
polymorphism allows methods in different classes to share the same name but behave
differently based on the object calling them.
32
Example 1: Polymorphism in Methods
class Dog:
a
def speak(self):
ry
return "Bark"
u
class Cat:
r.s
def speak(self):
return "Meow"
de
#Function that demonstrates polymorphism co
defmake_sound(animal):
print([Link]())
:@
dog = Dog()
cat = Cat()
am
33
Python’s len() function behaves differently depending on the data type.
a
class Animal:
ry
def make_sound(self):
u
r.s
class Dog(Animal):
de
return "Bark"
co
dog= Dog()
Key Takeaway
@classmethod?
a
Both @staticmethod and @classmethod modify the behavior of methods, but they serve
different purposes.
st
1 .@staticmethod
In
• It belongs to the class but does not access or modify class attributes.
34
Example: @staticmethod
class MathOperations:
@staticmethod
a
def add(x, y):
ry
return x + y # No need to access class attributes
u
print([Link](3, 5)) # Output: 8
r.s
Here, add() is just a utility function that does not interact with the class.
de
2.@classmethod
• Used when a method needs to work on the class itself, not instances.
:@
Example: @classmethod
class Employee:
company = "Google"
am
@classmethod
gr
Employee.change_company("Microsoft")
Key Differences:
35
Feature @staticmethod @classmethod
a
Modifies class variables? No Yes
ry
Called on Class or instance Class or instance
u
Use case Utility functions Class-wide modifications
r.s
Key Takeaway
de
• Use @classmethod when modifying class-level attributes.
co
25. What is Multiple Inheritance in Python, and How Does It
Work?
:@
Multiple Inheritance allows a class to inherit from more than one parent class.
Python supports multiple inheritance, unlike some languages (e.g., Java), which
only support single inheritance.
am
class Engine:
def start(self):
a
class Wheels:
In
def roll(self):
36
class Car(Engine, Wheels): # Multiple inheritance
pass
a
my_car = Car()
ry
print(my_car.start()) # Output: Engine started
u
Here, Car inherits from both Engine and Wheels, gaining access to both methods.
r.s
Example 2: Method Resolution Order (MRO) in Multiple Inheritance
de
If multiple parent classes have a method with the same name, Python follows MRO
(Method Resolution Order) to determine which method to execute.
class A:
co
def show(self):
return "A"
:@
class B(A):
am
def show(self):
return "B"
gr
class C(A):
def show(self):
a
return "C"
st
In
pass
37
obj = D()
print([Link]()) # Output: B
Python follows the order defined in the class declaration (D(B, C)). It checks B first
before C.
a
ry
How to Check MRO?
u
print([Link]())
r.s
# Output: [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class
'__main__.A'>, <class 'object'>]
This shows the order in which Python searches for methods when calling
de
[Link]().
co
Key Takeaway
• MRO (Method Resolution Order) decides which method is executed when there's a
conflict.
order.
Final Summary
gr
@classmethod
Allows inheriting from multiple classes, follows MRO for
method resolution.
Multiple Inheritance
38
Advanced Python:
[Link]’sBuilt-in Data Types?
a
Pythonprovidesseveral built-in datatypescategorizedasnumeric, sequence, set,
ry
mapping, and boolean types.
u
Numeric Types
r.s
•
•
float → Floating-point numbers
de
• complex → Complex numbers
Example:
a = 10 # int
co
b = 3.14 # float
c = 2 + 3j # complex
:@
Sequence Types
•
tuple → Ordered, immutable collection
•
range → Represents a sequence of numbers
Example:
gr
rng = range(1, 5) # 1, 2, 3, 4
st
Set Types
In
Example:
39
s={1, 2, 3, 3} # {1, 2, 3}
Mapping Type
a
• dict → Key-value pairs
ry
Example:
u
Boolean Type
r.s
• bool → True or False
Example:
de
x = True
y = False
co
Binary Data Types
Example:
b = b"hello" # Bytes
Key Takeaway: Python has diverse data types for different needs, from numbers and
sequences to mappings and binary data.
gr
27. What is the Global Interpreter Lock (GIL) in Python, and How
a
The Global Interpreter Lock (GIL) is a mutex that allows only one thread to execute
Python bytecode at a time in CPython.
In
40
• Ensures thread safety.
a
• I/O-bound tasks (e.g., file I/O, network calls) are not affected much.
ry
Example: GIL Effect on Multi-threading
import threading
u
r.s
defcount():
for_ in range(1000000):
de
pass # CPU-intensive task
co
t1=[Link](target=count)
t2=[Link](target=count)
:@
[Link]()
[Link]()
am
[Link]()
[Link]()
gr
Even with two threads, execution time does not improve due to GIL.
a
p1 = Process(target=count)
p2 = Process(target=count)
41
[Link]()
[Link]()
a
ry
[Link]()
[Link]()
u
Use Jython or PyPy (alternative Python implementations without GIL).
r.s
Use async programming for I/O-bound tasks.
Key Takeaway: GIL limits multi-threading for CPU-bound tasks but not for I/O-bound
de
tasks. Use multiprocessing for parallelism.
map() Function
Example:
nums = [1, 2, 3, 4]
filter() Function
In
Example:
42
nums = [1, 2, 3, 4, 5, 6]
a
Used for selecting elements based on a condition.
ry
Key Differences:
u
Feature map() filter()
r.s
Purpose Transforms elements Filters elements
Returns New iterable with modified values New iterable with filtered values
de
Function Output Function returns any valueco Function must return True/False
Key Takeaway: Use map() for transformation, and filter() for selection.
:@
Basic Structure
try:
gr
except ZeroDivisionError as e:
st
print(f"Error: {e}")
finally:
In
43
Handling Multiple Exceptions
try:
x = int("abc") # ValueError
a
except (ZeroDivisionError, ValueError) as e:
ry
print(f"Handled error: {e}")
u
Using else with try/except
r.s
try:
x = 10 / 2 # No error
de
except ZeroDivisionError:
Key Takeaway: Always handle exceptions properly to prevent crashes. Use finally for
cleanup actions.
am
import threading
a
st
defprint_numbers():
foriin range(5):
In
print(i)
t1=[Link](target=print_numbers)
44
[Link]()
[Link]()
a
ry
[Link]-processing (for CPU-bound tasks)
u
r.s
def compute():
print(sum(range(1000000)))
de
p= Process(target=compute)
co
[Link]()
[Link]()
:@
import asyncio
gr
print("Task started")
a
print("Task finished")
In
[Link](task())
Best for handling thousands of concurrent tasks efficiently (e.g., API requests, DB
queries).
45
Final Summary
a
Python Data
Includes numeric, sequence, set, mapping, and boolean types.
ry
Types
GIL
Allows only one thread at a time; use multiprocessing to bypass it.
u
map() vs filter()
map() transforms elements; filter() selects elements.
r.s
Exception
Handling Use try/except/finally to prevent crashes.
de
Use threading for I/O-bound, multiprocessing for CPU-bound, and
Concurrency
async for high-performance tasks.
co
:@
am
a gr
st
In
46