Python Core Concepts - Part 2 (Questions 27-52)

Continue your Python mastery with advanced topics including closures, file handling, logging, threading, async programming, and more essential concepts for interviews.

🔐 Closures & Advanced Functions

  • 27. Explain closures in Python
    A closure is a nested function that remembers variables from its enclosing scope, even after the outer function has finished executing.
    def outer(x):
        def inner(y):
            return x + y
        return inner
    
    add_5 = outer(5)
    print(add_5(3))  # Output: 8
  • 28. Explain context managers
    They manage resources using __enter__ and __exit__ methods.
    with open('file.txt') as f:
        print(f.read())
    # File is automatically closed after the block

📁 File Handling

  • 29. What are Python's file modes?
    'r' - Read'w' - Write'a' - Append'rb'/'wb' - Binary'r+' - Read/Write
  • 30. How is logging different from print()?
    print(): Temporary, console-only output for debugging during development
    logging: Structured, persistent, configurable system with levels (DEBUG, INFO, WARNING, ERROR, CRITICAL)
    import logging
    
    logging.basicConfig(filename='app.log', level=logging.INFO)
    logging.info("App started")  # Logged in file
    print("App started")          # Only printed to console
  • 31. How does logging work in Python?
    The logging module provides a flexible framework for emitting log messages, supporting different severities and handlers (file, console, network).

⚡ Threading & Multiprocessing

  • 32. Differentiate between threading and multiprocessing
    Threading: Multiple threads within a single process, sharing memory, but limited by the GIL for CPU-bound tasks. Good for I/O-bound operations.
    Multiprocessing: Multiple processes, each with its own interpreter and memory space, bypassing the GIL for true parallelism. Good for CPU-bound operations.
  • 33. When would you use asyncio?
    Used for concurrent, non-blocking I/O with async and await, ideal for I/O-bound and high-level structured network code where tasks are cooperative.
    import asyncio
    
    async def fetch_data():
        print("Start fetching")
        await asyncio.sleep(2)  # Non-blocking
        print("Done fetching")
    
    asyncio.run(fetch_data())

📦 Packages & Modules

  • 34. What are Python packages and modules?
    Module: A file with Python code (.py file)
    Package: A collection of modules with __init__.py file
  • 35. How to manage environments?
    Use venv or virtualenv for isolated dependencies.
    # Create
    python3 -m venv env
    
    # Activate (Linux/Mac)
    source env/bin/activate
    
    # Activate (Windows)
    env\Scripts\activate
    
    # Deactivate
    deactivate
  • 36. What is PEP8?
    Python's official style guide for clean, readable code including indentation, naming conventions, and line length recommendations.
  • 37. How do you perform asynchronous file I/O in Python?
    While Python's built-in open() is blocking, libraries like aiofiles or using thread pools (e.g., ThreadPoolExecutor) with asyncio can achieve asynchronous file I/O.

🔤 Type Hints & Data Serialization

  • 38. What are type hints?
    Type hints provide expected data types of variables, function parameters, and return values, improving code readability and enabling static type checking.
    def add(x: int, y: int) -> int:
        return x + y
    
    name: str = "John"
    age: int = 25
  • 39. How to handle JSON in Python?
    Use the json module with json.load() and json.dump().
    import json
    
    # Load JSON from file
    with open('data.json') as f:
        data = json.load(f)
    
    # Dump JSON to file
    with open('output.json', 'w') as f:
        json.dump({'name': 'John'}, f)
  • 40. What is pickling and unpickling in Python?
    Pickling: Serializing Python objects into a byte stream (for saving to file or transferring)
    Unpickling: Deserializing — converting the byte stream back into an object
    ⚠ Caution: Never unpickle untrusted data — it can execute arbitrary code.
    import pickle
    
    data = {'a': 1, 'b': 2}
    # Pickle (serialize)
    pickle.dump(data, open('f.pkl', 'wb'))
    
    # Unpickle (deserialize)
    obj = pickle.load(open('f.pkl', 'rb'))

🌐 API Calls

  • 41. How to make API calls in Python?
    Use the requests library for simple API calls.
    import requests
    
    response = requests.get('https://api.github.com')
    data = response.json()
    print(response.status_code)
  • 42. Give an example of an async API call in Python
    import asyncio
    import httpx  # A modern, async-friendly HTTP client
    
    async def fetch_data(url):
        async with httpx.AsyncClient() as client:
            response = await client.get(url)
            return response.json()
    
    async def main():
        data = await fetch_data("https://api.example.com/data")
        print(data)
    
    if __name__ == "__main__":
        asyncio.run(main())

💾 Copy & Memory Operations

  • 43. What is the difference between shallow and deep copy?
    Shallow copy (copy.copy()): Only copies the top-level object — nested objects are still referenced
    Deep copy (copy.deepcopy()): Creates a completely independent clone — does not keep any reference to the original's nested objects
    import copy
    
    original = [[1, 2], [3, 4]]
    shallow = copy.copy(original)
    deep = copy.deepcopy(original)
    
    original[0][0] = 'X'
    print(shallow)  # [['X', 2], [3, 4]] - affected
    print(deep)     # [[1, 2], [3, 4]] - not affected
  • 44. How do you handle large files efficiently?
    Use generators or process line by line to avoid loading entire file into memory.
    with open('large_file.txt') as f:
        for line in f:  # Process one line at a time
            process(line)
  • 45. How do you manage packages and modules in Python?
    Packages are directories containing modules. Modules are single Python files. pip is used to install, upgrade, and remove packages from PyPI.

🔧 Advanced Concepts

  • 46. What is a generator expression?
    Similar to list comprehensions but uses parentheses () instead of square brackets, creating an iterator that yields items one by one, consuming less memory.
    # List comprehension - creates full list
    squares_list = [x**2 for x in range(10000)]
    
    # Generator expression - yields one at a time
    squares_gen = (x**2 for x in range(10000))
  • 47. Explain the with statement in Python
    The with statement simplifies resource management (like file I/O or locking) by ensuring that a resource is properly acquired before a block of code is executed and released afterwards, even if errors occur.
  • 49. What is monkey patching in Python?
    Monkey patching is modifying or extending a class or module at runtime without changing its original source code. Use case: quick fixes or mocking functions during testing.
    class Test:
        def greet(self):
            print("Hello")
    
    def new_greet():
        print("Hi, patched!")
    
    Test.greet = new_greet  # Monkey patching
    obj = Test()
    obj.greet()  # Output: Hi, patched!
    ⚠ Avoid in production — it can cause unpredictable behavior.
  • 50. What is duck typing in Python?
    Duck typing means an object's suitability is determined by behavior (methods/attributes) rather than its actual type. "If it quacks like a duck, it's a duck."
    class Duck:
        def quack(self):
            print("Quack!")
    
    class Person:
        def quack(self):
            print("I'm quacking like a duck!")
    
    def make_it_quack(obj):
        obj.quack()  # Works with any object that has quack()
    
    make_it_quack(Duck())
    make_it_quack(Person())  # Both work!
  • 51. What is method overriding?
    When a child class defines a method with the same name as a parent class method to modify behavior.
    class Parent:
        def show(self):
            print("Parent")
    
    class Child(Parent):
        def show(self):
            print("Child")
    
    Child().show()  # Output: Child
  • 52. What is method overloading?
    Python doesn't support true overloading (like Java); the latest method definition overrides previous ones. However, you can mimic it with default arguments.
    class Math:
        def add(self, a, b=0, c=0):
            return a + b + c
    
    m = Math()
    print(m.add(2))        # 2
    print(m.add(2, 3))     # 5
    print(m.add(2, 3, 4))  # 9