23 from __future__
import absolute_import, division, print_function
30 __all__ = (
"continueClass",
"inClass",
"TemplateMeta")
33 INTRINSIC_SPECIAL_ATTRIBUTES = frozenset((
47 """Return True if an attribute is safe to monkeypatch-transfer to another 50 This rejects special methods that are defined automatically for all 51 classes, leaving only those explicitly defined in a class decorated by 52 `continueClass` or registered with an instance of `TemplateMeta`. 54 if name.startswith(
"__")
and (value
is getattr(object, name,
None)
or 55 name
in INTRINSIC_SPECIAL_ATTRIBUTES):
61 """Re-open the decorated class, adding any new definitions into the original. 80 orig = getattr(sys.modules[cls.__module__], cls.__name__)
87 attr = cls.__dict__.get(name,
None)
or getattr(cls, name)
89 setattr(orig, name, attr)
94 """Add the decorated function to the given class as a method. 111 Standard decorators like ``classmethod``, ``staticmethod``, and 112 ``property`` may be used *after* this decorator. Custom decorators 113 may only be used if they return an object with a ``__name__`` attribute 114 or the ``name`` optional argument is provided. 121 if hasattr(func,
"__name__"):
122 name1 = func.__name__
124 if hasattr(func,
"__func__"):
126 name1 = func.__func__.__name__
127 elif hasattr(func,
"fget"):
129 name1 = func.fget.__name__
132 "Could not guess attribute name for '{}'.".
format(func)
134 setattr(cls, name1, func)
140 """A metaclass for abstract base classes that tie together wrapped C++ 143 C++ template classes are most easily wrapped with a separate Python class 144 for each template type, which results in an unnatural Python interface. 145 TemplateMeta provides a thin layer that connects these Python classes by 146 giving them a common base class and acting as a factory to construct them 149 To use, simply create a new class with the name of the template class, and 150 use ``TemplateMeta`` as its metaclass, and then call ``register`` on each 151 of its subclasses. This registers the class with a "type key" - usually a 152 Python representation of the C++ template types. The type key must be a 153 hashable object - strings, type objects, and tuples of these (for C++ 154 classes with multiple template parameters) are good choices. Alternate 155 type keys for existing classes can be added by calling ``alias``, but only 156 after a subclass already been registered with a "primary" type key. For 157 example (using Python 3 metaclass syntax):: 160 from ._image import ImageF, ImageD 162 class Image(metaclass=TemplateMeta): 165 Image.register(np.float32, ImageF) 166 Image.register(np.float64, ImageD) 167 Image.alias("F", ImageF) 168 Image.alias("D", ImageD) 170 We have intentionally used ``numpy`` types as the primary keys for these 171 objects in this example, with strings as secondary aliases simply because 172 the primary key is added as a ``dtype`` attribute on the the registered 173 classes (so ``ImageF.dtype == numpy.float32`` in the above example). 175 This allows user code to construct objects directly using ``Image``, as 176 long as an extra ``dtype`` keyword argument is passed that matches one of 179 img = Image(52, 64, dtype=np.float32) 181 This simply forwards additional positional and keyword arguments to the 182 wrapped template class's constructor. 184 The choice of "dtype" as the name of the template parameter is also 185 configurable, and in fact multiple template parameters are also supported, 186 by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple 187 containing the names of the template parameters. A ``TEMPLATE_DEFAULTS`` 188 attribute can also be defined to a tuple of the same length containing 189 default values for the template parameters, allowing them to be omitted in 190 constructor calls. When the length of these attributes is more than one, 191 the type keys passed to ``register`` and ``alias`` should be tuple of the 192 same length; when the length of these attributes is one, type keys should 193 generally not be tuples. 195 As an aid for those writing the Python wrappers for C++ classes, 196 ``TemplateMeta`` also provides a way to add pure-Python methods and other 197 attributes to the wrapped template classes. To add a ``sum`` method to 198 all registered types, for example, we can just do:: 200 class Image(metaclass=TemplateMeta): 203 return np.sum(self.getArray()) 205 Image.register(np.float32, ImageF) 206 Image.register(np.float64, ImageD) 210 ``TemplateMeta`` works by overriding the ``__instancecheck__`` and 211 ``__subclasscheck__`` special methods, and hence does not appear in 212 its registered subclasses' method resolution order or ``__bases__`` 213 attributes. That means its attributes are not inherited by registered 214 subclasses. Instead, attributes added to an instance of 215 ``TemplateMeta`` are *copied* into the types registered with it. These 216 attributes will thus *replace* existing attributes in those classes 217 with the same name, and subclasses cannot delegate to base class 218 implementations of these methods. 220 Finally, abstract base classes that use ``TemplateMeta`` define a dict- 221 like interface for accessing their registered subclasses, providing 222 something like the C++ syntax for templates:: 224 Image[np.float32] -> ImageF 227 Both primary dtypes and aliases can be used as keys in this interface, 228 which means types with aliases will be present multiple times in the dict. 229 To obtain the sequence of unique subclasses, use the ``__subclasses__`` 239 attrs[
"_inherited"] = {k: v
for k, v
in attrs.items()
247 attrs[
"TEMPLATE_PARAMS"] = \
248 attrs[
"_inherited"].pop(
"TEMPLATE_PARAMS", (
"dtype",))
249 attrs[
"TEMPLATE_DEFAULTS"] = \
250 attrs[
"_inherited"].pop(
"TEMPLATE_DEFAULTS",
251 (
None,)*len(attrs[
"TEMPLATE_PARAMS"]))
252 attrs[
"_registry"] = dict()
253 self = type.__new__(cls, name, bases, attrs)
255 if len(self.TEMPLATE_PARAMS) == 0:
257 "TEMPLATE_PARAMS must be a tuple with at least one element." 259 if len(self.TEMPLATE_DEFAULTS) != len(self.TEMPLATE_PARAMS):
261 "TEMPLATE_PARAMS and TEMPLATE_DEFAULTS must have same length." 275 for p, d
in zip(self.TEMPLATE_PARAMS, self.TEMPLATE_DEFAULTS):
276 tempKey = kwds.pop(p, d)
277 if isinstance(tempKey, np.dtype):
278 tempKey = tempKey.type
283 cls = self._registry.
get(key[0]
if len(key) == 1
else key,
None)
285 d = {k: v
for k, v
in zip(self.TEMPLATE_PARAMS, key)}
286 raise TypeError(
"No registered subclass for {}.".
format(d))
287 return cls(*args, **kwds)
292 if subclass
in self._registry:
294 for v
in self._registry.
values():
295 if issubclass(subclass, v):
302 if type(instance)
in self._registry:
304 for v
in self._registry.
values():
305 if isinstance(instance, v):
310 """Return a tuple of all classes that inherit from this class. 315 return tuple(
set(self._registry.
values()))
318 """Register a subclass of this ABC with the given key (a string, 319 number, type, or other hashable). 321 Register may only be called once for a given key or a given subclass. 324 raise ValueError(
"None may not be used as a key.")
325 if subclass
in self._registry.
values():
327 "This subclass has already registered with another key; " 328 "use alias() instead." 330 if self._registry.setdefault(key, subclass) != subclass:
331 if len(self.TEMPLATE_PARAMS) == 1:
332 d = {self.TEMPLATE_PARAMS[0]: key}
334 d = {k: v
for k, v
in zip(self.TEMPLATE_PARAMS, key)}
336 "Another subclass is already registered with {}".
format(d)
340 if self.TEMPLATE_DEFAULTS:
341 defaults = (self.TEMPLATE_DEFAULTS[0]
if 342 len(self.TEMPLATE_DEFAULTS) == 1
else 343 self.TEMPLATE_DEFAULTS)
345 conflictStr = (
"Base Class has an attribute with the same" 346 "name as a {} method in the default subclass" 347 ". Cannot link {} method to base class")
356 for name
in subclass.__dict__:
357 if name ==
"__new__":
359 obj = subclass.__dict__[name]
361 isBuiltin = isinstance(obj, types.BuiltinFunctionType)
362 isStatic = isinstance(obj, staticmethod)
363 if isBuiltin
or isStatic:
364 if hasattr(self, name):
365 raise AttributeError(conflictStr.format(
"static"))
366 setattr(self, name, obj)
368 elif isinstance(obj, classmethod):
369 if hasattr(self, name):
370 raise AttributeError(conflictStr.format(
"class"))
371 setattr(self, name, getattr(subclass, name))
373 def setattrSafe(name, value):
375 currentValue = getattr(subclass, name)
376 if currentValue != value:
377 msg = (
"subclass already has a '{}' attribute with " 380 msg.format(name, currentValue, value)
382 except AttributeError:
383 setattr(subclass, name, value)
385 if len(self.TEMPLATE_PARAMS) == 1:
386 setattrSafe(self.TEMPLATE_PARAMS[0], key)
387 elif len(self.TEMPLATE_PARAMS) == len(key):
388 for p, k
in zip(self.TEMPLATE_PARAMS, key):
392 "key must have {} elements (one for each of {})".
format(
393 len(self.TEMPLATE_PARAMS), self.TEMPLATE_PARAMS
397 for name, attr
in self._inherited.
items():
398 setattr(subclass, name, attr)
401 """Add an alias that allows an existing subclass to be accessed with a 405 raise ValueError(
"None may not be used as a key.")
406 if key
in self._registry:
407 raise KeyError(
"Cannot multiply-register key {}".
format(key))
408 primaryKey = tuple(getattr(subclass, p,
None)
409 for p
in self.TEMPLATE_PARAMS)
410 if len(primaryKey) == 1:
412 primaryKey = primaryKey[0]
413 if self._registry.
get(primaryKey,
None) != subclass:
414 raise ValueError(
"Subclass is not registered with this base class.")
415 self._registry[key] = subclass
421 return self._registry[key]
424 return iter(self._registry)
427 return len(self._registry)
430 return key
in self._registry
433 """Return an iterable containing all keys (including aliases). 435 return self._registry.
keys()
438 """Return an iterable of registered subclasses, with duplicates 439 corresponding to any aliases. 441 return self._registry.
values()
444 """Return an iterable of (key, subclass) pairs. 446 return self._registry.
items()
448 def get(self, key, default=None):
449 """Return the subclass associated with the given key (including 450 aliases), or ``default`` if the key is not recognized. 452 return self._registry.
get(key, default)
def isAttributeSafeToTransfer(name, value)
daf::base::PropertySet * set
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def inClass(cls, name=None)