乐闻世界logo
搜索文章和话题

What are closures in Python? How to use them?

2月21日 17:10

Python Closures Explained

Basic Concepts of Closures

Closures are an important concept in Python. A closure is a function object that retains access to variables from its defining scope even when executed outside that scope.

Basic Structure of Closures

python
def outer_function(x): """Outer function""" def inner_function(y): """Inner function""" return x + y return inner_function # Create closure closure = outer_function(10) # Call closure print(closure(5)) # 15 print(closure(20)) # 30

Three Conditions for Closures

  1. Must have a nested function (inner function)
  2. Inner function must reference variables from the outer function
  3. Outer function must return this inner function
python
def make_multiplier(factor): """Create multiplication closure""" def multiply(number): return number * factor return multiply # Create different multipliers double = make_multiplier(2) triple = make_multiplier(3) print(double(5)) # 10 print(triple(5)) # 15

How Closures Work

Variable Scope

python
def outer(): x = 10 def inner(): # Inner function can access outer function's variables print(f"Inner function accessing x: {x}") return x return inner closure = outer() print(closure()) # Inner function accessing x: 10, 10

Variable Lifecycle

python
def counter(): """Counter closure""" count = 0 def increment(): nonlocal count count += 1 return count return increment # Create counter my_counter = counter() print(my_counter()) # 1 print(my_counter()) # 2 print(my_counter()) # 3 # Create another counter another_counter = counter() print(another_counter()) # 1

closure Attribute

python
def outer(x): def inner(y): return x + y return inner closure = outer(10) # View closure variables print(closure.__closure__) # (<cell at 0x...: int object at 0x...>,) print(closure.__closure__[0].cell_contents) # 10

Practical Applications of Closures

1. Data Hiding and Encapsulation

python
def make_account(initial_balance): """Create bank account""" balance = initial_balance def deposit(amount): nonlocal balance balance += amount return balance def withdraw(amount): nonlocal balance if amount <= balance: balance -= amount return balance else: raise ValueError("Insufficient balance") def get_balance(): return balance # Return multiple functions return { 'deposit': deposit, 'withdraw': withdraw, 'get_balance': get_balance } # Create account account = make_account(100) # Use account print(account['deposit'](50)) # 150 print(account['withdraw'](30)) # 120 print(account['get_balance']()) # 120 # balance variable is hidden, cannot be accessed directly # print(balance) # NameError: name 'balance' is not defined

2. Function Factory

python
def make_power_function(power): """Create power function""" def power_function(base): return base ** power return power_function # Create different power functions square = make_power_function(2) cube = make_power_function(3) fourth_power = make_power_function(4) print(square(3)) # 9 print(cube(3)) # 27 print(fourth_power(3)) # 81

3. Lazy Evaluation

python
def lazy_sum(*args): """Lazy summation""" def sum(): total = 0 for num in args: total += num return total return sum # Create lazy sum function f = lazy_sum(1, 2, 3, 4, 5) # Calculate only when called print(f()) # 15

4. Caching and Memoization

python
def memoize(func): """Memoization decorator""" cache = {} def memoized(*args): if args not in cache: cache[args] = func(*args) return cache[args] return memoized @memoize def fibonacci(n): """Fibonacci sequence""" if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) print(fibonacci(10)) # 55 print(fibonacci(20)) # 6765

5. Callback Functions

python
def make_callback(callback): """Create callback function""" def execute(*args, **kwargs): print("Before callback execution...") result = callback(*args, **kwargs) print("After callback execution...") return result return execute def my_function(x, y): return x + y # Create function with callback callback_function = make_callback(my_function) print(callback_function(3, 5)) # Before callback execution..., 8, After callback execution...

6. State Maintenance

python
def make_state_machine(): """Create state machine""" state = 'idle' def transition(action): nonlocal state print(f"Current state: {state}, Action: {action}") if state == 'idle': if action == 'start': state = 'running' elif state == 'running': if action == 'pause': state = 'paused' elif action == 'stop': state = 'idle' elif state == 'paused': if action == 'resume': state = 'running' elif action == 'stop': state = 'idle' print(f"New state: {state}") return state return transition # Create state machine state_machine = make_state_machine() state_machine('start') # idle -> running state_machine('pause') # running -> paused state_machine('resume') # paused -> running state_machine('stop') # running -> idle

Closures and Decorators

Implementing Decorators with Closures

python
def my_decorator(func): """Simple decorator""" def wrapper(): print("Decorator: Before function call") result = func() print("Decorator: After function call") return result return wrapper @my_decorator def say_hello(): print("Hello!") say_hello() # Output: # Decorator: Before function call # Hello! # Decorator: After function call

Decorators with Parameters

python
def repeat(times): """Repeat execution decorator""" def decorator(func): def wrapper(*args, **kwargs): results = [] for _ in range(times): result = func(*args, **kwargs) results.append(result) return results return wrapper return decorator @repeat(3) def greet(name): return f"Hello, {name}!" print(greet("Alice")) # Output: ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']

Closure Considerations

1. Loop Variable Trap

python
# Wrong approach def create_multipliers(): return [lambda x: x * i for i in range(5)] multipliers = create_multipliers() print([m(2) for m in multipliers]) # [8, 8, 8, 8, 8] - Wrong! # Correct approach - use default parameter def create_multipliers_correct(): return [lambda x, i=i: x * i for i in range(5)] multipliers_correct = create_multipliers_correct() print([m(2) for m in multipliers_correct]) # [0, 2, 4, 6, 8] - Correct

