or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

execution.mdindex.mditerator-dict.mdmetaclass.mdmoves.mdstring-bytes.mdtesting.mdversion-detection.md

metaclass.mddocs/

0

# Metaclass and Decorator Utilities

1

2

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.

3

4

## Capabilities

5

6

### Metaclass Utilities

7

8

Functions for creating classes with metaclasses in a cross-version compatible way.

9

10

```python { .api }

11

def with_metaclass(meta: type, *bases: type) -> type

12

"""Create a base class with a metaclass."""

13

14

def add_metaclass(metaclass: type) -> Callable[[type], type]

15

"""Class decorator for adding a metaclass."""

16

```

17

18

**Usage Examples:**

19

20

```python

21

import six

22

23

# Method 1: Using with_metaclass for base class creation

24

class MyMeta(type):

25

def __new__(cls, name, bases, dct):

26

# Add custom behavior

27

dct['created_by'] = 'MyMeta'

28

return super(MyMeta, cls).__new__(cls, name, bases, dct)

29

30

# Create base class with metaclass

31

BaseClass = six.with_metaclass(MyMeta, object)

32

33

class MyClass(BaseClass):

34

pass

35

36

print(MyClass.created_by) # "MyMeta"

37

38

# Method 2: Using add_metaclass decorator

39

@six.add_metaclass(MyMeta)

40

class AnotherClass(object):

41

def method(self):

42

return "Hello"

43

44

print(AnotherClass.created_by) # "MyMeta"

45

```

46

47

### Decorator Utilities

48

49

Cross-version decorator utilities including functools.wraps replacement.

50

51

```python { .api }

52

def wraps(wrapped: Callable) -> Callable[[Callable], Callable]

53

"""Decorator to wrap a function with functools.wraps behavior."""

54

```

55

56

This provides `functools.wraps` functionality across Python versions, preserving function metadata when creating decorators.

57

58

**Usage Examples:**

59

60

```python

61

import six

62

63

# Create a decorator using six.wraps

64

def my_decorator(func):

65

@six.wraps(func)

66

def wrapper(*args, **kwargs):

67

print(f"Calling {func.__name__}")

68

result = func(*args, **kwargs)

69

print(f"Finished {func.__name__}")

70

return result

71

return wrapper

72

73

@my_decorator

74

def greet(name):

75

"""Greet someone by name."""

76

return f"Hello, {name}!"

77

78

# Function metadata is preserved

79

print(greet.__name__) # "greet"

80

print(greet.__doc__) # "Greet someone by name."

81

result = greet("Alice") # Prints: Calling greet, Hello, Alice!, Finished greet

82

```

83

84

### Unicode Compatibility Decorator

85

86

Class decorator for Python 2 unicode string compatibility.

87

88

```python { .api }

89

def python_2_unicode_compatible(cls: type) -> type

90

"""Class decorator for unicode compatibility in Python 2."""

91

```

92

93

This decorator allows classes to define `__str__` methods that return unicode strings in Python 2 and regular strings in Python 3.

94

95

**Usage Example:**

96

97

```python

98

import six

99

100

@six.python_2_unicode_compatible

101

class Person:

102

def __init__(self, name, age):

103

self.name = name

104

self.age = age

105

106

def __str__(self):

107

# This will work correctly in both Python 2 and 3

108

return f"Person(name='{self.name}', age={self.age})"

109

110

def __repr__(self):

111

return f"Person({self.name!r}, {self.age!r})"

112

113

# Works with unicode characters in both versions

114

person = Person("José", 30)

115

print(str(person)) # Handles unicode correctly

116

print(repr(person))

117

```

118

119

## Advanced Metaclass Patterns

120

121

### Custom Metaclass with six.with_metaclass

122

123

```python

124

import six

125

126

class SingletonMeta(type):

127

"""Metaclass that creates singleton instances."""

128

_instances = {}

129

130

def __call__(cls, *args, **kwargs):

131

if cls not in cls._instances:

132

cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)

133

return cls._instances[cls]

134

135

# Create singleton base class

136

SingletonBase = six.with_metaclass(SingletonMeta, object)

137

138

class DatabaseConnection(SingletonBase):

139

def __init__(self):

140

self.connection_id = id(self)

141

142

def connect(self):

143

return f"Connected with ID: {self.connection_id}"

144

145

# Test singleton behavior

146

db1 = DatabaseConnection()

147

db2 = DatabaseConnection()

148

print(db1 is db2) # True

149

print(db1.connect()) # Same connection ID

150

```

151

152

### Registry Metaclass Pattern

153

154

```python

155

import six

156

157

class RegistryMeta(type):

158

"""Metaclass that maintains a registry of all created classes."""

159

registry = {}

160

161

def __new__(cls, name, bases, dct):

162

new_class = super(RegistryMeta, cls).__new__(cls, name, bases, dct)

163

if name != 'BaseRegistered': # Skip base class

164

cls.registry[name] = new_class

165

return new_class

166

167

@classmethod

168

def get_class(cls, name):

169

return cls.registry.get(name)

170

171

@classmethod

172

def list_classes(cls):

173

return list(cls.registry.keys())

174

175

# Create base class with registry metaclass

176

BaseRegistered = six.with_metaclass(RegistryMeta, object)

177

178

class WidgetA(BaseRegistered):

179

pass

180

181

class WidgetB(BaseRegistered):

182

pass

183

184

# Access registry

185

print(RegistryMeta.list_classes()) # ['WidgetA', 'WidgetB']

186

widget_class = RegistryMeta.get_class('WidgetA')

187

widget = widget_class()

188

```

