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]\ntpl = (1, 2, 2, 3)\nst = {1, 2, 2, 3} # {1, 2, 3} - duplicates removed
lst = ["a", "b", "c"]\nprint(lst[0]) # "a"\n\ndct = {"name": "Alice", "age": 30}\nprint(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):\n def wrapper(*args, **kwargs):\n print(f"Calling {func.__name__}")\n return func(*args, **kwargs)\n return wrapper\n\n@log\ndef greet(name):\n return f"Hello, {name}"\n\ngreet("Alice") # prints "Calling greet", returns "Hello, Alice"
def func(*args, **kwargs):\n print(args) # (1, 2, 3)\n print(kwargs) # {"a": 4, "b": 5}\n\nfunc(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):\n a, b = 0, 1\n for _ in range(n):\n yield a\n a, b = b, a + b\n\nfor num in fibonacci(10):\n print(num)
List comprehension provides a concise way to create lists. It is more readable and often faster than equivalent for loops.
# Traditional\nsquares = []\nfor x in range(10):\n squares.append(x**2)\n\n# List comprehension\nsquares = [x**2 for x in range(10)]\n\n# With condition\nevens = [x for x in range(10) if x % 2 == 0]
lst = [x**2 for x in range(1000000)] # entire list in memory\ngen = (x**2 for x in range(1000000)) # lazy, one at a time
a = [1, 2, 3]\nb = [1, 2, 3]\nc = a\nprint(a == b) # True (same values)\nprint(a is b) # False (different objects)\nprint(a is c) # True (same object)
import copy\noriginal = [[1, 2], [3, 4]]\nshallow = copy.copy(original)\ndeep = copy.deepcopy(original)\n\nshallow[0][0] = 99 # affects original\ndeep[0][0] = 88 # does not affect original
class MyClass:\n count = 0\n @staticmethod\n def add(a, b): return a + b\n @classmethod\n def increment(cls): cls.count += 1
class Point:\n def __init__(self, x, y): self.x, self.y = x, y\n def __repr__(self): return f"Point({self.x}, {self.y})"\n def __str__(self): return f"({self.x}, {self.y})"
a = [1, 2]\na.append([3, 4]) # [1, 2, [3, 4]]\n\nb = [1, 2]\nb.extend([3, 4]) # [1, 2, 3, 4]
lst = [1, 2, 3, 2]\nlst.remove(2) # [1, 3, 2]\nlst.pop() # returns 2, lst is [1, 3]\ndel 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\n\ndef square(x): return x * x\n\nwith Pool(4) as pool:\n results = pool.map(square, range(10))
# lambda\nsquare = lambda x: x**2\n\n# def\ndef square(x):\n return x**2\n\n# lambda in sorted()\nstudents.sort(key=lambda s: s.age)
nums = [1, 2, 3, 4, 5]\nlist(map(lambda x: x*2, nums)) # [2,4,6,8,10]\nlist(filter(lambda x: x%2==0, nums)) # [2,4]\nfrom functools import reduce\nreduce(lambda a,b: a+b, nums) # 15
class Singleton:\n _instance = None\n def __new__(cls):\n if cls._instance is None:\n cls._instance = super().__new__(cls)\n return cls._instance
class MyClass:\n class_var = 0 # class variable\n def __init__(self, x):\n 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:\n def __init__(self, name):\n self._name = name\n @property\n def name(self): return self._name\n @name.setter\n def name(self, value):\n if not value: raise ValueError("Name required")\n self._name = value\n\nuser.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\n pass\nif value == None: # works but not idiomatic\n pass
try:\n result = risky_operation()\nexcept ValueError as e:\n handle_error(e)\nelse:\n process_result(result)\nfinally:\n cleanup()
if age < 0:\n raise ValueError("Age cannot be negative")\n\nassert 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:\n data = f.read()\n# file is automatically closed\n\n# Custom context manager\nfrom contextlib import contextmanager\n@contextmanager\ndef timer():\n start = time.time()\n yield\n print(f"Elapsed: {time.time() - start}s")
import pickle, json\ndata = {"name": "Alice", "age": 30}\npickle.dumps(data) # binary\njson.dumps(data) # text
for i in range(len(items)):\n print(i, items[i])\n\n# Better with enumerate\nfor i, item in enumerate(items):\n print(i, item)
a = [1, 2, 3]\nb = ["a", "b"]\nlist(zip(a, b)) # [(1,"a"), (2,"b")]\n\nfrom itertools import zip_longest\nlist(zip_longest(a, b, fillvalue="-")) # [(1,"a"), (2,"b"), (3,"-")]
print(any([False, 0, 1])) # True\nprint(all([True, 1, "a"])) # True\nprint(all([True, 0, "a"])) # False
a = [3, 1, 2]\na.sort() # a is now [1, 2, 3]\n\nb = [3, 1, 2]\nc = sorted(b) # b unchanged, c is [1, 2, 3]
s = "a,b,c"\nparts = s.split(",") # ["a", "b", "c"]\njoined = ",".join(parts) # "a,b,c"
__call__ makes an instance callable like a function. Regular methods are called with dot notation.
class Multiplier:\n def __init__(self, factor): self.factor = factor\n def __call__(self, x): return x * self.factor\n\ndouble = Multiplier(2)\nprint(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\nmath.sqrt(16)\n\nfrom math import sqrt\nsqrt(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():\n print("Running main")\n\nif __name__ == "__main__":\n main() # only runs when script is executed directly
def func(a, b, c): print(a, b, c)\n\nargs = [1, 2, 3]\nfunc(*args) # unpacks to func(1, 2, 3)\n\nkwargs = {"a": 1, "b": 2, "c": 3}\nfunc(**kwargs) # unpacks to func(a=1, b=2, c=3)
class Animal: pass\nclass Dog(Animal): pass\nd = Dog()\nprint(type(d) == Dog) # True\nprint(type(d) == Animal) # False\nprint(isinstance(d, Animal)) # True
d = {"name": "Alice"}\nprint(d["age"]) # KeyError\nprint(d.get("age")) # None\nprint(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\nfor item in chain([1,2], [3,4], [5,6]):\n 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\n\n# Regular dict\nd = {}\nd["key"] += 1 # KeyError\n\n# defaultdict\nd = defaultdict(int) # int() returns 0\nd["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\nc = Counter(["a", "b", "a", "c", "a"])\nprint(c) # Counter({"a": 3, "b": 1, "c": 1})\nprint(c.most_common(2)) # [("a", 3), ("b", 1)]
from collections import namedtuple\nPoint = namedtuple("Point", ["x", "y"])\np = Point(1, 2)\n\nfrom dataclasses import dataclass\n@dataclass\nclass Point:\n x: int\n y: int = 0 # default value
name = "Alice"\nage = 30\nprint(f"{name} is {age} years old") # f-string (preferred)\nprint("{} is {} years old".format(name, age))\nprint("%s is %d years old" % (name, age))
Explore 500+ free tutorials across 20+ languages and frameworks.