@@ -238,11 +238,10 @@ Here is what this might look like in an implementation:
238
238
239
239
struct MyTensorMetadata {
240
240
bool stored_on_gpu;
241
- // ..
242
- // should be a POD (plain old data) type
241
+ // ...
243
242
};
244
243
245
- // Register a new type MyTensor, and reserve space for sizeof( MyTensorMedadata)
244
+ // Register a new type MyTensor, and reserve space for MyTensorMedadata
246
245
nb::class_<MyTensor> cls(m, "MyTensor", nb::supplement<MyTensorMedadata>())
247
246
248
247
/// Mutable reference to 'MyTensorMedadata' portion in Python type object
@@ -251,13 +250,17 @@ Here is what this might look like in an implementation:
251
250
252
251
The :cpp:class: `nb::supplement\< T\> () <supplement> ` annotation implicitly also
253
252
passes :cpp:class: `nb::is_final() <is_final> ` to ensure that type objects with
254
- supplemental data cannot be subclassed in Python.
255
-
256
- nanobind requires that the specified type ``T `` be trivially default
257
- constructible. It zero-initializes the supplement when the type is first
258
- created but does not perform any further custom initialization or destruction.
259
- You can fill the supplement with different contents following the type
260
- creation, e.g., using the placement new operator.
253
+ supplemental data cannot be subclassed in Python. If you do wish to allow
254
+ subclassing, write ``nb::supplement<T>().inheritable() `` instead. Any subclasses
255
+ will get a new default-constructed copy of the supplemental ``T `` data, **not **
256
+ a copy of their base class's data.
257
+
258
+ nanobind requires that the specified type ``T `` be either constructible from
259
+ ``PyTypeObject* `` (the type that contains this supplement instance) or default
260
+ constructible. If ``T `` has a trivial default constructor, it will be
261
+ zero-initialized when the type is first created; otherwise, the appropriate
262
+ constructor will be run. You can fill the supplement with different contents
263
+ following the type creation.
261
264
262
265
The contents of the supplemental data are not directly visible to Python's
263
266
cyclic garbage collector, which creates challenges if you want to reference
@@ -268,6 +271,71 @@ name that begins with the symbol ``@``, then nanobind will prevent Python
268
271
code from rebinding or deleting the attribute after it has been set, making
269
272
the borrowed reference reasonably safe.
270
273
274
+ Metaclass customizations
275
+ ^^^^^^^^^^^^^^^^^^^^^^^^
276
+
277
+ nanobind internally creates a separate metaclass for each distinct
278
+ supplement type ``T `` used in your program. It is possible to customize this
279
+ metaclass in some limited ways, by providing a static member function of the
280
+ supplement type: ``static void T::init_metaclass(PyTypeObject *mcls) ``.
281
+ If provided, this function will be invoked once after the metaclass has been
282
+ created but before any types that use it have been created. It can customize
283
+ the metaclass's behavior by assigning attributes, including ``__dunder__ ``
284
+ methods.
285
+
286
+ It is not possible to provide custom :ref: `type slots <typeslots >` for the
287
+ metaclass, nor can the metaclass itself contain user-provided data (try
288
+ defining properties backed by global variables instead). It is not possible
289
+ to customize the metaclass's base or its type, because nanobind requires all
290
+ nanobind instances to have the same meta-metaclass for quick identification.
291
+
292
+ The following example shows how you could customize
293
+ ``__instancecheck__ `` to obtain results similar to :py:class: `abc.ABC `\' s
294
+ support for virtual base classes.
295
+
296
+ .. code-block :: cpp
297
+
298
+ struct HasVirtualSubclasses {
299
+ static void init_metaclass(PyTypeObject *mcls) {
300
+ nb::cpp_function_def(
301
+ [](nb::handle cls, nb::handle instance) {
302
+ if (!nb::type_check(cls) ||
303
+ !nb::type_has_supplement<HasVirtualSubclasses>(cls))
304
+ return false;
305
+
306
+ auto& supp = nb::type_supplement<HasVirtualSubclasses>(cls);
307
+ return PyType_IsSubtype((PyTypeObject *) instance.type(),
308
+ (PyTypeObject *) cls.ptr()) ||
309
+ nb::borrow<nb::set>(supp.subclasses).contains(
310
+ instance.type());
311
+ },
312
+ nb::is_method(), nb::scope(metatype),
313
+ nb::name("__instancecheck__"));
314
+ }
315
+
316
+ explicit HasVirtualSubclasses(PyTypeObject *tp) {
317
+ nb::set subclasses_set;
318
+ nb::handle(tp).attr("@subclasses") = subclasses_set;
319
+ subclasses = subclasses_set;
320
+ }
321
+
322
+ void register(nb::handle tp) {
323
+ nb::borrow<nb::set>(subclasses).add(tp);
324
+ }
325
+
326
+ nb::handle subclasses;
327
+ };
328
+
329
+ struct Collection {};
330
+ struct Set {};
331
+
332
+ auto coll = nb::class_<Collection>(m, "Collection",
333
+ nb::supplement<HasVirtualSubclasses>());
334
+ auto set = nb::class_<Set>(m, "Set").def(nb::init<>());
335
+ nb::type_supplement<HasVirtualSubclasses>(coll).register(set);
336
+
337
+ // assert isinstance(m.Set(), m.Collection)
338
+
271
339
.. _typeslots :
272
340
273
341
Customizing type creation
0 commit comments