Skip to content

Commit 9354563

Browse files
authored
Make @dispatchable return a function (#19)
Not returning a function can confuse some users (because they have strict checks for `FunctionType`). NumPy got away with this, but it could be one stumbling block. NOTE(seberg): In the future, with some things moved to C, this could be a more significant overhead and we may want to consider optionally disabling it as an optimization (or more carefully checking how necessary it is and changing the default).
1 parent 40c9ff0 commit 9354563

2 files changed

Lines changed: 34 additions & 5 deletions

File tree

src/spatch/backend_system.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -822,11 +822,9 @@ def __repr__(self):
822822

823823
class Dispatchable:
824824
# Dispatchable function object
825-
#
826-
# TODO: We may want to return a function just to be nice (not having a func was
827-
# OK in NumPy for example, but has a few little stumbling blocks)
828-
def __init__(self, backend_system, func, dispatch_args, ident=None):
829-
functools.update_wrapper(self, func)
825+
826+
def __new__(cls, backend_system, func, dispatch_args, ident=None):
827+
self = object.__new__(cls)
830828

831829
self._backend_system = backend_system
832830
self._default_func = func
@@ -907,6 +905,31 @@ def __init__(self, backend_system, func, dispatch_args, ident=None):
907905
else:
908906
self.__doc__ = None # not our problem
909907

908+
# Return a function, not a class instance.
909+
# This helps with type checkers, html docs, reprs, etc.
910+
def new_func(*args, **kwargs):
911+
return self(*args, **kwargs)
912+
913+
# Standard function-wrapping stuff (like `functools.update_wrapper`).
914+
# Should we set attrs to `self` or just `new_func`?
915+
new_func.__doc__ = self.__doc__
916+
new_func.__name__ = self.__name__ = func.__name__
917+
new_func.__module__ = self.__module__ = func.__module__
918+
new_func.__qualname__ = self.__qualname__ = func.__qualname__
919+
new_func.__defaults__ = self.__defaults__ = getattr(func, "__defaults__", None)
920+
new_func.__kwdefaults__ = self.__kwdefaults__ = getattr(func, "__kwdefaults__", None)
921+
new_func.__annotations__ = self.__annotations__ = getattr(func, "__annotations__", {})
922+
new_func.__type_params__ = self.__type_params__ = getattr(func, "__type_params__", ())
923+
new_func.__dict__.update(func.__dict__)
924+
# self.__dict__.update(func.__dict__) # Don't clobber self.__dict__; too risky!
925+
self.__wrapped__ = func
926+
new_func.__wrapped__ = self
927+
928+
# As we add more public API to dispatchable functions, set them on `new_func` here
929+
# such as `new_func.invoke = self.invoke`.
930+
931+
return new_func
932+
910933
def __get__(self, obj, objtype=None):
911934
if obj is None:
912935
return self

tests/test_priority.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import types
2+
13
import pytest
24

35
from spatch.backend_system import BackendSystem
@@ -135,3 +137,7 @@ def test_opts_context_basic(bs):
135137
assert bs.backend_opts().backends == ("IntB", "RealB", "IntB2", "IntSubB", "FloatB")
136138

137139
assert bs.dummy_func(1) == ("IntB", (1,), {})
140+
141+
142+
def test_dispatchable_is_function(bs):
143+
assert isinstance(bs.dummy_func, types.FunctionType)

0 commit comments

Comments
 (0)