Python 2 and 3 compatibility utilities
—
Utilities for working with metaclasses and creating decorators that work across Python versions. These functions provide unified interfaces for metaclass usage and decorator creation that handle the syntax differences between Python 2 and 3.
Functions for creating classes with metaclasses in a cross-version compatible way.
def with_metaclass(meta: type, *bases: type) -> type
"""Create a base class with a metaclass."""
def add_metaclass(metaclass: type) -> Callable[[type], type]
"""Class decorator for adding a metaclass."""Usage Examples:
import six
# Method 1: Using with_metaclass for base class creation
class MyMeta(type):
def __new__(cls, name, bases, dct):
# Add custom behavior
dct['created_by'] = 'MyMeta'
return super(MyMeta, cls).__new__(cls, name, bases, dct)
# Create base class with metaclass
BaseClass = six.with_metaclass(MyMeta, object)
class MyClass(BaseClass):
pass
print(MyClass.created_by) # "MyMeta"
# Method 2: Using add_metaclass decorator
@six.add_metaclass(MyMeta)
class AnotherClass(object):
def method(self):
return "Hello"
print(AnotherClass.created_by) # "MyMeta"Cross-version decorator utilities including functools.wraps replacement.
def wraps(wrapped: Callable) -> Callable[[Callable], Callable]
"""Decorator to wrap a function with functools.wraps behavior."""This provides functools.wraps functionality across Python versions, preserving function metadata when creating decorators.
Usage Examples:
import six
# Create a decorator using six.wraps
def my_decorator(func):
@six.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@my_decorator
def greet(name):
"""Greet someone by name."""
return f"Hello, {name}!"
# Function metadata is preserved
print(greet.__name__) # "greet"
print(greet.__doc__) # "Greet someone by name."
result = greet("Alice") # Prints: Calling greet, Hello, Alice!, Finished greetClass decorator for Python 2 unicode string compatibility.
def python_2_unicode_compatible(cls: type) -> type
"""Class decorator for unicode compatibility in Python 2."""This decorator allows classes to define __str__ methods that return unicode strings in Python 2 and regular strings in Python 3.
Usage Example:
import six
@six.python_2_unicode_compatible
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
# This will work correctly in both Python 2 and 3
return f"Person(name='{self.name}', age={self.age})"
def __repr__(self):
return f"Person({self.name!r}, {self.age!r})"
# Works with unicode characters in both versions
person = Person("José", 30)
print(str(person)) # Handles unicode correctly
print(repr(person))import six
class SingletonMeta(type):
"""Metaclass that creates singleton instances."""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
return cls._instances[cls]
# Create singleton base class
SingletonBase = six.with_metaclass(SingletonMeta, object)
class DatabaseConnection(SingletonBase):
def __init__(self):
self.connection_id = id(self)
def connect(self):
return f"Connected with ID: {self.connection_id}"
# Test singleton behavior
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
print(db1.connect()) # Same connection IDimport six
class RegistryMeta(type):
"""Metaclass that maintains a registry of all created classes."""
registry = {}
def __new__(cls, name, bases, dct):
new_class = super(RegistryMeta, cls).__new__(cls, name, bases, dct)
if name != 'BaseRegistered': # Skip base class
cls.registry[name] = new_class
return new_class
@classmethod
def get_class(cls, name):
return cls.registry.get(name)
@classmethod
def list_classes(cls):
return list(cls.registry.keys())
# Create base class with registry metaclass
BaseRegistered = six.with_metaclass(RegistryMeta, object)
class WidgetA(BaseRegistered):
pass
class WidgetB(BaseRegistered):
pass
# Access registry
print(RegistryMeta.list_classes()) # ['WidgetA', 'WidgetB']
widget_class = RegistryMeta.get_class('WidgetA')
widget = widget_class()import six
class ValidatedMeta(type):
"""Metaclass that adds attribute validation."""
def __new__(cls, name, bases, dct):
# Find validation rules
validators = {}
for key, value in list(dct.items()):
if key.startswith('validate_'):
attr_name = key[9:] # Remove 'validate_' prefix
validators[attr_name] = value
del dct[key] # Remove validator from class dict
# Create the class
new_class = super(ValidatedMeta, cls).__new__(cls, name, bases, dct)
new_class._validators = validators
# Override __setattr__ if not already defined
if '__setattr__' not in dct:
new_class.__setattr__ = cls._validated_setattr
return new_class
@staticmethod
def _validated_setattr(self, name, value):
if hasattr(self.__class__, '_validators') and name in self.__class__._validators:
validator = self.__class__._validators[name]
validator(self, value)
object.__setattr__(self, name, value)
# Use the validation metaclass
@six.add_metaclass(ValidatedMeta)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def validate_age(self, value):
if not isinstance(value, int) or value < 0:
raise ValueError("Age must be a non-negative integer")
def validate_name(self, value):
if not isinstance(value, six.string_types) or len(value) == 0:
raise ValueError("Name must be a non-empty string")
# Test validation
person = Person("Alice", 30)
person.age = 25 # OK
try:
person.age = -5 # Raises ValueError
except ValueError as e:
print(f"Validation error: {e}")import six
# Abstract base class pattern with metaclass
class AbstractMeta(type):
"""Metaclass for creating abstract base classes."""
def __new__(cls, name, bases, dct):
# Collect abstract methods
abstract_methods = set()
for base in bases:
if hasattr(base, '_abstract_methods'):
abstract_methods.update(base._abstract_methods)
for key, value in dct.items():
if getattr(value, '_abstract', False):
abstract_methods.add(key)
elif key in abstract_methods:
abstract_methods.discard(key) # Implemented
new_class = super(AbstractMeta, cls).__new__(cls, name, bases, dct)
new_class._abstract_methods = frozenset(abstract_methods)
return new_class
def __call__(cls, *args, **kwargs):
if cls._abstract_methods:
raise TypeError(f"Can't instantiate abstract class {cls.__name__} "
f"with abstract methods {', '.join(cls._abstract_methods)}")
return super(AbstractMeta, cls).__call__(*args, **kwargs)
def abstract_method(func):
"""Decorator to mark methods as abstract."""
func._abstract = True
return func
# Create abstract base class
@six.add_metaclass(AbstractMeta)
class Shape:
@abstract_method
def area(self):
pass
@abstract_method
def perimeter(self):
pass
def describe(self):
return f"Shape with area {self.area()} and perimeter {self.perimeter()}"
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# Usage
rect = Rectangle(5, 3)
print(rect.describe()) # Shape with area 15 and perimeter 16
# This would raise TypeError:
# shape = Shape() # Can't instantiate abstract class
# Mixin pattern with metaclass support
class LoggingMixin:
"""Mixin that adds logging capabilities."""
def log(self, message):
class_name = self.__class__.__name__
six.print_(f"[{class_name}] {message}")
@six.add_metaclass(ValidatedMeta)
class LoggedPerson(LoggingMixin):
def __init__(self, name, age):
self.name = name
self.age = age
self.log(f"Created person: {name}, age {age}")
def validate_age(self, value):
if not isinstance(value, int) or value < 0:
self.log(f"Invalid age validation: {value}")
raise ValueError("Age must be a non-negative integer")
def celebrate_birthday(self):
old_age = self.age
self.age += 1
self.log(f"Happy birthday! Age changed from {old_age} to {self.age}")
# Usage
person = LoggedPerson("Bob", 25) # [LoggedPerson] Created person: Bob, age 25
person.celebrate_birthday() # [LoggedPerson] Happy birthday! Age changed from 25 to 26Install with Tessl CLI
npx tessl i tessl/pypi-six