29 __all__ = (
"continueClass",
"inClass",
"TemplateMeta")
32 INTRINSIC_SPECIAL_ATTRIBUTES = frozenset((
46 """Return True if an attribute is safe to monkeypatch-transfer to another
49 This rejects special methods that are defined automatically for all
50 classes, leaving only those explicitly defined in a class decorated by
51 `continueClass` or registered with an instance of `TemplateMeta`.
53 if name.startswith(
"__")
and (value
is getattr(object, name,
None)
or
54 name
in INTRINSIC_SPECIAL_ATTRIBUTES):
60 """Re-open the decorated class, adding any new definitions into the original.
64 .. code-block:: python
76 .. code-block:: python
84 Python's built-in `super` function does not behave properly in classes
85 decorated with `continueClass`. Base class methods must be invoked
86 directly using their explicit types instead.
89 orig = getattr(sys.modules[cls.__module__], cls.__name__)
96 attr = cls.__dict__.get(name,
None)
or getattr(cls, name)
98 setattr(orig, name, attr)
103 """Add the decorated function to the given class as a method.
107 .. code-block:: python
118 .. code-block:: python
124 Standard decorators like ``classmethod``, ``staticmethod``, and
125 ``property`` may be used *after* this decorator. Custom decorators
126 may only be used if they return an object with a ``__name__`` attribute
127 or the ``name`` optional argument is provided.
134 if hasattr(func,
"__name__"):
135 name1 = func.__name__
137 if hasattr(func,
"__func__"):
139 name1 = func.__func__.__name__
140 elif hasattr(func,
"fget"):
142 name1 = func.fget.__name__
145 "Could not guess attribute name for '{}'.".
format(func)
147 setattr(cls, name1, func)
153 """A metaclass for abstract base classes that tie together wrapped C++
156 C++ template classes are most easily wrapped with a separate Python class
157 for each template type, which results in an unnatural Python interface.
158 TemplateMeta provides a thin layer that connects these Python classes by
159 giving them a common base class and acting as a factory to construct them
162 To use, simply create a new class with the name of the template class, and
163 use ``TemplateMeta`` as its metaclass, and then call ``register`` on each
164 of its subclasses. This registers the class with a "type key" - usually a
165 Python representation of the C++ template types. The type key must be a
166 hashable object - strings, type objects, and tuples of these (for C++
167 classes with multiple template parameters) are good choices. Alternate
168 type keys for existing classes can be added by calling ``alias``, but only
169 after a subclass already been registered with a "primary" type key. For
172 .. code-block:: python
175 from ._image import ImageF, ImageD
177 class Image(metaclass=TemplateMeta):
180 Image.register(np.float32, ImageF)
181 Image.register(np.float64, ImageD)
182 Image.alias("F", ImageF)
183 Image.alias("D", ImageD)
185 We have intentionally used ``numpy`` types as the primary keys for these
186 objects in this example, with strings as secondary aliases simply because
187 the primary key is added as a ``dtype`` attribute on the the registered
188 classes (so ``ImageF.dtype == numpy.float32`` in the above example).
190 This allows user code to construct objects directly using ``Image``, as
191 long as an extra ``dtype`` keyword argument is passed that matches one of
194 .. code-block:: python
196 img = Image(52, 64, dtype=np.float32)
198 This simply forwards additional positional and keyword arguments to the
199 wrapped template class's constructor.
201 The choice of "dtype" as the name of the template parameter is also
202 configurable, and in fact multiple template parameters are also supported,
203 by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple
204 containing the names of the template parameters. A ``TEMPLATE_DEFAULTS``
205 attribute can also be defined to a tuple of the same length containing
206 default values for the template parameters, allowing them to be omitted in
207 constructor calls. When the length of these attributes is more than one,
208 the type keys passed to ``register`` and ``alias`` should be tuple of the
209 same length; when the length of these attributes is one, type keys should
210 generally not be tuples.
212 As an aid for those writing the Python wrappers for C++ classes,
213 ``TemplateMeta`` also provides a way to add pure-Python methods and other
214 attributes to the wrapped template classes. To add a ``sum`` method to
215 all registered types, for example, we can just do::
217 .. code-block:: python
219 class Image(metaclass=TemplateMeta):
222 return np.sum(self.getArray())
224 Image.register(np.float32, ImageF)
225 Image.register(np.float64, ImageD)
229 ``TemplateMeta`` works by overriding the ``__instancecheck__`` and
230 ``__subclasscheck__`` special methods, and hence does not appear in
231 its registered subclasses' method resolution order or ``__bases__``
232 attributes. That means its attributes are not inherited by registered
233 subclasses. Instead, attributes added to an instance of
234 ``TemplateMeta`` are *copied* into the types registered with it. These
235 attributes will thus *replace* existing attributes in those classes
236 with the same name, and subclasses cannot delegate to base class
237 implementations of these methods.
239 Finally, abstract base classes that use ``TemplateMeta`` define a dict-
240 like interface for accessing their registered subclasses, providing
241 something like the C++ syntax for templates::
243 .. code-block:: python
245 Image[np.float32] -> ImageF
248 Both primary dtypes and aliases can be used as keys in this interface,
249 which means types with aliases will be present multiple times in the dict.
250 To obtain the sequence of unique subclasses, use the ``__subclasses__``
255 Python's built-in `super` function does not behave properly in classes
256 that have `TemplateMeta` as their metaclass (which should be rare, as
257 TemplateMeta ABCs will have base classes of their own)..
267 attrs[
"_inherited"] = {k: v
for k, v
in attrs.items()
275 attrs[
"TEMPLATE_PARAMS"] = \
276 attrs[
"_inherited"].pop(
"TEMPLATE_PARAMS", (
"dtype",))
277 attrs[
"TEMPLATE_DEFAULTS"] = \
278 attrs[
"_inherited"].pop(
"TEMPLATE_DEFAULTS",
279 (
None,)*len(attrs[
"TEMPLATE_PARAMS"]))
280 attrs[
"_registry"] = dict()
281 self = type.__new__(cls, name, bases, attrs)
283 if len(self.TEMPLATE_PARAMS) == 0:
285 "TEMPLATE_PARAMS must be a tuple with at least one element."
287 if len(self.TEMPLATE_DEFAULTS) != len(self.TEMPLATE_PARAMS):
289 "TEMPLATE_PARAMS and TEMPLATE_DEFAULTS must have same length."
302 for p, d
in zip(cls.TEMPLATE_PARAMS, cls.TEMPLATE_DEFAULTS):
303 tempKey = kwds.pop(p, d)
304 if isinstance(tempKey, np.dtype):
305 tempKey = tempKey.type
310 clz = cls._registry.
get(key[0]
if len(key) == 1
else key,
None)
312 d = {k: v
for k, v
in zip(cls.TEMPLATE_PARAMS, key)}
313 raise TypeError(
"No registered subclass for {}.".
format(d))
314 return clz(*args, **kwds)
319 if subclass
in cls._registry:
321 for v
in cls._registry.
values():
322 if issubclass(subclass, v):
329 if type(instance)
in cls._registry:
331 for v
in cls._registry.
values():
332 if isinstance(instance, v):
337 """Return a tuple of all classes that inherit from this class.
342 return tuple(
set(cls._registry.
values()))
345 """Register a subclass of this ABC with the given key (a string,
346 number, type, or other hashable).
348 Register may only be called once for a given key or a given subclass.
351 raise ValueError(
"None may not be used as a key.")
352 if subclass
in cls._registry.
values():
354 "This subclass has already registered with another key; "
355 "use alias() instead."
357 if cls._registry.setdefault(key, subclass) != subclass:
358 if len(cls.TEMPLATE_PARAMS) == 1:
359 d = {cls.TEMPLATE_PARAMS[0]: key}
361 d = {k: v
for k, v
in zip(cls.TEMPLATE_PARAMS, key)}
363 "Another subclass is already registered with {}".
format(d)
367 if cls.TEMPLATE_DEFAULTS:
368 defaults = (cls.TEMPLATE_DEFAULTS[0]
if
369 len(cls.TEMPLATE_DEFAULTS) == 1
else
370 cls.TEMPLATE_DEFAULTS)
372 conflictStr = (
"Base class has attribute {}"
373 " which is a {} method of {}."
374 " Cannot link method to base class.")
383 for name
in subclass.__dict__:
384 if name
in (
"__new__",
"__init_subclass__"):
386 obj = subclass.__dict__[name]
388 isBuiltin = isinstance(obj, types.BuiltinFunctionType)
389 isStatic = isinstance(obj, staticmethod)
390 if isBuiltin
or isStatic:
391 if hasattr(cls, name):
392 raise AttributeError(
393 conflictStr.format(name,
"static", subclass))
394 setattr(cls, name, obj)
396 elif isinstance(obj, classmethod):
397 if hasattr(cls, name):
398 raise AttributeError(
399 conflictStr.format(name,
"class", subclass))
400 setattr(cls, name, getattr(subclass, name))
402 def setattrSafe(name, value):
404 currentValue = getattr(subclass, name)
405 if currentValue != value:
406 msg = (
"subclass already has a '{}' attribute with "
409 msg.format(name, currentValue, value)
411 except AttributeError:
412 setattr(subclass, name, value)
414 if len(cls.TEMPLATE_PARAMS) == 1:
415 setattrSafe(cls.TEMPLATE_PARAMS[0], key)
416 elif len(cls.TEMPLATE_PARAMS) == len(key):
417 for p, k
in zip(cls.TEMPLATE_PARAMS, key):
421 "key must have {} elements (one for each of {})".
format(
422 len(cls.TEMPLATE_PARAMS), cls.TEMPLATE_PARAMS
426 for name, attr
in cls._inherited.
items():
427 setattr(subclass, name, attr)
430 """Add an alias that allows an existing subclass to be accessed with a
434 raise ValueError(
"None may not be used as a key.")
435 if key
in cls._registry:
436 raise KeyError(
"Cannot multiply-register key {}".
format(key))
437 primaryKey = tuple(getattr(subclass, p,
None)
438 for p
in cls.TEMPLATE_PARAMS)
439 if len(primaryKey) == 1:
441 primaryKey = primaryKey[0]
442 if cls._registry.
get(primaryKey,
None) != subclass:
443 raise ValueError(
"Subclass is not registered with this base class.")
444 cls._registry[key] = subclass
450 return cls._registry[key]
453 return iter(cls._registry)
456 return len(cls._registry)
459 return key
in cls._registry
462 """Return an iterable containing all keys (including aliases).
464 return cls._registry.
keys()
467 """Return an iterable of registered subclasses, with duplicates
468 corresponding to any aliases.
470 return cls._registry.
values()
473 """Return an iterable of (key, subclass) pairs.
475 return cls._registry.
items()
477 def get(cls, key, default=None):
478 """Return the subclass associated with the given key (including
479 aliases), or ``default`` if the key is not recognized.
481 return cls._registry.
get(key, default)