uarray 0.9.2__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
uarray/_backend.py ADDED
@@ -0,0 +1,800 @@
1
+ from __future__ import annotations
2
+
3
+ import types
4
+ import inspect
5
+ import functools
6
+ from . import _uarray
7
+ import copyreg
8
+ import pickle
9
+ import contextlib
10
+ import warnings
11
+
12
+ from collections.abc import Callable, Generator, Iterable
13
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar, Literal, overload, no_type_check
14
+
15
+ from ._uarray import (
16
+ BackendNotImplementedError,
17
+ _Function,
18
+ _SkipBackendContext,
19
+ _SetBackendContext,
20
+ _BackendState,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from typing_extensions import ParamSpec
25
+ from ._typing import (
26
+ _SupportsUA,
27
+ _PartialDispatchable,
28
+ _ReplacerFunc,
29
+ )
30
+
31
+ _P = ParamSpec("_P")
32
+
33
+ _T = TypeVar("_T")
34
+ _T2 = TypeVar("_T2")
35
+ _Self = TypeVar("_Self")
36
+ _TT = TypeVar("_TT", bound=type)
37
+
38
+ __all__ = [
39
+ "set_backend",
40
+ "set_global_backend",
41
+ "skip_backend",
42
+ "register_backend",
43
+ "determine_backend",
44
+ "determine_backend_multi",
45
+ "clear_backends",
46
+ "create_multimethod",
47
+ "generate_multimethod",
48
+ "_Function",
49
+ "BackendNotImplementedError",
50
+ "Dispatchable",
51
+ "wrap_single_convertor",
52
+ "wrap_single_convertor_instance",
53
+ "all_of_type",
54
+ "mark_as",
55
+ "set_state",
56
+ "get_state",
57
+ "reset_state",
58
+ "_BackendState",
59
+ "_SkipBackendContext",
60
+ "_SetBackendContext",
61
+ ]
62
+
63
+
64
+ @no_type_check
65
+ def unpickle_function(
66
+ mod_name: str,
67
+ qname: str,
68
+ self_: object,
69
+ ) -> Callable[..., Any]:
70
+ import importlib
71
+
72
+ try:
73
+ module = importlib.import_module(mod_name)
74
+ qname = qname.split(".")
75
+ func = module
76
+ for q in qname:
77
+ func = getattr(func, q)
78
+
79
+ if self_ is not None:
80
+ func = types.MethodType(func, self_)
81
+
82
+ return func
83
+ except (ImportError, AttributeError) as e:
84
+ from pickle import UnpicklingError
85
+
86
+ raise UnpicklingError from e
87
+
88
+
89
+ @no_type_check
90
+ def pickle_function(
91
+ func: Callable[..., Any],
92
+ ) -> tuple[
93
+ Callable[[str, str, object], Callable[..., Any]],
94
+ tuple[str, str, Any | None],
95
+ ]:
96
+ mod_name = getattr(func, "__module__", None)
97
+ qname = getattr(func, "__qualname__", None)
98
+ self_ = getattr(func, "__self__", None)
99
+
100
+ try:
101
+ test = unpickle_function(mod_name, qname, self_)
102
+ except pickle.UnpicklingError:
103
+ test = None
104
+
105
+ if test is not func:
106
+ raise pickle.PicklingError(
107
+ "Can't pickle {}: it's not the same object as {}".format(func, test)
108
+ )
109
+
110
+ return unpickle_function, (mod_name, qname, self_)
111
+
112
+
113
+ def pickle_state(
114
+ state: _BackendState,
115
+ ) -> tuple[
116
+ Callable[[dict[str, Any], dict[str, Any], bool], _BackendState],
117
+ tuple[dict[str, Any], dict[str, Any], bool],
118
+ ]:
119
+ return _uarray._BackendState._unpickle, state._pickle()
120
+
121
+
122
+ def pickle_set_backend_context(
123
+ ctx: _SetBackendContext,
124
+ ) -> tuple[type[_SetBackendContext], tuple[_SupportsUA, bool, bool],]:
125
+ return _SetBackendContext, ctx._pickle()
126
+
127
+
128
+ def pickle_skip_backend_context(
129
+ ctx: _SkipBackendContext,
130
+ ) -> tuple[type[_SkipBackendContext], tuple[_SupportsUA],]:
131
+ return _SkipBackendContext, ctx._pickle()
132
+
133
+
134
+ # TODO: Remove the `if` block once python/typeshed#7415
135
+ # has been integrated into mypy
136
+ if not TYPE_CHECKING:
137
+ copyreg.pickle(_Function, pickle_function)
138
+ copyreg.pickle(_uarray._BackendState, pickle_state)
139
+ copyreg.pickle(_SetBackendContext, pickle_set_backend_context)
140
+ copyreg.pickle(_SkipBackendContext, pickle_skip_backend_context)
141
+
142
+
143
+ def get_state() -> _BackendState:
144
+ """
145
+ Returns an opaque object containing the current state of all the backends.
146
+
147
+ Can be used for synchronization between threads/processes.
148
+
149
+ See Also
150
+ --------
151
+ set_state
152
+ Sets the state returned by this function.
153
+ """
154
+ return _uarray.get_state()
155
+
156
+
157
+ @contextlib.contextmanager
158
+ def reset_state() -> Generator[None, None, None]:
159
+ """
160
+ Returns a context manager that resets all state once exited.
161
+
162
+ See Also
163
+ --------
164
+ set_state
165
+ Context manager that sets the backend state.
166
+ get_state
167
+ Gets a state to be set by this context manager.
168
+ """
169
+ with set_state(get_state()):
170
+ yield
171
+
172
+
173
+ @contextlib.contextmanager
174
+ def set_state(state: _BackendState) -> Generator[None, None, None]:
175
+ """
176
+ A context manager that sets the state of the backends to one returned by :obj:`get_state`.
177
+
178
+ See Also
179
+ --------
180
+ get_state
181
+ Gets a state to be set by this context manager.
182
+ """
183
+ old_state = get_state()
184
+ _uarray.set_state(state)
185
+ try:
186
+ yield
187
+ finally:
188
+ _uarray.set_state(old_state, True)
189
+
190
+
191
+ def create_multimethod(
192
+ *args: Any,
193
+ **kwargs: Any,
194
+ ) -> Callable[[Callable[_P, tuple[Dispatchable[Any, Any], ...]]], _Function[_P]]:
195
+ """
196
+ Creates a decorator for generating multimethods.
197
+
198
+ This function creates a decorator that can be used with an argument
199
+ extractor in order to generate a multimethod. Other than for the
200
+ argument extractor, all arguments are passed on to
201
+ :obj:`generate_multimethod`.
202
+
203
+ See Also
204
+ --------
205
+ generate_multimethod
206
+ Generates a multimethod.
207
+ """
208
+
209
+ def wrapper(a):
210
+ return generate_multimethod(a, *args, **kwargs)
211
+
212
+ return wrapper
213
+
214
+
215
+ def generate_multimethod(
216
+ argument_extractor: Callable[_P, tuple[Dispatchable[Any, Any], ...]],
217
+ argument_replacer: _ReplacerFunc,
218
+ domain: str,
219
+ default: None | Callable[..., Any] = None,
220
+ ) -> _Function[_P]:
221
+ """
222
+ Generates a multimethod.
223
+
224
+ Parameters
225
+ ----------
226
+ argument_extractor : ArgumentExtractorType
227
+ A callable which extracts the dispatchable arguments. Extracted arguments
228
+ should be marked by the :obj:`Dispatchable` class. It has the same signature
229
+ as the desired multimethod.
230
+ argument_replacer : ArgumentReplacerType
231
+ A callable with the signature (args, kwargs, dispatchables), which should also
232
+ return an (args, kwargs) pair with the dispatchables replaced inside the args/kwargs.
233
+ domain : str
234
+ A string value indicating the domain of this multimethod.
235
+ default: Optional[Callable], optional
236
+ The default implementation of this multimethod, where ``None`` (the default) specifies
237
+ there is no default implementation.
238
+
239
+ Examples
240
+ --------
241
+ In this example, ``a`` is to be dispatched over, so we return it, while marking it as an ``int``.
242
+ The trailing comma is needed because the args have to be returned as an iterable.
243
+
244
+ >>> def override_me(a, b):
245
+ ... return Dispatchable(a, int),
246
+
247
+ Next, we define the argument replacer that replaces the dispatchables inside args/kwargs with the
248
+ supplied ones.
249
+
250
+ >>> def override_replacer(args, kwargs, dispatchables):
251
+ ... return (dispatchables[0], args[1]), {}
252
+
253
+ Next, we define the multimethod.
254
+
255
+ >>> overridden_me = generate_multimethod(
256
+ ... override_me, override_replacer, "ua_examples"
257
+ ... )
258
+
259
+ Notice that there's no default implementation, unless you supply one.
260
+
261
+ >>> overridden_me(1, "a")
262
+ Traceback (most recent call last):
263
+ ...
264
+ uarray.BackendNotImplementedError: ...
265
+
266
+ >>> overridden_me2 = generate_multimethod(
267
+ ... override_me, override_replacer, "ua_examples", default=lambda x, y: (x, y)
268
+ ... )
269
+ >>> overridden_me2(1, "a")
270
+ (1, 'a')
271
+
272
+ See Also
273
+ --------
274
+ uarray
275
+ See the module documentation for how to override the method by creating backends.
276
+ """
277
+ kw_defaults, arg_defaults, opts = get_defaults(argument_extractor)
278
+ ua_func = _Function(
279
+ argument_extractor,
280
+ argument_replacer,
281
+ domain,
282
+ arg_defaults,
283
+ kw_defaults,
284
+ default,
285
+ )
286
+
287
+ return functools.update_wrapper(ua_func, argument_extractor) # type: ignore[return-value]
288
+
289
+
290
+ def set_backend(
291
+ backend: _SupportsUA,
292
+ coerce: bool = False,
293
+ only: bool = False,
294
+ ) -> _SetBackendContext:
295
+ """
296
+ A context manager that sets the preferred backend.
297
+
298
+ Parameters
299
+ ----------
300
+ backend
301
+ The backend to set.
302
+ coerce
303
+ Whether or not to coerce to a specific backend's types. Implies ``only``.
304
+ only
305
+ Whether or not this should be the last backend to try.
306
+
307
+ See Also
308
+ --------
309
+ skip_backend: A context manager that allows skipping of backends.
310
+ set_global_backend: Set a single, global backend for a domain.
311
+ """
312
+ # Deprecated: 2022-08-17, To be removed: 2023-08-17
313
+ # See gh-237 and https://discuss.scientific-python.org/t/requirements-and-discussion-of-a-type-dispatcher-for-the-ecosystem/157/40
314
+ warnings.warn("uarray.skip_backend is deprecated, please migrate to scoped backends.", category=DeprecationWarning)
315
+ try:
316
+ return backend.__ua_cache__["set", coerce, only]
317
+ except AttributeError:
318
+ backend.__ua_cache__ = {} # type: ignore[misc]
319
+ except KeyError:
320
+ pass
321
+
322
+ ctx = _SetBackendContext(backend, coerce, only)
323
+ backend.__ua_cache__["set", coerce, only] = ctx
324
+ return ctx
325
+
326
+
327
+ def skip_backend(backend: _SupportsUA) -> _SkipBackendContext:
328
+ """
329
+ A context manager that allows one to skip a given backend from processing
330
+ entirely. This allows one to use another backend's code in a library that
331
+ is also a consumer of the same backend.
332
+
333
+ Parameters
334
+ ----------
335
+ backend
336
+ The backend to skip.
337
+
338
+ See Also
339
+ --------
340
+ set_backend: A context manager that allows setting of backends.
341
+ set_global_backend: Set a single, global backend for a domain.
342
+ """
343
+ try:
344
+ return backend.__ua_cache__["skip"]
345
+ except AttributeError:
346
+ backend.__ua_cache__ = {} # type: ignore[misc]
347
+ except KeyError:
348
+ pass
349
+
350
+ ctx = _SkipBackendContext(backend)
351
+ backend.__ua_cache__["skip"] = ctx
352
+ return ctx
353
+
354
+
355
+ def get_defaults(
356
+ f: Callable[..., Any],
357
+ ) -> tuple[dict[str, Any], tuple[Any, ...], set[str]]:
358
+ sig = inspect.signature(f)
359
+ kw_defaults = {}
360
+ arg_defaults = []
361
+ opts = set()
362
+ for k, v in sig.parameters.items():
363
+ if v.default is not inspect.Parameter.empty:
364
+ kw_defaults[k] = v.default
365
+ if v.kind in (
366
+ inspect.Parameter.POSITIONAL_ONLY,
367
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
368
+ ):
369
+ arg_defaults.append(v.default)
370
+ opts.add(k)
371
+
372
+ return kw_defaults, tuple(arg_defaults), opts
373
+
374
+
375
+ def set_global_backend(
376
+ backend: _SupportsUA,
377
+ coerce: bool = False,
378
+ only: bool = False,
379
+ *,
380
+ try_last: bool = False,
381
+ ) -> None:
382
+ """
383
+ This utility method replaces the default backend for permanent use. It
384
+ will be tried in the list of backends automatically, unless the
385
+ ``only`` flag is set on a backend. This will be the first tried
386
+ backend outside the :obj:`set_backend` context manager.
387
+
388
+ Note that this method is not thread-safe.
389
+
390
+ .. warning::
391
+ We caution library authors against using this function in
392
+ their code. We do *not* support this use-case. This function
393
+ is meant to be used only by users themselves, or by a reference
394
+ implementation, if one exists.
395
+
396
+ Parameters
397
+ ----------
398
+ backend
399
+ The backend to register.
400
+ coerce : bool
401
+ Whether to coerce input types when trying this backend.
402
+ only : bool
403
+ If ``True``, no more backends will be tried if this fails.
404
+ Implied by ``coerce=True``.
405
+ try_last : bool
406
+ If ``True``, the global backend is tried after registered backends.
407
+
408
+ See Also
409
+ --------
410
+ set_backend: A context manager that allows setting of backends.
411
+ skip_backend: A context manager that allows skipping of backends.
412
+ """
413
+ _uarray.set_global_backend(backend, coerce, only, try_last)
414
+
415
+
416
+ def register_backend(backend: _SupportsUA) -> None:
417
+ """
418
+ This utility method sets registers backend for permanent use. It
419
+ will be tried in the list of backends automatically, unless the
420
+ ``only`` flag is set on a backend.
421
+
422
+ Note that this method is not thread-safe.
423
+
424
+ Parameters
425
+ ----------
426
+ backend
427
+ The backend to register.
428
+ """
429
+ _uarray.register_backend(backend)
430
+
431
+
432
+ def clear_backends(
433
+ domain: None | str,
434
+ registered: bool = True,
435
+ globals: bool = False,
436
+ ) -> None:
437
+ """
438
+ This utility method clears registered backends.
439
+
440
+ .. warning::
441
+ We caution library authors against using this function in
442
+ their code. We do *not* support this use-case. This function
443
+ is meant to be used only by users themselves.
444
+
445
+ .. warning::
446
+ Do NOT use this method inside a multimethod call, or the
447
+ program is likely to crash.
448
+
449
+ Parameters
450
+ ----------
451
+ domain : Optional[str]
452
+ The domain for which to de-register backends. ``None`` means
453
+ de-register for all domains.
454
+ registered : bool
455
+ Whether or not to clear registered backends. See :obj:`register_backend`.
456
+ globals : bool
457
+ Whether or not to clear global backends. See :obj:`set_global_backend`.
458
+
459
+ See Also
460
+ --------
461
+ register_backend : Register a backend globally.
462
+ set_global_backend : Set a global backend.
463
+ """
464
+ _uarray.clear_backends(domain, registered, globals)
465
+
466
+
467
+ class Dispatchable(Generic[_T, _TT]):
468
+ """
469
+ A utility class which marks an argument with a specific dispatch type.
470
+
471
+
472
+ Attributes
473
+ ----------
474
+ value
475
+ The value of the Dispatchable.
476
+
477
+ type
478
+ The type of the Dispatchable.
479
+
480
+ Examples
481
+ --------
482
+ >>> x = Dispatchable(1, str)
483
+ >>> x
484
+ <Dispatchable: type=<class 'str'>, value=1>
485
+
486
+ See Also
487
+ --------
488
+ all_of_type
489
+ Marks all unmarked parameters of a function.
490
+
491
+ mark_as
492
+ Allows one to create a utility function to mark as a given type.
493
+ """
494
+
495
+ def __init__(
496
+ self,
497
+ value: _T,
498
+ dispatch_type: _TT,
499
+ coercible: bool = True,
500
+ ) -> None:
501
+ self.value = value
502
+ self.type = dispatch_type
503
+ self.coercible = coercible
504
+
505
+ @overload
506
+ def __getitem__(self, index: Literal[0]) -> _TT:
507
+ ...
508
+
509
+ @overload
510
+ def __getitem__(self, index: Literal[1]) -> _T:
511
+ ...
512
+
513
+ def __getitem__(self, index: Literal[0, 1]) -> _TT | _T:
514
+ return (self.type, self.value)[index]
515
+
516
+ def __str__(self) -> str:
517
+ return "<{0}: type={1!r}, value={2!r}>".format(
518
+ type(self).__name__, self.type, self.value
519
+ )
520
+
521
+ __repr__ = __str__
522
+
523
+
524
+ def mark_as(dispatch_type: _TT) -> _PartialDispatchable[_TT]:
525
+ """
526
+ Creates a utility function to mark something as a specific type.
527
+
528
+ Examples
529
+ --------
530
+ >>> mark_int = mark_as(int)
531
+ >>> mark_int(1)
532
+ <Dispatchable: type=<class 'int'>, value=1>
533
+ """
534
+ # Pretend a more specific `functools.partial` sub-type is returned
535
+ return functools.partial( # type: ignore[return-value]
536
+ Dispatchable, dispatch_type=dispatch_type,
537
+ )
538
+
539
+
540
+ def all_of_type(
541
+ arg_type: _TT,
542
+ ) -> Callable[
543
+ [Callable[_P, Iterable[_T]]],
544
+ Callable[_P, tuple[Dispatchable[_T, _TT], ...]],
545
+ ]:
546
+ """
547
+ Marks all unmarked arguments as a given type.
548
+
549
+ Examples
550
+ --------
551
+ >>> @all_of_type(str)
552
+ ... def f(a, b):
553
+ ... return a, Dispatchable(b, int)
554
+ >>> f('a', 1)
555
+ (<Dispatchable: type=<class 'str'>, value='a'>, <Dispatchable: type=<class 'int'>, value=1>)
556
+ """
557
+
558
+ def outer(func):
559
+ @functools.wraps(func)
560
+ def inner(*args, **kwargs):
561
+ extracted_args = func(*args, **kwargs)
562
+ return tuple(
563
+ Dispatchable(arg, arg_type)
564
+ if not isinstance(arg, Dispatchable)
565
+ else arg
566
+ for arg in extracted_args
567
+ )
568
+
569
+ return inner
570
+
571
+ return outer
572
+
573
+
574
+ def wrap_single_convertor(
575
+ convert_single: Callable[[_T, _TT, bool], _T2],
576
+ ) -> Callable[[Iterable[Dispatchable[_T, _TT]], bool], list[_T2]]:
577
+ """
578
+ Wraps a ``__ua_convert__`` defined for a single element to all elements.
579
+ If any of them return ``NotImplemented``, the operation is assumed to be
580
+ undefined.
581
+
582
+ Accepts a signature of (value, type, coerce).
583
+ """
584
+
585
+ @functools.wraps(convert_single)
586
+ def __ua_convert__(dispatchables, coerce):
587
+ converted = []
588
+ for d in dispatchables:
589
+ c = convert_single(d.value, d.type, coerce and d.coercible)
590
+
591
+ if c is NotImplemented:
592
+ return NotImplemented
593
+
594
+ converted.append(c)
595
+
596
+ return converted
597
+
598
+ return __ua_convert__
599
+
600
+
601
+ def wrap_single_convertor_instance(
602
+ convert_single: Callable[[_Self, _T, _TT, bool], _T2],
603
+ ) -> Callable[[_Self, Iterable[Dispatchable[_T, _TT]], bool], list[_T2]]:
604
+ """
605
+ Wraps a ``__ua_convert__`` defined for a single element to all elements.
606
+ If any of them return ``NotImplemented``, the operation is assumed to be
607
+ undefined.
608
+
609
+ Accepts a signature of (value, type, coerce).
610
+ """
611
+
612
+ @functools.wraps(convert_single)
613
+ def __ua_convert__(self, dispatchables, coerce):
614
+ converted = []
615
+ for d in dispatchables:
616
+ c = convert_single(self, d.value, d.type, coerce and d.coercible)
617
+
618
+ if c is NotImplemented:
619
+ return NotImplemented
620
+
621
+ converted.append(c)
622
+
623
+ return converted
624
+
625
+ return __ua_convert__
626
+
627
+
628
+ def determine_backend(
629
+ value: object,
630
+ dispatch_type: type[Any],
631
+ *,
632
+ domain: str,
633
+ only: bool = True,
634
+ coerce: bool = False,
635
+ ) -> _SetBackendContext:
636
+ """Set the backend to the first active backend that supports ``value``
637
+
638
+ This is useful for functions that call multimethods without any dispatchable
639
+ arguments. You can use :func:`determine_backend` to ensure the same backend
640
+ is used everywhere in a block of multimethod calls.
641
+
642
+ Parameters
643
+ ----------
644
+ value
645
+ The value being tested
646
+ dispatch_type
647
+ The dispatch type associated with ``value``, aka
648
+ ":ref:`marking <MarkingGlossary>`".
649
+ domain: string
650
+ The domain to query for backends and set.
651
+ coerce: bool
652
+ Whether or not to allow coercion to the backend's types. Implies ``only``.
653
+ only: bool
654
+ Whether or not this should be the last backend to try.
655
+
656
+ See Also
657
+ --------
658
+ set_backend: For when you know which backend to set
659
+
660
+ Notes
661
+ -----
662
+
663
+ Support is determined by the ``__ua_convert__`` protocol. Backends not
664
+ supporting the type must return ``NotImplemented`` from their
665
+ ``__ua_convert__`` if they don't support input of that type.
666
+
667
+ Examples
668
+ --------
669
+
670
+ Suppose we have two backends ``BackendA`` and ``BackendB`` each supporting
671
+ different types, ``TypeA`` and ``TypeB``. Neither supporting the other type:
672
+
673
+ >>> with ua.set_backend(ex.BackendA):
674
+ ... ex.call_multimethod(ex.TypeB(), ex.TypeB())
675
+ Traceback (most recent call last):
676
+ ...
677
+ uarray.BackendNotImplementedError: ...
678
+
679
+ Now consider a multimethod that creates a new object of ``TypeA``, or
680
+ ``TypeB`` depending on the active backend.
681
+
682
+ >>> with ua.set_backend(ex.BackendA), ua.set_backend(ex.BackendB):
683
+ ... res = ex.creation_multimethod()
684
+ ... ex.call_multimethod(res, ex.TypeA())
685
+ Traceback (most recent call last):
686
+ ...
687
+ uarray.BackendNotImplementedError: ...
688
+
689
+ ``res`` is an object of ``TypeB`` because ``BackendB`` is set in the
690
+ innermost with statement. So, ``call_multimethod`` fails since the types
691
+ don't match.
692
+
693
+ Instead, we need to first find a backend suitable for all of our objects.
694
+
695
+ >>> with ua.set_backend(ex.BackendA), ua.set_backend(ex.BackendB):
696
+ ... x = ex.TypeA()
697
+ ... with ua.determine_backend(x, "mark", domain="ua_examples"):
698
+ ... res = ex.creation_multimethod()
699
+ ... ex.call_multimethod(res, x)
700
+ TypeA
701
+
702
+ """
703
+ dispatchables = (Dispatchable(value, dispatch_type, coerce),)
704
+ backend = _uarray.determine_backend(domain, dispatchables, coerce)
705
+
706
+ return set_backend(backend, coerce=coerce, only=only)
707
+
708
+
709
+ def determine_backend_multi(
710
+ dispatchables: Iterable[Any],
711
+ *,
712
+ domain: str,
713
+ only: bool = True,
714
+ coerce: bool = False,
715
+ **kwargs: type[Any],
716
+ ) -> _SetBackendContext:
717
+ """Set a backend supporting all ``dispatchables``
718
+
719
+ This is useful for functions that call multimethods without any dispatchable
720
+ arguments. You can use :func:`determine_backend_multi` to ensure the same
721
+ backend is used everywhere in a block of multimethod calls involving
722
+ multiple arrays.
723
+
724
+ Parameters
725
+ ----------
726
+ dispatchables: Sequence[Union[uarray.Dispatchable, Any]]
727
+ The dispatchables that must be supported
728
+ domain: string
729
+ The domain to query for backends and set.
730
+ coerce: bool
731
+ Whether or not to allow coercion to the backend's types. Implies ``only``.
732
+ only: bool
733
+ Whether or not this should be the last backend to try.
734
+ dispatch_type: Optional[Any]
735
+ The default dispatch type associated with ``dispatchables``, aka
736
+ ":ref:`marking <MarkingGlossary>`".
737
+
738
+ See Also
739
+ --------
740
+ determine_backend: For a single dispatch value
741
+ set_backend: For when you know which backend to set
742
+
743
+ Notes
744
+ -----
745
+
746
+ Support is determined by the ``__ua_convert__`` protocol. Backends not
747
+ supporting the type must return ``NotImplemented`` from their
748
+ ``__ua_convert__`` if they don't support input of that type.
749
+
750
+ Examples
751
+ --------
752
+
753
+ :func:`determine_backend` allows the backend to be set from a single
754
+ object. :func:`determine_backend_multi` allows multiple objects to be
755
+ checked simultaneously for support in the backend. Suppose we have a
756
+ ``BackendAB`` which supports ``TypeA`` and ``TypeB`` in the same call,
757
+ and a ``BackendBC`` that doesn't support ``TypeA``.
758
+
759
+ >>> with ua.set_backend(ex.BackendAB), ua.set_backend(ex.BackendBC):
760
+ ... a, b = ex.TypeA(), ex.TypeB()
761
+ ... with ua.determine_backend_multi(
762
+ ... [ua.Dispatchable(a, "mark"), ua.Dispatchable(b, "mark")],
763
+ ... domain="ua_examples"
764
+ ... ):
765
+ ... res = ex.creation_multimethod()
766
+ ... ex.call_multimethod(res, a, b)
767
+ TypeA
768
+
769
+ This won't call ``BackendBC`` because it doesn't support ``TypeA``.
770
+
771
+ We can also use leave out the ``ua.Dispatchable`` if we specify the
772
+ default ``dispatch_type`` for the ``dispatchables`` argument.
773
+
774
+ >>> with ua.set_backend(ex.BackendAB), ua.set_backend(ex.BackendBC):
775
+ ... a, b = ex.TypeA(), ex.TypeB()
776
+ ... with ua.determine_backend_multi(
777
+ ... [a, b], dispatch_type="mark", domain="ua_examples"
778
+ ... ):
779
+ ... res = ex.creation_multimethod()
780
+ ... ex.call_multimethod(res, a, b)
781
+ TypeA
782
+
783
+ """
784
+ if "dispatch_type" in kwargs:
785
+ disp_type = kwargs.pop("dispatch_type")
786
+ dispatchables = tuple(
787
+ d if isinstance(d, Dispatchable) else Dispatchable(d, disp_type)
788
+ for d in dispatchables
789
+ )
790
+ else:
791
+ dispatchables = tuple(dispatchables)
792
+ if not all(isinstance(d, Dispatchable) for d in dispatchables):
793
+ raise TypeError("dispatchables must be instances of uarray.Dispatchable")
794
+
795
+ if len(kwargs) != 0:
796
+ raise TypeError("Received unexpected keyword arguments: {}".format(kwargs))
797
+
798
+ backend = _uarray.determine_backend(domain, dispatchables, coerce)
799
+
800
+ return set_backend(backend, coerce=coerce, only=only)