2. Modifying Outer Variables

python
def outer(): count = 0 def increment(): nonlocal count # Must use nonlocal keyword count += 1 return count return increment counter = outer() print(counter()) # 1 print(counter()) # 2

3. Memory Leak Risk

python
def large_closure(): """Create large closure""" large_data = list(range(1000000)) def process(): return sum(large_data[:100]) return process # Closure keeps reference to large_data # Even if only using a small portion closure = large_closure() # If closure is no longer needed, should delete reference del closure

Closures vs Classes

Closure Implementation

python
def make_counter(): """Implement counter using closure""" count = 0 def increment(): nonlocal count count += 1 return count def get_count(): return count return { 'increment': increment, 'get_count': get_count } counter = make_counter() print(counter['increment']()) # 1 print(counter['increment']()) # 2 print(counter['get_count']()) # 2

Class Implementation

python
class Counter: """Implement counter using class""" def __init__(self): self.count = 0 def increment(self): self.count += 1 return self.count def get_count(self): return self.count counter = Counter() print(counter.increment()) # 1 print(counter.increment()) # 2 print(counter.get_count()) # 2

When to Use Closures vs Classes

python
# Scenarios for using closures: # 1. Simple state maintenance def make_accumulator(): total = 0 def add(value): nonlocal total total += value return total return add # 2. Function factory def make_power(power): def power_function(base): return base ** power return power_function # Scenarios for using classes: # 1. Complex state management class BankAccount: def __init__(self, initial_balance): self.balance = initial_balance self.transactions = [] def deposit(self, amount): self.balance += amount self.transactions.append(('deposit', amount)) def withdraw(self, amount): if amount <= self.balance: self.balance -= amount self.transactions.append(('withdraw', amount)) def get_balance(self): return self.balance def get_transactions(self): return self.transactions # 2. Need multiple methods and attributes class Calculator: def __init__(self): self.history = [] def add(self, a, b): result = a + b self.history.append(f"{a} + {b} = {result}") return result def subtract(self, a, b): result = a - b self.history.append(f"{a} - {b} = {result}") return result def get_history(self): return self.history

Advanced Closure Applications

1. Partial Function Application

python
def partial(func, *args, **kwargs): """Partial function application""" def wrapper(*more_args, **more_kwargs): all_args = args + more_args all_kwargs = {**kwargs, **more_kwargs} return func(*all_args, **all_kwargs) return wrapper def power(base, exponent): return base ** exponent square = partial(power, exponent=2) cube = partial(power, exponent=3) print(square(5)) # 25 print(cube(5)) # 125

2. Function Composition

python
def compose(*functions): """Function composition""" def wrapper(arg): result = arg for func in reversed(functions): result = func(result) return result return wrapper def add_one(x): return x + 1 def multiply_two(x): return x * 2 def square(x): return x ** 2 # Compose functions combined = compose(square, multiply_two, add_one) print(combined(3)) # ((3 + 1) * 2) ** 2 = 64

3. Validators

python
def make_validator(validator_func, error_message): """Create validator""" def validate(value): if not validator_func(value): raise ValueError(error_message) return value return validate # Create validators is_positive = make_validator( lambda x: x > 0, "Value must be positive" ) is_email = make_validator( lambda x: '@' in x and '.' in x, "Invalid email address" ) # Use validators print(is_positive(10)) # 10 # is_positive(-5) # ValueError: Value must be positive print(is_email("user@example.com")) # user@example.com # is_email("invalid") # ValueError: Invalid email address

4. Rate Limiter

python
import time def rate_limiter(max_calls, time_window): """Create rate limiter""" calls = [] def limiter(func): def wrapper(*args, **kwargs): current_time = time.time() # Remove call records outside time window calls[:] = [call_time for call_time in calls if current_time - call_time < time_window] # Check if limit exceeded if len(calls) >= max_calls: raise Exception(f"Rate limit exceeded: {max_calls} calls/{time_window} seconds") # Record call calls.append(current_time) # Execute function return func(*args, **kwargs) return wrapper return limiter @rate_limiter(max_calls=3, time_window=1) def api_call(): print("API call successful") return "success" # Test rate limiting api_call() # Success api_call() # Success api_call() # Success # api_call() # Exception: Rate limit exceeded: 3 calls/1 seconds

Summary

Core concepts of Python closures:

  1. Basic Definition: A closure is a function object that retains access to variables from its defining scope
  2. Three Conditions: Nested function, references outer variables, returns inner function
  3. How It Works: Maintains references to outer variables through __closure__ attribute

Practical applications of closures:

  • Data hiding and encapsulation
  • Function factory
  • Lazy evaluation
  • Caching and memoization
  • Callback functions
  • State maintenance

Closure considerations:

  • Loop variable trap
  • Use nonlocal to modify outer variables
  • Be aware of memory leak risks

Closures vs Classes:

  • Closures: Suitable for simple state maintenance and function factories
  • Classes: Suitable for complex state management and multiple methods

Advanced closure applications:

  • Partial function application
  • Function composition
  • Validators
  • Rate limiters

Closures are a powerful and elegant feature in Python that allow functions to maintain state, implement data hiding, and create more flexible and reusable code. Mastering closures is very important for writing high-quality Python code.

标签:Python