plum
plum copied to clipboard
`multimeta`-like metaclass for plum
Hello!
Would it be possible to add a multimeta
-like metaclass for plum, pulled directly from the multimethod source code?
I have a working example below:
from plum import dispatch
def plume(*args, **kwargs):
def wrapper(func):
func.__plume__ = Plume(args, kwargs)
return func
return wrapper
# Adapted From: https://github.com/coady/multimethod/blob/main/multimethod/__init__.py#L488-L498
class plumeta(type):
"""Convert all callables in namespace to Dispatchers."""
class __prepare__(dict):
def __init__(self, *args):
self.__dispatcher__ = Dispatcher()
super().__setitem__("__dispatcher__", self.__dispatcher__)
def __setitem__(self, key, value):
if callable(value):
args, kwargs = getattr(value, "__plume__", (tuple(), dict()))
value = getattr(self.get(key), "dispatch", self.__dispatcher__)(
value, *args, **kwargs
)
super().__setitem__(key, value)
class Test(metaclass=plumeta):
def a(self, c):
print(f"{c} is an unknown type in 'a'!")
def a(self):
print("Nothing has been provided to 'a'!")
def a(self, c: str):
print(f"{c} is a string in 'a'!")
def a(self, c: int):
print(f"{c} is an integer in 'a'!")
def a(self, c: str, d: int):
print(f"{c} is a string and {d} is an integer in 'a'!")
def b(self, c):
print(f"{c} is an unknown type in 'b'!")
def b(self):
print("Nothing has been provided to 'b'!")
def b(self, c: int):
print(f"{c} is a integer in 'b'!")
def b(self, c: str):
print(f"{c} is a string in 'b'!")
def b(self, c: int, d: str):
print(f"{c} is an integer and {d} is a string in 'b'!")
test = Test()
test.a() # Nothing has been provided to 'a'!
test.a(lambda x: x) # <function <lambda> at 0x000000000000> is an unknown type in 'a'!
test.a("a") # a is a string in 'a'!
test.a(1) # 1 is an integer in 'a'!
test.a("a", 1) # a is a string and 1 is an integer in 'a'!
test.b() # Nothing has been provided to 'b'!
test.b(lambda x: x) # <function <lambda> at 0x000000000000> is an unknown type in 'b'!
test.b(2) # 2 is an integer in 'b'!
test.b("b") # b is a string in 'b'!
test.b(2, "b") # 2 is an integer and b is a string in 'b'!
Of course, I'm assuming there are other tests I'd have to run to ensure the same result, but otherwise, would this work?
Thank you kindly for your consideration on the matter!
Hey @sylvorg!
Thanks for opening an issue. :) This sounds very reasonable.
Iām currently away, but should be back soon. I will get back to you some time next week.
Got it; no worries, and I look forward to your return!
Hey @sylvorg!
I'm back again. I think such a metaclass could be useful and would be a very sensible addition to the library. :)
Unfortunately, I don't have the capacity to do this myself on the short term. I am a little overloaded at the moment. :( However, if you would like to have a stab at this, contributions are very welcome! Otherwise, I'll put it on the TODO list and will implement this at a later point in time.
I can probably put together a PR, but I'm not exactly sure where to put the class; should I just put it in __init__.py
, or create a separate file just for it? And how should I credit the original author(s)? Should I just ping them in this issue?
@sylvorg, very sorry for the super late reply. Work has been very busy, meaning that I have less-than-usual capacity for side projects. :(
I think a separate file would make sense. Perhaps we can call the class DispatchMeta
in plum/dispatchmeta.py
to keep consistent with the current naming and capitalisation of things?
And how should I credit the original author(s)? Should I just ping them in this issue?
A clear remark in the docstring would suffice, I think. Pinging them in this issue might be nice too. :)
I'll ask @coady now; hopefully they aren't too irritated by this request, considering I disturbed them quite a bit before coming here! š
How does something like the following look?
from collections import namedtuple
from plum import Dispatcher
Plume = namedtuple("Plume", "args,kwargs")
def plume(*args, **kwargs):
def wrapper(func):
func.__plume__ = Plume(args, kwargs)
return func
return wrapper
# Adapted From: https://github.com/coady/multimethod/blob/main/multimethod/__init__.py#L488-L498
class DispatchMeta(type):
"""Convert all callables in namespace to Dispatchers."""
class __prepare__(dict):
def __init__(self, *args):
self.__dispatcher__ = Dispatcher()
super().__setitem__("__dispatcher__", self.__dispatcher__)
def __setitem__(self, key, value):
if callable(value):
args, kwargs = getattr(value, "__plume__", (tuple(), dict()))
if not kwargs.get("disabled", False):
value = getattr(self.get(key), "dispatch", self.__dispatcher__)(
value, *args, **kwargs
)
super().__setitem__(key, value)
The plume
function (name subject to change) allows users to set settings like the precedence, while the disabled
keyword argument prevents the specified functions from being dispatched, to prevent unwanted recursion to parent classes from child classes, for example.
@sylvorg I think that looks great!! What would you think of something like this:
from plum import Dispatcher, configure_dispatch
dispatch = Dispatcher()
class MyClass(metaclass=dispatch.meta):
@configure_dispatch(precedence=1)
def method(self, x):
return x
Here dispatch.meta
creates a metaclass that uses dispatch
as the dispatcher. plume
is a funny one :D, but perhaps configure_dispatch
is a little clearer š
.
... but perhaps configure_dispatch is a little clearer š .
Oh, y'all are no fun. š¹
I love the dispatch.meta
Idea, though! So would I just add meta
as a nested class in Dispatcher
? Also, configure_dispatch
seems a bit too long, in my opinion; in wondering if there's some way to check if you're defining a function in a class and act accordingly... Maybe inspect.is_method
?
@sylvorg, haha feel free to export an alias plume = configure_dispatch
too. :P
So would I just add meta as a nested class in Dispatcher?
Maybe use a @property
and create the class dynamically:
class Dispatch:
...
@property
def meta(self):
class DispatchMeta(type):
...
return meta
How would that look?
Also, configure_dispatch seems a bit too long
Hmm, I do agree, though the user can always do from plum import configure_dispatch as plume
or from plum import configure_dispatch as conf
or so.
in wondering if there's some way to check if you're defining a function in a class and act accordingly... Maybe inspect.is_method?
Ah, this is clever! How about we get rid of configure_dispatch
/plume
and just do this:
from plum import Dispatcher, configure_dispatch
dispatch = Dispatcher()
class MyClass(metaclass=dispatch.meta):
@dispatch(precedence=1)
def method(self, x):
return x
Then, in the metaclass, we can check whether the function is already dispatched (in that case it's an instance of plum.function.Function
).
Got it on the class creation!
When checking a function in the metaclass, then, should we just ignore the already dispatched methods, since they've already been added to the specified dispatcher?
Also, I have a very special request to use a different base class for the metaclass if needed; there is a package which provides a metaclass for automatically creating slots from the self
variables in the __init__
method, and I'd like to use that if possible; should I just put the __prepare__
class in it's own class externally and use that to create my own version of this, or should there be a way to use different base classes?