__init__.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. """
  2. .. note:
  3. If you are looking for overrides for NumPy-specific methods, see the
  4. documentation for :obj:`unumpy`. This page explains how to write
  5. back-ends and multimethods.
  6. ``uarray`` is built around a back-end protocol, and overridable multimethods.
  7. It is necessary to define multimethods for back-ends to be able to override them.
  8. See the documentation of :obj:`generate_multimethod` on how to write multimethods.
  9. Let's start with the simplest:
  10. ``__ua_domain__`` defines the back-end *domain*. The domain consists of period-
  11. separated string consisting of the modules you extend plus the submodule. For
  12. example, if a submodule ``module2.submodule`` extends ``module1``
  13. (i.e., it exposes dispatchables marked as types available in ``module1``),
  14. then the domain string should be ``"module1.module2.submodule"``.
  15. For the purpose of this demonstration, we'll be creating an object and setting
  16. its attributes directly. However, note that you can use a module or your own type
  17. as a backend as well.
  18. >>> class Backend: pass
  19. >>> be = Backend()
  20. >>> be.__ua_domain__ = "ua_examples"
  21. It might be useful at this point to sidetrack to the documentation of
  22. :obj:`generate_multimethod` to find out how to generate a multimethod
  23. overridable by :obj:`uarray`. Needless to say, writing a backend and
  24. creating multimethods are mostly orthogonal activities, and knowing
  25. one doesn't necessarily require knowledge of the other, although it
  26. is certainly helpful. We expect core API designers/specifiers to write the
  27. multimethods, and implementors to override them. But, as is often the case,
  28. similar people write both.
  29. Without further ado, here's an example multimethod:
  30. >>> import uarray as ua
  31. >>> from uarray import Dispatchable
  32. >>> def override_me(a, b):
  33. ... return Dispatchable(a, int),
  34. >>> def override_replacer(args, kwargs, dispatchables):
  35. ... return (dispatchables[0], args[1]), {}
  36. >>> overridden_me = ua.generate_multimethod(
  37. ... override_me, override_replacer, "ua_examples"
  38. ... )
  39. Next comes the part about overriding the multimethod. This requires
  40. the ``__ua_function__`` protocol, and the ``__ua_convert__``
  41. protocol. The ``__ua_function__`` protocol has the signature
  42. ``(method, args, kwargs)`` where ``method`` is the passed
  43. multimethod, ``args``/``kwargs`` specify the arguments and ``dispatchables``
  44. is the list of converted dispatchables passed in.
  45. >>> def __ua_function__(method, args, kwargs):
  46. ... return method.__name__, args, kwargs
  47. >>> be.__ua_function__ = __ua_function__
  48. The other protocol of interest is the ``__ua_convert__`` protocol. It has the
  49. signature ``(dispatchables, coerce)``. When ``coerce`` is ``False``, conversion
  50. between the formats should ideally be an ``O(1)`` operation, but it means that
  51. no memory copying should be involved, only views of the existing data.
  52. >>> def __ua_convert__(dispatchables, coerce):
  53. ... for d in dispatchables:
  54. ... if d.type is int:
  55. ... if coerce and d.coercible:
  56. ... yield str(d.value)
  57. ... else:
  58. ... yield d.value
  59. >>> be.__ua_convert__ = __ua_convert__
  60. Now that we have defined the backend, the next thing to do is to call the multimethod.
  61. >>> with ua.set_backend(be):
  62. ... overridden_me(1, "2")
  63. ('override_me', (1, '2'), {})
  64. Note that the marked type has no effect on the actual type of the passed object.
  65. We can also coerce the type of the input.
  66. >>> with ua.set_backend(be, coerce=True):
  67. ... overridden_me(1, "2")
  68. ... overridden_me(1.0, "2")
  69. ('override_me', ('1', '2'), {})
  70. ('override_me', ('1.0', '2'), {})
  71. Another feature is that if you remove ``__ua_convert__``, the arguments are not
  72. converted at all and it's up to the backend to handle that.
  73. >>> del be.__ua_convert__
  74. >>> with ua.set_backend(be):
  75. ... overridden_me(1, "2")
  76. ('override_me', (1, '2'), {})
  77. You also have the option to return ``NotImplemented``, in which case processing moves on
  78. to the next back-end, which in this case, doesn't exist. The same applies to
  79. ``__ua_convert__``.
  80. >>> be.__ua_function__ = lambda *a, **kw: NotImplemented
  81. >>> with ua.set_backend(be):
  82. ... overridden_me(1, "2")
  83. Traceback (most recent call last):
  84. ...
  85. uarray.BackendNotImplementedError: ...
  86. The last possibility is if we don't have ``__ua_convert__``, in which case the job is left
  87. up to ``__ua_function__``, but putting things back into arrays after conversion will not be
  88. possible.
  89. """
  90. from ._backend import *
  91. __version__ = '0.8.8.dev0+aa94c5a4.scipy'