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)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def isAttributeSafeToTransfer(name, value)
daf::base::PropertySet * set
def inClass(cls, name=None)