23 from __future__
import absolute_import, division, print_function
28 __all__ = (
"continueClass",
"inClass",
"TemplateMeta")
31 INTRINSIC_SPECIAL_ATTRIBUTES = frozenset((
45 """Return True if an attribute is safe to monkeypatch-transfer to another 48 This rejects special methods that are defined automatically for all 49 classes, leaving only those explicitly defined in a class decorated by 50 `continueClass` or registered with an instance of `TemplateMeta`. 52 if name.startswith(
"__")
and (value
is getattr(object, name,
None)
or 53 name
in INTRINSIC_SPECIAL_ATTRIBUTES):
59 """Re-open the decorated class, adding any new definitions into the original. 78 orig = getattr(sys.modules[cls.__module__], cls.__name__)
85 attr = cls.__dict__.get(name,
None)
or getattr(cls, name)
87 setattr(orig, name, attr)
92 """Add the decorated function to the given class as a method. 109 Standard decorators like ``classmethod``, ``staticmethod``, and 110 ``property`` may be used *after* this decorator. Custom decorators 111 may only be used if they return an object with a ``__name__`` attribute 112 or the ``name`` optional argument is provided. 119 if hasattr(func,
"__name__"):
120 name1 = func.__name__
122 if hasattr(func,
"__func__"):
124 name1 = func.__func__.__name__
125 elif hasattr(func,
"fget"):
127 name1 = func.fget.__name__
130 "Could not guess attribute name for '{}'.".
format(func)
132 setattr(cls, name1, func)
138 """A metaclass for abstract base classes that tie together wrapped C++ 141 C++ template classes are most easily wrapped with a separate Python class 142 for each template type, which results in an unnatural Python interface. 143 TemplateMeta provides a thin layer that connects these Python classes by 144 giving them a common base class and acting as a factory to construct them 147 To use, simply create a new class with the name of the template class, and 148 use ``TemplateMeta`` as its metaclass, and then call ``register`` on each 149 of its subclasses. This registers the class with a "type key" - usually a 150 Python representation of the C++ template types. The type key must be a 151 hashable object - strings, type objects, and tuples of these (for C++ 152 classes with multiple template parameters) are good choices. Alternate 153 type keys for existing classes can be added by calling ``alias``, but only 154 after a subclass already been registered with a "primary" type key. For 155 example (using Python 3 metaclass syntax):: 158 from ._image import ImageF, ImageD 160 class Image(metaclass=TemplateMeta): 163 Image.register(np.float32, ImageF) 164 Image.register(np.float64, ImageD) 165 Image.alias("F", ImageF) 166 Image.alias("D", ImageD) 168 We have intentionally used ``numpy`` types as the primary keys for these 169 objects in this example, with strings as secondary aliases simply because 170 the primary key is added as a ``dtype`` attribute on the the registered 171 classes (so ``ImageF.dtype == numpy.float32`` in the above example). 173 This allows user code to construct objects directly using ``Image``, as 174 long as an extra ``dtype`` keyword argument is passed that matches one of 177 img = Image(52, 64, dtype=np.float32) 179 This simply forwards additional positional and keyword arguments to the 180 wrapped template class's constructor. 182 The choice of "dtype" as the name of the template parameter is also 183 configurable, and in fact multiple template parameters are also supported, 184 by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple 185 containing the names of the template parameters. A ``TEMPLATE_DEFAULTS`` 186 attribute can also be defined to a tuple of the same length containing 187 default values for the template parameters, allowing them to be omitted in 188 constructor calls. When the length of these attributes is more than one, 189 the type keys passed to ``register`` and ``alias`` should be tuple of the 190 same length; when the length of these attributes is one, type keys should 191 generally not be tuples. 193 As an aid for those writing the Python wrappers for C++ classes, 194 ``TemplateMeta`` also provides a way to add pure-Python methods and other 195 attributes to the wrapped template classes. To add a ``sum`` method to 196 all registered types, for example, we can just do:: 198 class Image(metaclass=TemplateMeta): 201 return np.sum(self.getArray()) 203 Image.register(np.float32, ImageF) 204 Image.register(np.float64, ImageD) 208 ``TemplateMeta`` works by overriding the ``__instancecheck__`` and 209 ``__subclasscheck__`` special methods, and hence does not appear in 210 its registered subclasses' method resolution order or ``__bases__`` 211 attributes. That means its attributes are not inherited by registered 212 subclasses. Instead, attributes added to an instance of 213 ``TemplateMeta`` are *copied* into the types registered with it. These 214 attributes will thus *replace* existing attributes in those classes 215 with the same name, and subclasses cannot delegate to base class 216 implementations of these methods. 218 Finally, abstract base classes that use ``TemplateMeta`` define a dict- 219 like interface for accessing their registered subclasses, providing 220 something like the C++ syntax for templates:: 222 Image[np.float32] -> ImageF 225 Both primary dtypes and aliases can be used as keys in this interface, 226 which means types with aliases will be present multiple times in the dict. 227 To obtain the sequence of unique subclasses, use the ``__subclasses__`` 237 attrs[
"_inherited"] = {k: v
for k, v
in attrs.items()
245 attrs[
"TEMPLATE_PARAMS"] = \
246 attrs[
"_inherited"].pop(
"TEMPLATE_PARAMS", (
"dtype",))
247 attrs[
"TEMPLATE_DEFAULTS"] = \
248 attrs[
"_inherited"].pop(
"TEMPLATE_DEFAULTS",
249 (
None,)*len(attrs[
"TEMPLATE_PARAMS"]))
250 attrs[
"_registry"] = dict()
251 self = type.__new__(cls, name, bases, attrs)
253 if len(self.TEMPLATE_PARAMS) == 0:
255 "TEMPLATE_PARAMS must be a tuple with at least one element." 257 if len(self.TEMPLATE_DEFAULTS) != len(self.TEMPLATE_PARAMS):
259 "TEMPLATE_PARAMS and TEMPLATE_DEFAULTS must have same length." 268 key = tuple(kwds.pop(p, d)
for p, d
in zip(self.TEMPLATE_PARAMS,
269 self.TEMPLATE_DEFAULTS))
271 cls = self._registry.
get(key[0]
if len(key) == 1
else key,
None)
273 d = {k: v
for k, v
in zip(self.TEMPLATE_PARAMS, key)}
274 raise TypeError(
"No registered subclass for {}.".
format(d))
275 return cls(*args, **kwds)
280 if subclass
in self._registry:
282 for v
in self._registry.
values():
283 if issubclass(subclass, v):
290 if type(instance)
in self._registry:
292 for v
in self._registry.
values():
293 if isinstance(instance, v):
298 """Return a tuple of all classes that inherit from this class. 303 return tuple(
set(self._registry.
values()))
306 """Register a subclass of this ABC with the given key (a string, 307 number, type, or other hashable). 309 Register may only be called once for a given key or a given subclass. 312 raise ValueError(
"None may not be used as a key.")
313 if subclass
in self._registry.
values():
315 "This subclass has already registered with another key; " 316 "use alias() instead." 318 if self._registry.setdefault(key, subclass) != subclass:
319 if len(self.TEMPLATE_PARAMS) == 1:
320 d = {self.TEMPLATE_PARAMS[0]: key}
322 d = {k: v
for k, v
in zip(self.TEMPLATE_PARAMS, key)}
324 "Another subclass is already registered with {}".
format(d)
328 if self.TEMPLATE_DEFAULTS:
329 defaults = (self.TEMPLATE_DEFAULTS[0]
if 330 len(self.TEMPLATE_DEFAULTS) == 1
else 331 self.TEMPLATE_DEFAULTS)
333 conflictStr = (
"Base Class has an attribute with the same" 334 "name as a {} method in the default subclass" 335 ". Cannot link {} method to base class")
344 for name
in subclass.__dict__:
345 if name ==
"__new__":
347 obj = subclass.__dict__[name]
349 isBuiltin = isinstance(obj, types.BuiltinFunctionType)
350 isStatic = isinstance(obj, staticmethod)
351 if isBuiltin
or isStatic:
352 if hasattr(self, name):
353 raise AttributeError(conflictStr.format(
"static"))
354 setattr(self, name, obj)
356 elif isinstance(obj, classmethod):
357 if hasattr(self, name):
358 raise AttributeError(conflictStr.format(
"class"))
359 setattr(self, name, getattr(subclass, name))
361 def setattrSafe(name, value):
363 currentValue = getattr(subclass, name)
364 if currentValue != value:
365 msg = (
"subclass already has a '{}' attribute with " 368 msg.format(name, currentValue, value)
370 except AttributeError:
371 setattr(subclass, name, value)
373 if len(self.TEMPLATE_PARAMS) == 1:
374 setattrSafe(self.TEMPLATE_PARAMS[0], key)
375 elif len(self.TEMPLATE_PARAMS) == len(key):
376 for p, k
in zip(self.TEMPLATE_PARAMS, key):
380 "key must have {} elements (one for each of {})".
format(
381 len(self.TEMPLATE_PARAMS), self.TEMPLATE_PARAMS
385 for name, attr
in self._inherited.
items():
386 setattr(subclass, name, attr)
389 """Add an alias that allows an existing subclass to be accessed with a 393 raise ValueError(
"None may not be used as a key.")
394 if key
in self._registry:
395 raise KeyError(
"Cannot multiply-register key {}".
format(key))
396 primaryKey = tuple(getattr(subclass, p,
None)
397 for p
in self.TEMPLATE_PARAMS)
398 if len(primaryKey) == 1:
400 primaryKey = primaryKey[0]
401 if self._registry.
get(primaryKey,
None) != subclass:
402 raise ValueError(
"Subclass is not registered with this base class.")
403 self._registry[key] = subclass
409 return self._registry[key]
412 return iter(self._registry)
415 return len(self._registry)
418 return key
in self._registry
421 """Return an iterable containing all keys (including aliases). 423 return self._registry.
keys()
426 """Return an iterable of registered subclasses, with duplicates 427 corresponding to any aliases. 429 return self._registry.
values()
432 """Return an iterable of (key, subclass) pairs. 434 return self._registry.
items()
436 def get(self, key, default=None):
437 """Return the subclass associated with the given key (including 438 aliases), or ``default`` if the key is not recognized. 440 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)