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 83 orig = getattr(sys.modules[cls.__module__], cls.__name__)
90 attr = cls.__dict__.get(name,
None)
or getattr(cls, name)
92 setattr(orig, name, attr)
97 """Add the decorated function to the given class as a method. 101 .. code-block:: python 112 .. code-block:: python 118 Standard decorators like ``classmethod``, ``staticmethod``, and 119 ``property`` may be used *after* this decorator. Custom decorators 120 may only be used if they return an object with a ``__name__`` attribute 121 or the ``name`` optional argument is provided. 128 if hasattr(func,
"__name__"):
129 name1 = func.__name__
131 if hasattr(func,
"__func__"):
133 name1 = func.__func__.__name__
134 elif hasattr(func,
"fget"):
136 name1 = func.fget.__name__
139 "Could not guess attribute name for '{}'.".
format(func)
141 setattr(cls, name1, func)
147 """A metaclass for abstract base classes that tie together wrapped C++ 150 C++ template classes are most easily wrapped with a separate Python class 151 for each template type, which results in an unnatural Python interface. 152 TemplateMeta provides a thin layer that connects these Python classes by 153 giving them a common base class and acting as a factory to construct them 156 To use, simply create a new class with the name of the template class, and 157 use ``TemplateMeta`` as its metaclass, and then call ``register`` on each 158 of its subclasses. This registers the class with a "type key" - usually a 159 Python representation of the C++ template types. The type key must be a 160 hashable object - strings, type objects, and tuples of these (for C++ 161 classes with multiple template parameters) are good choices. Alternate 162 type keys for existing classes can be added by calling ``alias``, but only 163 after a subclass already been registered with a "primary" type key. For 166 .. code-block:: python 169 from ._image import ImageF, ImageD 171 class Image(metaclass=TemplateMeta): 174 Image.register(np.float32, ImageF) 175 Image.register(np.float64, ImageD) 176 Image.alias("F", ImageF) 177 Image.alias("D", ImageD) 179 We have intentionally used ``numpy`` types as the primary keys for these 180 objects in this example, with strings as secondary aliases simply because 181 the primary key is added as a ``dtype`` attribute on the the registered 182 classes (so ``ImageF.dtype == numpy.float32`` in the above example). 184 This allows user code to construct objects directly using ``Image``, as 185 long as an extra ``dtype`` keyword argument is passed that matches one of 188 .. code-block:: python 190 img = Image(52, 64, dtype=np.float32) 192 This simply forwards additional positional and keyword arguments to the 193 wrapped template class's constructor. 195 The choice of "dtype" as the name of the template parameter is also 196 configurable, and in fact multiple template parameters are also supported, 197 by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple 198 containing the names of the template parameters. A ``TEMPLATE_DEFAULTS`` 199 attribute can also be defined to a tuple of the same length containing 200 default values for the template parameters, allowing them to be omitted in 201 constructor calls. When the length of these attributes is more than one, 202 the type keys passed to ``register`` and ``alias`` should be tuple of the 203 same length; when the length of these attributes is one, type keys should 204 generally not be tuples. 206 As an aid for those writing the Python wrappers for C++ classes, 207 ``TemplateMeta`` also provides a way to add pure-Python methods and other 208 attributes to the wrapped template classes. To add a ``sum`` method to 209 all registered types, for example, we can just do:: 211 .. code-block:: python 213 class Image(metaclass=TemplateMeta): 216 return np.sum(self.getArray()) 218 Image.register(np.float32, ImageF) 219 Image.register(np.float64, ImageD) 223 ``TemplateMeta`` works by overriding the ``__instancecheck__`` and 224 ``__subclasscheck__`` special methods, and hence does not appear in 225 its registered subclasses' method resolution order or ``__bases__`` 226 attributes. That means its attributes are not inherited by registered 227 subclasses. Instead, attributes added to an instance of 228 ``TemplateMeta`` are *copied* into the types registered with it. These 229 attributes will thus *replace* existing attributes in those classes 230 with the same name, and subclasses cannot delegate to base class 231 implementations of these methods. 233 Finally, abstract base classes that use ``TemplateMeta`` define a dict- 234 like interface for accessing their registered subclasses, providing 235 something like the C++ syntax for templates:: 237 .. code-block:: python 239 Image[np.float32] -> ImageF 242 Both primary dtypes and aliases can be used as keys in this interface, 243 which means types with aliases will be present multiple times in the dict. 244 To obtain the sequence of unique subclasses, use the ``__subclasses__`` 254 attrs[
"_inherited"] = {k: v
for k, v
in attrs.items()
262 attrs[
"TEMPLATE_PARAMS"] = \
263 attrs[
"_inherited"].pop(
"TEMPLATE_PARAMS", (
"dtype",))
264 attrs[
"TEMPLATE_DEFAULTS"] = \
265 attrs[
"_inherited"].pop(
"TEMPLATE_DEFAULTS",
266 (
None,)*len(attrs[
"TEMPLATE_PARAMS"]))
267 attrs[
"_registry"] = dict()
268 self = type.__new__(cls, name, bases, attrs)
270 if len(self.TEMPLATE_PARAMS) == 0:
272 "TEMPLATE_PARAMS must be a tuple with at least one element." 274 if len(self.TEMPLATE_DEFAULTS) != len(self.TEMPLATE_PARAMS):
276 "TEMPLATE_PARAMS and TEMPLATE_DEFAULTS must have same length." 289 for p, d
in zip(cls.TEMPLATE_PARAMS, cls.TEMPLATE_DEFAULTS):
290 tempKey = kwds.pop(p, d)
291 if isinstance(tempKey, np.dtype):
292 tempKey = tempKey.type
297 clz = cls._registry.
get(key[0]
if len(key) == 1
else key,
None)
299 d = {k: v
for k, v
in zip(cls.TEMPLATE_PARAMS, key)}
300 raise TypeError(
"No registered subclass for {}.".
format(d))
301 return clz(*args, **kwds)
306 if subclass
in cls._registry:
308 for v
in cls._registry.
values():
309 if issubclass(subclass, v):
316 if type(instance)
in cls._registry:
318 for v
in cls._registry.
values():
319 if isinstance(instance, v):
324 """Return a tuple of all classes that inherit from this class. 329 return tuple(
set(cls._registry.
values()))
332 """Register a subclass of this ABC with the given key (a string, 333 number, type, or other hashable). 335 Register may only be called once for a given key or a given subclass. 338 raise ValueError(
"None may not be used as a key.")
339 if subclass
in cls._registry.
values():
341 "This subclass has already registered with another key; " 342 "use alias() instead." 344 if cls._registry.setdefault(key, subclass) != subclass:
345 if len(cls.TEMPLATE_PARAMS) == 1:
346 d = {cls.TEMPLATE_PARAMS[0]: key}
348 d = {k: v
for k, v
in zip(cls.TEMPLATE_PARAMS, key)}
350 "Another subclass is already registered with {}".
format(d)
354 if cls.TEMPLATE_DEFAULTS:
355 defaults = (cls.TEMPLATE_DEFAULTS[0]
if 356 len(cls.TEMPLATE_DEFAULTS) == 1
else 357 cls.TEMPLATE_DEFAULTS)
359 conflictStr = (
"Base class has attribute {}" 360 " which is a {} method of {}." 361 " Cannot link method to base class.")
370 for name
in subclass.__dict__:
371 if name
in (
"__new__",
"__init_subclass__"):
373 obj = subclass.__dict__[name]
375 isBuiltin = isinstance(obj, types.BuiltinFunctionType)
376 isStatic = isinstance(obj, staticmethod)
377 if isBuiltin
or isStatic:
378 if hasattr(cls, name):
379 raise AttributeError(
380 conflictStr.format(name,
"static", subclass))
381 setattr(cls, name, obj)
383 elif isinstance(obj, classmethod):
384 if hasattr(cls, name):
385 raise AttributeError(
386 conflictStr.format(name,
"class", subclass))
387 setattr(cls, name, getattr(subclass, name))
389 def setattrSafe(name, value):
391 currentValue = getattr(subclass, name)
392 if currentValue != value:
393 msg = (
"subclass already has a '{}' attribute with " 396 msg.format(name, currentValue, value)
398 except AttributeError:
399 setattr(subclass, name, value)
401 if len(cls.TEMPLATE_PARAMS) == 1:
402 setattrSafe(cls.TEMPLATE_PARAMS[0], key)
403 elif len(cls.TEMPLATE_PARAMS) == len(key):
404 for p, k
in zip(cls.TEMPLATE_PARAMS, key):
408 "key must have {} elements (one for each of {})".
format(
409 len(cls.TEMPLATE_PARAMS), cls.TEMPLATE_PARAMS
413 for name, attr
in cls._inherited.
items():
414 setattr(subclass, name, attr)
417 """Add an alias that allows an existing subclass to be accessed with a 421 raise ValueError(
"None may not be used as a key.")
422 if key
in cls._registry:
423 raise KeyError(
"Cannot multiply-register key {}".
format(key))
424 primaryKey = tuple(getattr(subclass, p,
None)
425 for p
in cls.TEMPLATE_PARAMS)
426 if len(primaryKey) == 1:
428 primaryKey = primaryKey[0]
429 if cls._registry.
get(primaryKey,
None) != subclass:
430 raise ValueError(
"Subclass is not registered with this base class.")
431 cls._registry[key] = subclass
437 return cls._registry[key]
440 return iter(cls._registry)
443 return len(cls._registry)
446 return key
in cls._registry
449 """Return an iterable containing all keys (including aliases). 451 return cls._registry.
keys()
454 """Return an iterable of registered subclasses, with duplicates 455 corresponding to any aliases. 457 return cls._registry.
values()
460 """Return an iterable of (key, subclass) pairs. 462 return cls._registry.
items()
464 def get(cls, key, default=None):
465 """Return the subclass associated with the given key (including 466 aliases), or ``default`` if the key is not recognized. 468 return cls._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)