Zhang Jian's Blog

Python Interview Common Questions and Answers (1-hour Quick View)

Author: Zhang Jian Posted 2 days ago 21 min read

I. Python Basics

1. Variables and Data Types

  • Dynamic Typing: Variable types are determined at runtime, and their types can be changed.

2. List vs. Tuple

Feature List Tuple
Mutability Mutable Immutable
Syntax [] ()
As Dict Key Cannot Can

3. Dictionary and Set

# Dictionary operations
d = {'a': 1}
d.get('b', 0)  # Safe access, returns 0 if key 'b' does not exist

# Set operations
s = {1,2,3}
s.add(4)  # Add element

II. Core Concepts

1. Deep vs. Shallow Copy

import copy
lst = [1, [2,3]]
shallow = lst.copy()        # Shallow copy
deep = copy.deepcopy(lst)   # Deep copy

2. Function Argument Pitfalls

# Incorrect example
def func(a, lst=[]):  # Default list will persist across calls
    lst.append(a)
    return lst


print(func(1))  # Outputs [1]
print(func(2))  # Outputs [1, 2] instead of the expected [2]
print(func(3))  # Outputs [1, 2, 3] instead of the expected [3]
# Correct example
def func(a, lst=None):
    if lst is None:
        lst = []  # A new list is created for each call
    lst.append(a)
    return lst


print(func(1))  # Outputs [1]
print(func(2))  # Outputs [2]
print(func(3, [1, 2]))  # Outputs [1, 2, 3]

The root cause of this issue is that default arguments in Python are evaluated once when the function is defined, not each time the function is called.

3. Decorator Template

@decorator1
def my_function(a, b):
    return a + b

# Equivalent to:
# my_function = decorator1(my_function)

Example: Logger Decorator

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function: {func.__name__}, Arguments: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"Function {func.__name__} returned: {result}")
        return result
    return wrapper

@logger
def add(a, b):
    return a + b

add(3, 5)
# Output:
# Calling function: add, Arguments: (3, 5), {}
# Function add returned: 8

III. Object-Oriented Programming (OOP)

Class Structure Example

class MyClass:
    class_var = 0  # Class variable

    def __init__(self, x):
        self.x = x  # Instance variable

    @staticmethod
    def static_method():
        pass

    @classmethod
    def class_method(cls):
        pass

IV. Common Pitfalls

1. Loop Variable Leakage (Closure Issue)

funcs = [lambda x: x + i for i in range(3)]
# The 'i' in all lambda functions will be its final value, 2
print([f(0) for f in funcs])  # Outputs [2,2,2], not the expected [0,1,2]

Why does this happen?

  1. Closure Characteristics:

    • The lambda function creates a closure, which captures the variable i itself, not its value at the time of creation.
    • Python closures are "late binding," meaning they look up the variable's value when the function is called, not when it's defined.
  2. Loop Execution Process:

    • When the list comprehension executes, the value of i becomes 0, then 1, then 2.
    • However, the lambda functions are not executed immediately; they are only created.
    • All lambda functions refer to the same variable i.
  3. Situation at Actual Call Time:

    • After the loop finishes, the final value of i is 2.
    • When we call f(0), all lambda functions look up the current value of i, which is 2.
    • Therefore, each function calculates 0 + 2, resulting in 2.

How to solve this issue?

Use a factory function (a form of closure).

def make_adder(i):
    return lambda x: x + i

funcs = [make_adder(i) for i in range(3)]
print([f(0) for f in funcs])  # Outputs [0,1,2]
  • By using a factory function make_adder, a new scope is created for each call.
  • Each lambda captures the i from its respective (different) scope.

Quick Recap / Cheat Sheet

  1. Mutable objects: list/dict/set
  2. Immutable objects: int/float/str/tuple
  3. Shallow copy only copies the outermost layer.
  4. Decorators are essentially nested functions (or higher-order functions).
  5. GIL (Global Interpreter Lock) is a mutex that ensures only one thread executes Python bytecode at any given time.