189

190

### Attribute Validation Metaclass

191

192

```python

193

import six

194

195

class ValidatedMeta(type):

196

"""Metaclass that adds attribute validation."""

197

198

def __new__(cls, name, bases, dct):

199

# Find validation rules

200

validators = {}

201

for key, value in list(dct.items()):

202

if key.startswith('validate_'):

203

attr_name = key[9:] # Remove 'validate_' prefix

204

validators[attr_name] = value

205

del dct[key] # Remove validator from class dict

206

207

# Create the class

208

new_class = super(ValidatedMeta, cls).__new__(cls, name, bases, dct)

209

new_class._validators = validators

210

211

# Override __setattr__ if not already defined

212

if '__setattr__' not in dct:

213

new_class.__setattr__ = cls._validated_setattr

214

215

return new_class

216

217

@staticmethod

218

def _validated_setattr(self, name, value):

219

if hasattr(self.__class__, '_validators') and name in self.__class__._validators:

220

validator = self.__class__._validators[name]

221

validator(self, value)

222

object.__setattr__(self, name, value)

223

224

# Use the validation metaclass

225

@six.add_metaclass(ValidatedMeta)

226

class Person:

227

def __init__(self, name, age):

228

self.name = name

229

self.age = age

230

231

def validate_age(self, value):

232

if not isinstance(value, int) or value < 0:

233

raise ValueError("Age must be a non-negative integer")

234

235

def validate_name(self, value):

236

if not isinstance(value, six.string_types) or len(value) == 0:

237

raise ValueError("Name must be a non-empty string")

238

239

# Test validation

240

person = Person("Alice", 30)

241

person.age = 25 # OK

242

try:

243

person.age = -5 # Raises ValueError

244

except ValueError as e:

245

print(f"Validation error: {e}")

246

```

247

248

## Common Usage Patterns

249

250

```python

251

import six

252

253

# Abstract base class pattern with metaclass

254

class AbstractMeta(type):

255

"""Metaclass for creating abstract base classes."""

256

257

def __new__(cls, name, bases, dct):

258

# Collect abstract methods

259

abstract_methods = set()

260

for base in bases:

261

if hasattr(base, '_abstract_methods'):

262

abstract_methods.update(base._abstract_methods)

263

264

for key, value in dct.items():

265

if getattr(value, '_abstract', False):

266

abstract_methods.add(key)

267

elif key in abstract_methods:

268

abstract_methods.discard(key) # Implemented

269

270

new_class = super(AbstractMeta, cls).__new__(cls, name, bases, dct)

271

new_class._abstract_methods = frozenset(abstract_methods)

272

return new_class

273

274

def __call__(cls, *args, **kwargs):

275

if cls._abstract_methods:

276

raise TypeError(f"Can't instantiate abstract class {cls.__name__} "

277

f"with abstract methods {', '.join(cls._abstract_methods)}")

278

return super(AbstractMeta, cls).__call__(*args, **kwargs)

279

280

def abstract_method(func):

281

"""Decorator to mark methods as abstract."""

282

func._abstract = True

283

return func

284

285

# Create abstract base class

286

@six.add_metaclass(AbstractMeta)

287

class Shape:

288

@abstract_method

289

def area(self):

290

pass

291

292

@abstract_method

293

def perimeter(self):

294

pass

295

296

def describe(self):

297

return f"Shape with area {self.area()} and perimeter {self.perimeter()}"

298

299

class Rectangle(Shape):

300

def __init__(self, width, height):

301

self.width = width

302

self.height = height

303

304

def area(self):

305

return self.width * self.height

306

307

def perimeter(self):

308

return 2 * (self.width + self.height)

309

310

# Usage

311

rect = Rectangle(5, 3)

312

print(rect.describe()) # Shape with area 15 and perimeter 16

313

314

# This would raise TypeError:

315

# shape = Shape() # Can't instantiate abstract class

316

317

# Mixin pattern with metaclass support

318

class LoggingMixin:

319

"""Mixin that adds logging capabilities."""

320

321

def log(self, message):

322

class_name = self.__class__.__name__

323

six.print_(f"[{class_name}] {message}")

324

325

@six.add_metaclass(ValidatedMeta)

326

class LoggedPerson(LoggingMixin):

327

def __init__(self, name, age):

328

self.name = name

329

self.age = age

330

self.log(f"Created person: {name}, age {age}")

331

332

def validate_age(self, value):

333

if not isinstance(value, int) or value < 0:

334

self.log(f"Invalid age validation: {value}")

335

raise ValueError("Age must be a non-negative integer")

336

337

def celebrate_birthday(self):

338

old_age = self.age

339

self.age += 1

340

self.log(f"Happy birthday! Age changed from {old_age} to {self.age}")

341

342

# Usage

343

person = LoggedPerson("Bob", 25) # [LoggedPerson] Created person: Bob, age 25

344

person.celebrate_birthday() # [LoggedPerson] Happy birthday! Age changed from 25 to 26

345

```