Skip to main content

collections.UserList — List Wrapper for Subclassing

📚 Official Documentation & Resources

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 data attribute
  • 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

MethodDescriptionTime ComplexityReturn TypeExample
__init__(initlist=None)Initialize UserList with optional listO(n)UserListUserList([1, 2, 3])
__getitem__(index)Get item by indexO(1)Anyuser_list[0]
__setitem__(index, value)Set item by indexO(1)Noneuser_list[0] = value
__delitem__(index)Delete item by indexO(n)Nonedel user_list[0]
__contains__(item)Check if item existsO(n)boolitem in user_list
__len__()Get number of itemsO(1)intlen(user_list)
__iter__()Iterate over itemsO(n)iteratorfor item in user_list
append(item)Add item to endO(1)Noneuser_list.append(item)
insert(index, item)Insert item at indexO(n)Noneuser_list.insert(0, item)
remove(item)Remove first occurrenceO(n)Noneuser_list.remove(item)
pop(index=-1)Remove and return itemO(1) or O(n)Anyuser_list.pop()
index(item, start=0, stop=None)Find index of itemO(n)intuser_list.index(item)
count(item)Count occurrencesO(n)intuser_list.count(item)
sort(key=None, reverse=False)Sort list in placeO(n log n)Noneuser_list.sort()
reverse()Reverse list in placeO(n)Noneuser_list.reverse()
extend(iterable)Extend with iterableO(k)Noneuser_list.extend([4, 5])
clear()Remove all itemsO(n)Noneuser_list.clear()
copy()Create shallow copyO(n)UserListnew_list = user_list.copy()

Properties/Attributes

AttributeDescriptionTypeExample
dataUnderlying list storing the datalistuser_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

  1. Override Only What You Need - Don't override methods unnecessarily
  2. Use Data Attribute - Access self.data for the underlying list
  3. Call Super Methods - Always call super() when overriding methods
  4. Validate Early - Validate inputs in append/insert when possible
  5. Document Behavior - Clearly document any custom behavior
  6. Consider Performance - Use regular list if you don't need custom behavior
  7. 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.