collections.UserList — List Wrapper for Subclassing
📚 Official Documentation & Resources
- Python Official Documentation - Complete API reference and examples
- PEP 289 - User classes design patterns and rationale
- Real Python Tutorial - In-depth tutorial with practical examples
- Python Module of the Week - Comprehensive examples and use cases
- GeeksforGeeks Guide - Beginner-friendly tutorial
- Python Tips Blog - Quick reference and tips
Overview
collections.UserList is a wrapper around list objects that provides a convenient base class for creating custom list-like classes. Unlike inheriting from list directly, UserList stores the underlying data in a regular list accessible via the data attribute, making it easier to override methods without worrying about circular calls or complex inheritance issues.
🎯 Key Characteristics
- List Wrapper - Wraps a regular list in the
dataattribute - Safe Subclassing - Avoids complex inheritance issues with built-in list
- Method Override Friendly - Easy to customize specific list behaviors
- Full List Interface - Supports all standard list operations
- Transparent Access - Behaves exactly like a regular list
- Initialization Flexibility - Multiple ways to initialize with data
📚 Basic Usage
Simple Example
from collections import UserList
# Basic UserList usage
user_list = UserList([1, 2, 3, 4, 5])
print(user_list[0]) # 1
user_list.append(6)
print(list(user_list)) # [1, 2, 3, 4, 5, 6]
# Access underlying data
print(user_list.data) # [1, 2, 3, 4, 5, 6]
# Custom list with validation
class ValidatedList(UserList):
def append(self, item):
if not isinstance(item, (int, float)):
raise TypeError("Only numbers allowed")
super().append(item)
def __setitem__(self, index, value):
if not isinstance(value, (int, float)):
raise TypeError("Only numbers allowed")
super().__setitem__(index, value)
# Usage with validation
numbers = ValidatedList([1, 2, 3])
numbers.append(4) # ✓ Valid
# numbers.append("invalid") # ✗ TypeError: Only numbers allowed
# numbers[0] = "invalid" # ✗ TypeError: Only numbers allowed
Core Methods
from collections import UserList
# Initialize with different methods
empty_list = UserList()
from_list = UserList([1, 2, 3])
from_iterable = UserList(range(5))
# Access data attribute
print(from_list.data) # [1, 2, 3]
# All regular list operations work
print(len(from_list)) # 3
print(2 in from_list) # True
print(from_list[1]) # 2
🔧 UserList API Reference
Methods
| Method | Description | Time Complexity | Return Type | Example |
|---|---|---|---|---|
__init__(initlist=None) | Initialize UserList with optional list | O(n) | UserList | UserList([1, 2, 3]) |
__getitem__(index) | Get item by index | O(1) | Any | user_list[0] |
__setitem__(index, value) | Set item by index | O(1) | None | user_list[0] = value |
__delitem__(index) | Delete item by index | O(n) | None | del user_list[0] |
__contains__(item) | Check if item exists | O(n) | bool | item in user_list |
__len__() | Get number of items | O(1) | int | len(user_list) |
__iter__() | Iterate over items | O(n) | iterator | for item in user_list |
append(item) | Add item to end | O(1) | None | user_list.append(item) |
insert(index, item) | Insert item at index | O(n) | None | user_list.insert(0, item) |
remove(item) | Remove first occurrence | O(n) | None | user_list.remove(item) |
pop(index=-1) | Remove and return item | O(1) or O(n) | Any | user_list.pop() |
index(item, start=0, stop=None) | Find index of item | O(n) | int | user_list.index(item) |
count(item) | Count occurrences | O(n) | int | user_list.count(item) |
sort(key=None, reverse=False) | Sort list in place | O(n log n) | None | user_list.sort() |
reverse() | Reverse list in place | O(n) | None | user_list.reverse() |
extend(iterable) | Extend with iterable | O(k) | None | user_list.extend([4, 5]) |
clear() | Remove all items | O(n) | None | user_list.clear() |
copy() | Create shallow copy | O(n) | UserList | new_list = user_list.copy() |
Properties/Attributes
| Attribute | Description | Type | Example |
|---|---|---|---|
data | Underlying list storing the data | list | user_list.data |
Detailed Method Examples
from collections import UserList
# Initialize test UserList
ul = UserList([1, 2, 3, 4, 5])
print(f"Original: {list(ul)}") # [1, 2, 3, 4, 5]
# Access and modification
print(f"First item: {ul[0]}") # 1
print(f"Last item: {ul[-1]}") # 5
ul[1] = 20
print(f"After setting index 1: {list(ul)}") # [1, 20, 3, 4, 5]
# Append and insert
ul.append(6)
ul.insert(0, 0)
print(f"After append and insert: {list(ul)}") # [0, 1, 20, 3, 4, 5, 6]
# Remove operations
ul.remove(20) # Remove by value
print(f"After remove(20): {list(ul)}") # [0, 1, 3, 4, 5, 6]
popped = ul.pop() # Remove last
print(f"Popped: {popped}") # 6
print(f"After pop(): {list(ul)}") # [0, 1, 3, 4, 5]
popped_index = ul.pop(1) # Remove at index
print(f"Popped at index 1: {popped_index}") # 1
print(f"After pop(1): {list(ul)}") # [0, 3, 4, 5]
# Search operations
print(f"Index of 3: {ul.index(3)}") # 1
print(f"Count of 3: {ul.count(3)}") # 1
print(f"Contains 4: {4 in ul}") # True
# Extend and sort
ul.extend([10, 7, 8])
print(f"After extend: {list(ul)}") # [0, 3, 4, 5, 10, 7, 8]
ul.sort()
print(f"After sort: {list(ul)}") # [0, 3, 4, 5, 7, 8, 10]
ul.reverse()
print(f"After reverse: {list(ul)}") # [10, 8, 7, 5, 4, 3, 0]
# Copy
ul_copy = ul.copy()
ul_copy.append(99)
print(f"Original: {list(ul)}") # [10, 8, 7, 5, 4, 3, 0]
print(f"Copy: {list(ul_copy)}") # [10, 8, 7, 5, 4, 3, 0, 99]
# Clear
ul.clear()
print(f"After clear: {list(ul)}") # []
🎯 Primary Use Cases
1. Type-Safe Collections
Use Case: Lists that only accept specific types with validation and type conversion. Why UserList: Easy method override for validation without complex list inheritance issues.
from collections import UserList
from typing import Union, Type, Any
from datetime import datetime
class TypedList(UserList):
"""List that enforces type constraints."""
def __init__(self, allowed_types, initlist=None):
self.allowed_types = allowed_types if isinstance(allowed_types, tuple) else (allowed_types,)
super().__init__()
if initlist:
for item in initlist:
self.append(item) # Use append to trigger validation
def _validate_type(self, item: Any) -> bool:
"""Validate item type."""
return isinstance(item, self.allowed_types)
def append(self, item: Any):
"""Append with type validation."""
if not self._validate_type(item):
raise TypeError(f"Item must be of type {self.allowed_types}, got {type(item)}")
super().append(item)
def insert(self, index: int, item: Any):
"""Insert with type validation."""
if not self._validate_type(item):
raise TypeError(f"Item must be of type {self.allowed_types}, got {type(item)}")
super().insert(index, item)
def __setitem__(self, index: int, item: Any):
"""Set item with type validation."""
if not self._validate_type(item):
raise TypeError(f"Item must be of type {self.allowed_types}, got {type(item)}")
super().__setitem__(index, item)
def extend(self, iterable):
"""Extend with type validation."""
for item in iterable:
self.append(item) # Use append for validation
class NumberList(TypedList):
"""List that only accepts numbers."""
def __init__(self, initlist=None):
super().__init__((int, float), initlist)
def sum(self) -> Union[int, float]:
"""Calculate sum of all numbers."""
return sum(self.data)
def average(self) -> float:
"""Calculate average of all numbers."""
if not self.data:
return 0.0
return sum(self.data) / len(self.data)
def max_value(self) -> Union[int, float, None]:
"""Get maximum value."""
return max(self.data) if self.data else None
class StringList(TypedList):
"""List that only accepts strings."""
def __init__(self, initlist=None):
super().__init__(str, initlist)
def join(self, separator: str = '') -> str:
"""Join all strings."""
return separator.join(self.data)
def filter_by_prefix(self, prefix: str) -> 'StringList':
"""Filter strings by prefix."""
filtered = [s for s in self.data if s.startswith(prefix)]
return StringList(filtered)
# Usage
numbers = NumberList([1, 2, 3, 4.5])
numbers.append(10)
print(f"Numbers: {list(numbers)}") # [1, 2, 3, 4.5, 10]
print(f"Sum: {numbers.sum()}") # 20.5
print(f"Average: {numbers.average()}") # 4.1
strings = StringList(['hello', 'world', 'python'])
strings.append('programming')
print(f"Strings: {list(strings)}") # ['hello', 'world', 'python', 'programming']
print(f"Joined: {strings.join(' ')}") # hello world python programming
# Type validation in action
try:
numbers.append("invalid") # TypeError
except TypeError as e:
print(f"Error: {e}")
2. Audit Trail List
Use Case: List that tracks all modifications for auditing and debugging purposes. Why UserList: Allows transparent list operations while logging all changes.
from collections import UserList
from datetime import datetime
from typing import Any, List, Dict
import copy
class AuditList(UserList):
"""List that maintains an audit trail of all operations."""
def __init__(self, initlist=None, name="AuditList"):
super().__init__(initlist or [])
self.name = name
self.audit_log: List[Dict[str, Any]] = []
self._log_operation('create', {'initial_data': copy.deepcopy(self.data)})
def _log_operation(self, operation: str, details: Dict[str, Any] = None):
"""Log an operation to the audit trail."""
log_entry = {
'timestamp': datetime.now(),
'operation': operation,
'list_size': len(self.data),
'details': details or {}
}
self.audit_log.append(log_entry)
def append(self, item: Any):
"""Append with audit logging."""
super().append(item)
self._log_operation('append', {'item': item, 'new_index': len(self.data) - 1})
def insert(self, index: int, item: Any):
"""Insert with audit logging."""
super().insert(index, item)
self._log_operation('insert', {'index': index, 'item': item})
def remove(self, item: Any):
"""Remove with audit logging."""
try:
index = self.data.index(item)
super().remove(item)
self._log_operation('remove', {'item': item, 'old_index': index})
except ValueError:
self._log_operation('remove_failed', {'item': item, 'reason': 'not_found'})
raise
def pop(self, index: int = -1):
"""Pop with audit logging."""
if not self.data:
self._log_operation('pop_failed', {'index': index, 'reason': 'empty_list'})
raise IndexError("pop from empty list")
item = super().pop(index)
self._log_operation('pop', {'index': index, 'item': item})
return item
def __setitem__(self, index: int, value: Any):
"""Set item with audit logging."""
old_value = self.data[index] if 0 <= index < len(self.data) else None
super().__setitem__(index, value)
self._log_operation('set_item', {
'index': index,
'old_value': old_value,
'new_value': value
})
def __delitem__(self, index: int):
"""Delete item with audit logging."""
old_value = self.data[index]
super().__delitem__(index)
self._log_operation('delete_item', {'index': index, 'deleted_value': old_value})
def extend(self, iterable):
"""Extend with audit logging."""
items = list(iterable)
start_index = len(self.data)
super().extend(items)
self._log_operation('extend', {'items': items, 'start_index': start_index})
def clear(self):
"""Clear with audit logging."""
old_data = copy.deepcopy(self.data)
super().clear()
self._log_operation('clear', {'cleared_data': old_data})
def sort(self, *, key=None, reverse=False):
"""Sort with audit logging."""
old_data = copy.deepcopy(self.data)
super().sort(key=key, reverse=reverse)
self._log_operation('sort', {
'key': str(key) if key else None,
'reverse': reverse,
'old_order': old_data
})
def reverse(self):
"""Reverse with audit logging."""
old_data = copy.deepcopy(self.data)
super().reverse()
self._log_operation('reverse', {'old_order': old_data})
def get_audit_log(self, operation_type: str = None) -> List[Dict[str, Any]]:
"""Get audit log entries, optionally filtered by operation type."""
if operation_type:
return [entry for entry in self.audit_log if entry['operation'] == operation_type]
return self.audit_log.copy()
def print_audit_summary(self):
"""Print a summary of audit log."""
print(f"Audit Summary for {self.name}")
print(f"Total operations: {len(self.audit_log)}")
print(f"Current size: {len(self.data)}")
operations = {}
for entry in self.audit_log:
op = entry['operation']
operations[op] = operations.get(op, 0) + 1
print("Operations count:")
for op, count in sorted(operations.items()):
print(f" {op}: {count}")
def rollback_to_operation(self, operation_index: int):
"""Rollback to state after a specific operation (simulation)."""
if not 0 <= operation_index < len(self.audit_log):
raise IndexError("Invalid operation index")
print(f"Would rollback to operation {operation_index}: {self.audit_log[operation_index]}")
# In a real implementation, you would reconstruct the state
# Usage
tracked_list = AuditList([1, 2, 3], name="UserData")
tracked_list.append(4)
tracked_list.insert(1, 1.5)
tracked_list.remove(2)
tracked_list[0] = 'updated'
tracked_list.extend([5, 6])
tracked_list.sort()
print(f"Final list: {list(tracked_list)}")
# Review audit trail
tracked_list.print_audit_summary()
print("\nDetailed audit log:")
for i, entry in enumerate(tracked_list.get_audit_log()):
timestamp = entry['timestamp'].strftime('%H:%M:%S.%f')[:-3]
print(f"{i}: [{timestamp}] {entry['operation']} - {entry['details']}")
🎯 When to Use UserList
✅ Ideal Use Cases
- Custom List Classes - When you need to override list behavior
- Data Validation - Lists that need input validation
- Type-Safe Collections - Lists that enforce type constraints
- Audit Trails - Lists that track modifications
- Event-Driven Lists - Lists that notify observers of changes
- Lazy Loading - Lists that load data on-demand
- Pagination - Lists that handle paginated data sources
❌ When NOT to Use UserList
- Simple List Needs - Use regular list for basic operations
- Performance Critical Code - UserList has slight overhead
- Memory Constrained - Regular list uses less memory
- No Custom Behavior - If you don't need to override methods
💡 Best Practices
- Override Only What You Need - Don't override methods unnecessarily
- Use Data Attribute - Access
self.datafor the underlying list - Call Super Methods - Always call
super()when overriding methods - Validate Early - Validate inputs in append/insert when possible
- Document Behavior - Clearly document any custom behavior
- Consider Performance - Use regular list if you don't need custom behavior
- Handle Edge Cases - Consider empty lists, negative indices, etc.
UserList is the ideal choice when you need a list with custom behavior while maintaining the familiar list interface. Its transparent wrapper design makes it perfect for data validation, audit trails, and creating domain-specific list classes.