23 from __future__
import absolute_import, division, print_function
27 __all__ = (
"continueClass",
"inClass",
"TemplateMeta")
30 INTRINSIC_SPECIAL_ATTRIBUTES = frozenset((
44 """Return True if an attribute is safe to monkeypatch-transfer to another
47 This rejects special methods that are defined automatically for all
48 classes, leaving only those explicitly defined in a class decorated by
49 `continueClass` or registered with an instance of `TemplateMeta`.
51 if name.startswith(
"__")
and (value
is getattr(object, name,
None)
or
52 name
in INTRINSIC_SPECIAL_ATTRIBUTES):
58 """Re-open the decorated class, adding any new definitions into the original.
77 orig = getattr(sys.modules[cls.__module__], cls.__name__)
84 attr = cls.__dict__.get(name,
None)
or getattr(cls, name)
86 setattr(orig, name, attr)
91 """Add the decorated function to the given class as a method.
108 Standard decorators like ``classmethod``, ``staticmethod``, and
109 ``property`` may be used *after* this decorator. Custom decorators
110 may only be used if they return an object with a ``__name__`` attribute
111 or the ``name`` optional argument is provided.
118 if hasattr(func,
"__name__"):
119 name1 = func.__name__
121 if hasattr(func,
"__func__"):
123 name1 = func.__func__.__name__
124 elif hasattr(func,
"fget"):
126 name1 = func.fget.__name__
129 "Could not guess attribute name for '{}'.".format(func)
131 setattr(cls, name1, func)
137 """A metaclass for abstract base classes that tie together wrapped C++
140 C++ template classes are most easily wrapped with a separate Python class
141 for each template type, which results in an unnatural Python interface.
142 TemplateMeta provides a thin layer that connects these Python classes by
143 giving them a common base class and acting as a factory to construct them
146 To use, simply create a new class with the name of the template class, and
147 use ``TemplateMeta`` as its metaclass, and then call ``register`` on each
148 of its subclasses. This registers the class with a "type key" - usually a
149 Python representation of the C++ template types. The type key must be a
150 hashable object - strings, type objects, and tuples of these (for C++
151 classes with multiple template parameters) are good choices. Alternate
152 type keys for existing classes can be added by calling ``alias``, but only
153 after a subclass already been registered with a "primary" type key. For
154 example (using Python 3 metaclass syntax)::
157 from ._image import ImageF, ImageD
159 class Image(metaclass=TemplateMeta):
162 Image.register(np.float32, ImageF)
163 Image.register(np.float64, ImageD)
164 Image.alias("F", ImageF)
165 Image.alias("D", ImageD)
167 We have intentionally used ``numpy`` types as the primary keys for these
168 objects in this example, with strings as secondary aliases simply because
169 the primary key is added as a ``dtype`` attribute on the the registered
170 classes (so ``ImageF.dtype == numpy.float32`` in the above example).
172 This allows user code to construct objects directly using ``Image``, as
173 long as an extra ``dtype`` keyword argument is passed that matches one of
176 img = Image(52, 64, dtype=np.float32)
178 This simply forwards additional positional and keyword arguments to the
179 wrapped template class's constructor.
181 The choice of "dtype" as the name of the template parameter is also
182 configurable, and in fact multiple template parameters are also supported,
183 by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple
184 containing the names of the template parameters. A ``TEMPLATE_DEFAULTS``
185 attribute can also be defined to a tuple of the same length containing
186 default values for the template parameters, allowing them to be omitted in
187 constructor calls. When the length of these attributes is more than one,
188 the type keys passed to ``register`` and ``alias`` should be tuple of the
189 same length; when the length of these attributes is one, type keys should
190 generally not be tuples.
192 As an aid for those writing the Python wrappers for C++ classes,
193 ``TemplateMeta`` also provides a way to add pure-Python methods and other
194 attributes to the wrapped template classes. To add a ``sum`` method to
195 all registered types, for example, we can just do::
197 class Image(metaclass=TemplateMeta):
200 return np.sum(self.getArray())
202 Image.register(np.float32, ImageF)
203 Image.register(np.float64, ImageD)
207 ``TemplateMeta`` works by overriding the ``__instancecheck__`` and
208 ``__subclasscheck__`` special methods, and hence does not appear in
209 its registered subclasses' method resolution order or ``__bases__``
210 attributes. That means its attributes are not inherited by registered
211 subclasses. Instead, attributes added to an instance of
212 ``TemplateMeta`` are *copied* into the types registered with it. These
213 attributes will thus *replace* existing attributes in those classes
214 with the same name, and subclasses cannot delegate to base class
215 implementations of these methods.
217 Finally, abstract base classes that use ``TemplateMeta`` define a dict-
218 like interface for accessing their registered subclasses, providing
219 something like the C++ syntax for templates::
221 Image[np.float32] -> ImageF
224 Both primary dtypes and aliases can be used as keys in this interface,
225 which means types with aliases will be present multiple times in the dict.
226 To obtain the sequence of unique subclasses, use the ``__subclasses__``
236 attrs[
"_inherited"] = {k: v
for k, v
in attrs.items()
244 attrs[
"TEMPLATE_PARAMS"] = \
245 attrs[
"_inherited"].pop(
"TEMPLATE_PARAMS", (
"dtype",))
246 attrs[
"TEMPLATE_DEFAULTS"] = \
247 attrs[
"_inherited"].pop(
"TEMPLATE_DEFAULTS",
248 (
None,)*len(attrs[
"TEMPLATE_PARAMS"]))
249 attrs[
"_registry"] = dict()
250 self = type.__new__(cls, name, bases, attrs)
252 if len(self.TEMPLATE_PARAMS) == 0:
254 "TEMPLATE_PARAMS must be a tuple with at least one element."
256 if len(self.TEMPLATE_DEFAULTS) != len(self.TEMPLATE_PARAMS):
258 "TEMPLATE_PARAMS and TEMPLATE_DEFAULTS must have same length."
267 key = tuple(kwds.pop(p, d)
for p, d
in zip(self.TEMPLATE_PARAMS,
268 self.TEMPLATE_DEFAULTS))
270 cls = self._registry.get(key[0]
if len(key) == 1
else key,
None)
272 d = {k: v
for k, v
in zip(self.TEMPLATE_PARAMS, key)}
273 raise TypeError(
"No registered subclass for {}.".format(d))
274 return cls(*args, **kwds)
279 if subclass
in self._registry:
281 for v
in self._registry.values():
282 if issubclass(subclass, v):
289 if type(instance)
in self._registry:
291 for v
in self._registry.values():
292 if isinstance(instance, v):
297 """Return a tuple of all classes that inherit from this class.
302 return tuple(set(self._registry.values()))
305 """Register a subclass of this ABC with the given key (a string,
306 number, type, or other hashable).
308 Register may only be called once for a given key or a given subclass.
311 raise ValueError(
"None may not be used as a key.")
312 if subclass
in self._registry.values():
314 "This subclass has already registered with another key; "
315 "use alias() instead."
317 if self._registry.setdefault(key, subclass) != subclass:
318 if len(self.TEMPLATE_PARAMS) == 1:
319 d = {self.TEMPLATE_PARAMS[0]: key}
321 d = {k: v
for k, v
in zip(self.TEMPLATE_PARAMS, key)}
323 "Another subclass is already registered with {}".format(d)
326 def setattrSafe(name, value):
328 currentValue = getattr(subclass, name)
329 if currentValue != value:
330 msg = (
"subclass already has a '{}' attribute with "
333 msg.format(name, currentValue, value)
335 except AttributeError:
336 setattr(subclass, name, value)
338 if len(self.TEMPLATE_PARAMS) == 1:
339 setattrSafe(self.TEMPLATE_PARAMS[0], key)
340 elif len(self.TEMPLATE_PARAMS) == len(key):
341 for p, k
in zip(self.TEMPLATE_PARAMS, key):
345 "key must have {} elements (one for each of {})".format(
346 len(self.TEMPLATE_PARAMS), self.TEMPLATE_PARAMS
350 for name, attr
in self._inherited.items():
351 setattr(subclass, name, attr)
354 """Add an alias that allows an existing subclass to be accessed with a
358 raise ValueError(
"None may not be used as a key.")
359 if key
in self._registry:
360 raise KeyError(
"Cannot multiply-register key {}".format(key))
361 primaryKey = tuple(getattr(subclass, p,
None)
362 for p
in self.TEMPLATE_PARAMS)
363 if len(primaryKey) == 1:
365 primaryKey = primaryKey[0]
366 if self._registry.get(primaryKey,
None) != subclass:
367 raise ValueError(
"Subclass is not registered with this base class.")
368 self._registry[key] = subclass
374 return self._registry[key]
377 return iter(self._registry)
380 return len(self._registry)
383 return key
in self._registry
386 """Return an iterable containing all keys (including aliases).
388 return self._registry.keys()
391 """Return an iterable of registered subclasses, with duplicates
392 corresponding to any aliases.
394 return self._registry.values()
397 """Return an iterable of (key, subclass) pairs.
399 return self._registry.items()
401 def get(self, key, default=None):
402 """Return the subclass associated with the given key (including
403 aliases), or ``default`` if the key is not recognized.
405 return self._registry.get(key, default)
def isAttributeSafeToTransfer