Curated questions covering OOP, decorators, generators, list comprehensions, data structures, Django, Flask, and Python 3 features.
Python is a high-level, interpreted, dynamically-typed language. Key features: simple syntax, extensive standard library, dynamic typing, automatic memory management (garbage collection), supports multiple paradigms (OOP, functional, procedural), and a vast ecosystem (Django, Flask, NumPy, pandas).
lst = [1, 2, 2, 3]
tpl = (1, 2, 2, 3)
st = {1, 2, 2, 3} # {1, 2, 3} - duplicates removed
lst = ["a", "b", "c"]
print(lst[0]) # "a"
dct = {"name": "Alice", "age": 30}
print(dct["name"]) # "Alice"
A decorator wraps a function to extend its behavior without modifying it. Used for logging, authentication, caching, and timing.
def log(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log
def greet(name):
return f"Hello, {name}"
greet("Alice") # prints "Calling greet", returns "Hello, Alice"
def func(*args, **kwargs):
print(args) # (1, 2, 3)
print(kwargs) # {"a": 4, "b": 5}
func(1, 2, 3, a=4, b=5)
A generator yields values one at a time using yield, enabling memory-efficient iteration over large datasets. Generators are lazy - they produce values on demand.
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
for num in fibonacci(10):
print(num)
List comprehension provides a concise way to create lists. It is more readable and often faster than equivalent for loops.
# Traditional
squares = []
for x in range(10):
squares.append(x**2)
# List comprehension
squares = [x**2 for x in range(10)]
# With condition
evens = [x for x in range(10) if x % 2 == 0]
lst = [x**2 for x in range(1000000)] # entire list in memory
gen = (x**2 for x in range(1000000)) # lazy, one at a time
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (same values)
print(a is b) # False (different objects)
print(a is c) # True (same object)
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
shallow[0][0] = 99 # affects original
deep[0][0] = 88 # does not affect original
class MyClass:
count = 0
@staticmethod
def add(a, b): return a + b
@classmethod
def increment(cls): cls.count += 1
class Point:
def __init__(self, x, y): self.x, self.y = x, y
def __repr__(self): return f"Point({self.x}, {self.y})"
def __str__(self): return f"({self.x}, {self.y})"
a = [1, 2]
a.append([3, 4]) # [1, 2, [3, 4]]
b = [1, 2]
b.extend([3, 4]) # [1, 2, 3, 4]
lst = [1, 2, 3, 2]
lst.remove(2) # [1, 3, 2]
lst.pop() # returns 2, lst is [1, 3]
del lst[0] # [3]
xrange() existed in Python 2 and returned a generator. range() in Python 2 returned a list. In Python 3, range() behaves like Python 2 xrange() (returns a lazy range object), and xrange() was removed.
The GIL is a mutex that allows only one thread to execute Python bytecode at a time, even on multi-core systems. This simplifies memory management but limits CPU-bound parallelism. Use multiprocessing for CPU-bound tasks; threading still works for I/O-bound tasks.
from multiprocessing import Pool
def square(x): return x * x
with Pool(4) as pool:
results = pool.map(square, range(10))
# lambda
square = lambda x: x**2
# def
def square(x):
return x**2
# lambda in sorted()
students.sort(key=lambda s: s.age)
nums = [1, 2, 3, 4, 5]
list(map(lambda x: x*2, nums)) # [2,4,6,8,10]
list(filter(lambda x: x%2==0, nums)) # [2,4]
from functools import reduce
reduce(lambda a,b: a+b, nums) # 15
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
class MyClass:
class_var = 0 # class variable
def __init__(self, x):
self.x = x # instance variable
@property allows you to define methods that are accessed like attributes, providing controlled access to private variables without explicit getter/setter calls.
class User:
def __init__(self, name):
self._name = name
@property
def name(self): return self._name
@name.setter
def name(self, value):
if not value: raise ValueError("Name required")
self._name = value
user.name = "Alice" # calls setter
is None checks object identity (recommended). == None checks equality (calls __eq__). Always use is None because None is a singleton - there is only one None object in Python.
if value is None: # correct
pass
if value == None: # works but not idiomatic
pass
try:
result = risky_operation()
except ValueError as e:
handle_error(e)
else:
process_result(result)
finally:
cleanup()
if age < 0:
raise ValueError("Age cannot be negative")
assert age >= 0, "Age must be non-negative"
The with statement ensures proper resource cleanup using context managers (__enter__ and __exit__ methods). Commonly used for file handling, database connections, and locks.
with open("file.txt", "r") as f:
data = f.read()
# file is automatically closed
# Custom context manager
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield
print(f"Elapsed: {time.time() - start}s")
import pickle, json
data = {"name": "Alice", "age": 30}
pickle.dumps(data) # binary
json.dumps(data) # text
for i in range(len(items)):
print(i, items[i])
# Better with enumerate
for i, item in enumerate(items):
print(i, item)
a = [1, 2, 3]
b = ["a", "b"]
list(zip(a, b)) # [(1,"a"), (2,"b")]
from itertools import zip_longest
list(zip_longest(a, b, fillvalue="-")) # [(1,"a"), (2,"b"), (3,"-")]
print(any([False, 0, 1])) # True
print(all([True, 1, "a"])) # True
print(all([True, 0, "a"])) # False
a = [3, 1, 2]
a.sort() # a is now [1, 2, 3]
b = [3, 1, 2]
c = sorted(b) # b unchanged, c is [1, 2, 3]
s = "a,b,c"
parts = s.split(",") # ["a", "b", "c"]
joined = ",".join(parts) # "a,b,c"
__call__ makes an instance callable like a function. Regular methods are called with dot notation.
class Multiplier:
def __init__(self, factor): self.factor = factor
def __call__(self, x): return x * self.factor
double = Multiplier(2)
print(double(5)) # 10 - instance called like a function
__init__.py marks a directory as a Python package, allowing imports from it. It can be empty or contain package initialization code. Python 3.3+ supports namespace packages without __init__.py, but it is still recommended for explicit package definition.
import math
math.sqrt(16)
from math import sqrt
sqrt(16)
Code under if __name__ == "__main__": only runs when the script is executed directly, not when imported as a module. Module-level code (outside the if) runs on both direct execution and import.
def main():
print("Running main")
if __name__ == "__main__":
main() # only runs when script is executed directly
def func(a, b, c): print(a, b, c)
args = [1, 2, 3]
func(*args) # unpacks to func(1, 2, 3)
kwargs = {"a": 1, "b": 2, "c": 3}
func(**kwargs) # unpacks to func(a=1, b=2, c=3)
class Animal: pass
class Dog(Animal): pass
d = Dog()
print(type(d) == Dog) # True
print(type(d) == Animal) # False
print(isinstance(d, Animal)) # True
d = {"name": "Alice"}
print(d["age"]) # KeyError
print(d.get("age")) # None
print(d.get("age", 0)) # 0
itertools.chain(*iterables) creates a lazy iterator that chains multiple iterables without creating a new list in memory. list1 + list2 creates a new list immediately. Use chain() for memory efficiency with large iterables.
from itertools import chain
for item in chain([1,2], [3,4], [5,6]):
print(item) # 1,2,3,4,5,6 - no intermediate list created
defaultdict automatically creates a default value for missing keys using a factory function. Regular dict raises KeyError for missing keys.
from collections import defaultdict
# Regular dict
d = {}
d["key"] += 1 # KeyError
# defaultdict
d = defaultdict(int) # int() returns 0
d["key"] += 1 # works, d["key"] is now 1
Counter is a dict subclass for counting hashable objects. It provides convenient methods like most_common(), elements(), and arithmetic operations on counts.
from collections import Counter
c = Counter(["a", "b", "a", "c", "a"])
print(c) # Counter({"a": 3, "b": 1, "c": 1})
print(c.most_common(2)) # [("a", 3), ("b", 1)]
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(1, 2)
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int = 0 # default value
name = "Alice"
age = 30
print(f"{name} is {age} years old") # f-string (preferred)
print("{} is {} years old".format(name, age))
print("%s is %d years old" % (name, age))
Explore 500+ free tutorials across 20+ languages and frameworks.