Python is a high-level, interpreted programming language known for its simplicity and readability. It is widely used in web development, data science, artificial intelligence (AI), machine learning (ML), automation, and scripting.
Feature | Python (Interpreted) | Java (Bytecode + JVM) | C (Compiled) |
---|---|---|---|
Compilation | Compiled to bytecode automatically at runtime | Compiled to bytecode (.class files) |
Compiled to machine code (.exe , .out ) |
Execution | Bytecode runs on Python Virtual Machine (PVM) | Bytecode runs on Java Virtual Machine (JVM) | Runs directly on CPU |
Portability | Runs on any OS with Python installed | Runs on any OS with JVM installed | Needs recompilation for different OS |
Speed | Slower (interpreted at runtime) | Faster than Python | Fastest (direct machine code execution) |
- Python variables don’t need type declarations. Has mutable (list, dict, set) & immutable (numeric, tuple, string) types.
A function is a reusable block of code that performs a specific task. Python provides built-in functions (e.g., print()
, len()
, sum()
) and allows users to create custom functions.
def greet():
print("Hello, World!")
greet() # Output: Hello, World!
✅ Use def
keyword to define a function.
✅ Call the function using its name followed by ()
.
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # Output: Hello, Alice!
✅ Parameters are placeholders inside function definitions.
✅ Arguments are actual values passed to the function.
def greet(name="Guest"):
print(f"Hello, {name}!")
greet() # Output: Hello, Guest!
greet("Bob") # Output: Hello, Bob!
✅ If no argument is provided, the default value is used.
def add(a, b):
return a + b
result = add(5, 3)
print(result) # Output: 8
✅ return
sends a result back to the caller.
✅ Functions without return
return None
by default.
Python allows different types of arguments for flexibility.
Values are assigned based on their position.
def describe_pet(name, species):
print(f"{name} is a {species}")
describe_pet("Buddy", "dog") # Output: Buddy is a dog
Arguments are passed using key=value pairs.
describe_pet(species="cat", name="Whiskers")
# Output: Whiskers is a cat
✅ Order does not matter in keyword arguments.
Used when the number of arguments is unknown.
def sum_numbers(*numbers):
return sum(numbers)
print(sum_numbers(1, 2, 3, 4)) # Output: 10
✅ *args
collects multiple arguments into a tuple.
Used when the number of keyword arguments is unknown.
def display_info(**info):
for key, value in info.items():
print(f"{key}: {value}")
display_info(name="Alice", age=25, city="New York")
✅ **kwargs
collects multiple keyword arguments into a dictionary.
Lambda functions are one-liner functions used for small operations.
# Regular Function
def square(x):
return x * x
# Lambda Function
square = lambda x: x * x
print(square(5)) # Output: 25
✅ No def
, just lambda parameters: expression
.
✅ Used in sorting, filtering, and functional programming.
Functions that take other functions as arguments.
numbers = [1, 2, 3, 4]
squared = list(map(lambda x: x * x, numbers))
print(squared) # Output: [1, 4, 9, 16]
✅ map()
applies a function to each element of a sequence.
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # Output: [2, 4, 6]
✅ filter()
keeps only the True values.
A function inside another function.
def outer():
def inner():
print("Hello from inner function!")
inner()
outer()
✅ Inner function can only be called inside outer function.
A function calls itself for problems like factorial, Fibonacci, etc.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # Output: 120
✅ Recursion helps in divide-and-conquer problems.
Decorators modify function behavior without changing the function itself.
def decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@decorator
def say_hello():
print("Hello!")
say_hello()
✅ @decorator
is syntactic sugar for say_hello = decorator(say_hello)
.
Exception handling in Python allows us to gracefully handle errors instead of abruptly stopping the program. This is done using try-except
blocks.
try:
x = 5 / 0 # Division by zero
except ZeroDivisionError:
print("Cannot divide by zero!")
✅ try
block contains the code that may raise an exception.
✅ except
block handles the error gracefully.
🔹 Output:
Cannot divide by zero!
try:
num = int(input("Enter a number: "))
result = 10 / num
except ZeroDivisionError:
print("You cannot divide by zero!")
except ValueError:
print("Invalid input! Please enter a number.")
✅ Multiple except
blocks can handle different types of exceptions.
try:
x = int("abc") # Causes ValueError
except (ValueError, TypeError) as e:
print(f"An error occurred: {e}")
✅ Use a tuple (Error1, Error2)
to catch multiple exceptions in one block.
try:
num = int(input("Enter a number: "))
result = 10 / num
except ZeroDivisionError:
print("You cannot divide by zero!")
else:
print(f"Result: {result}") # Runs if no error occurs
✅ The else
block executes only if no exception occurs.
try:
file = open("test.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found!")
finally:
print("Execution completed!") # Always runs
✅ The finally
block always executes (even if an exception occurs).
✅ Used for cleanup operations like closing files.
def check_age(age):
if age < 18:
raise ValueError("Age must be 18 or above")
print("You are eligible.")
try:
check_age(16)
except ValueError as e:
print(e)
✅ raise
is used to manually trigger an exception.
class MyException(Exception):
pass # Custom exception class
try:
raise MyException("Something went wrong!")
except MyException as e:
print(e)
✅ You can define your own exception classes by inheriting from Exception
.
Object-Oriented Programming (OOP) is a programming paradigm that models real-world entities using classes and objects. Python supports OOP with key principles like Encapsulation, Inheritance, Polymorphism, and Abstraction.
# Defining a Class
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def display_info(self):
print(f"Car: {self.brand} {self.model}")
# Creating Objects
car1 = Car("Toyota", "Corolla")
car2 = Car("Honda", "Civic")
# Accessing Methods
car1.display_info() # Output: Car: Toyota Corolla
car2.display_info() # Output: Car: Honda Civic
✅ __init__
is the constructor that initializes object attributes.
✅ self
represents the instance of the class.
- Protects data using private variables (
__variable
).
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private variable
def deposit(self, amount):
self.__balance += amount
def get_balance(self):
return self.__balance
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
# print(account.__balance) # Error: Cannot access private variable
✅ Use __variable
to hide attributes.
✅ Provide getter/setter methods for controlled access.
- A class can inherit attributes & methods from another class.
class Animal:
def speak(self):
print("Animal speaks")
class Dog(Animal): # Dog inherits Animal
def speak(self):
print("Dog barks")
dog = Dog()
dog.speak() # Output: Dog barks
✅ Child class (Dog
) inherits from Parent class (Animal
).
✅ Method Overriding allows modifying behavior.
- Method Overriding: Redefining a method in a child class.
class Bird:
def sound(self):
print("Chirping")
class Parrot(Bird):
def sound(self):
print("Parrot is talking")
obj = Parrot()
obj.sound() # Output: Parrot is talking
- Method Overloading is not directly supported but can be simulated using default arguments.
class Math:
def add(self, a, b, c=0):
return a + b + c
m = Math()
print(m.add(2, 3)) # Output: 5
print(m.add(2, 3, 4)) # Output: 9
✅ Same method name but different behaviors.
- Achieved using abstract classes (
ABC
module).
from abc import ABC, abstractmethod
class Vehicle(ABC): # Abstract Class
@abstractmethod
def start(self):
pass # Abstract Method
class Car(Vehicle):
def start(self):
print("Car is starting")
c = Car()
c.start() # Output: Car is starting
✅ Hides implementation details and enforces method implementation in child classes.
class Parent:
def __init__(self):
print("Parent Constructor")
class Child(Parent):
def __init__(self):
super().__init__() # Calls Parent Constructor
print("Child Constructor")
c = Child()
# Output:
# Parent Constructor
# Child Constructor
- Allows customizing built-in operators for objects.
class Book:
def __init__(self, pages):
self.pages = pages
def __add__(self, other):
return self.pages + other.pages # Overload + operator
book1 = Book(300)
book2 = Book(200)
print(book1 + book2) # Output: 500
- A class can inherit from multiple parent classes.
class A:
def show(self):
print("Class A")
class B:
def display(self):
print("Class B")
class C(A, B):
pass
obj = C()
obj.show() # Output: Class A
obj.display() # Output: Class B
✅ Supports multiple inheritance but can lead to complexity (use wisely).