2

I am trying to implement a multimethod approach based on this article http://www.artima.com/weblogs/viewpost.jsp?thread=101605. There are two differences from this approach:

  • I only need to look at the first argument of the multimethod, so no need to form tuples of arg classes
  • The multimethods will live in classes, they will not be regular functions.

However I mixed up my classes a bit, and the call to self gets lost while dispatching a call to a class method.

Here is my code:

method_registry = {}


class SendMessageMultiMethod(object):
    """
    A class for implementing multimethod functionality
    """
    def __init__(self, name):
        self.name = name
        self.typemap = {}

    def __call__(self, message, extra_payload=None):
        """
        Overrriding method call and dispatching it to an actual method
        based on the supplied message class
        """
        first_arg_type = message.__class__
        function = self.typemap.get(first_arg_type)
        print(
            'Dispatching to function {} with message {} and extra payload {}...'
            .format(function, message, extra_payload)
        )
        return function(message, extra_payload)

    def register(self, type_, function):
        self.typemap[type_] = function


def use_for_type(*types):
    """
    A decorator that registers a method to use with certain types
    """

    def register(method):
        """Creating Multimethod with the method name
        and registering it at at method_registry dict """
        name = method.__name__
        mm = method_registry.get(name)
        if mm is None:
            mm = method_registry[name] = SendMessageMultiMethod(name)
        for type_ in types:
            mm.register(type_, method)
        return mm

    return register


class Sender(object):

    def send_messages(self, messages_list):
        for message in messages_list:
            # this is supposed to fire different send_message() methods
            # for different arg types
            self.send_message(message)

    @use_for_type(int, float)
    def send_message(self, message, *args, **kwargs):
        print('received call for int/float message {} with {}, {}'
              .format(message, args, kwargs))
        print('self is {}'.format(self))

    @use_for_type(bool)
    def send_message(self, message, *args, **kwargs):
        print('received call for bool message {} with {}, {}'
              .format(message, args, kwargs))
        print('self is {}'.format(self))

So when I call the send_messages method on a Sender class I receive the arguments in self, not in message variables. Here:

sender = Sender()
sender.send_messages([1, 2, True, 5.6])

output:

Dispatching to function <function Sender.send_message at 0x1013608c8> with message 1 and extra payload None...
received call for int/float message None with (), {}
self is 1
Dispatching to function <function Sender.send_message at 0x1013608c8> with message 2 and extra payload None...
received call for int/float message None with (), {}
self is 2
Dispatching to function <function Sender.send_message at 0x101360950> with message True and extra payload None...
received call for bool message None with (), {}
self is True
Dispatching to function <function Sender.send_message at 0x1013608c8> with message 5.6 and extra payload None...
received call for int/float message None with (), {}
self is 5.6

how do I not lose self and dispatch the message contents to the message variable?

kurtgn
  • 6,066
  • 7
  • 39
  • 72
  • Besides my answer, I'd recommend you look here: https://github.com/mrocklin/multipledispatch/tree/master/multipledispatch, especially at `core` and `dispatcher`, for code and/or inspiration for a more complete implementation. – Silly Freak Jan 28 '18 at 06:59

1 Answers1

2

As Python method signatures such as def send_message(self, message, *args, **kwargs) suggest, the first argument to a method must be the self object. Usually, by doing obj.send_message, you access the object's method, not the classes'. Try the following:

>>> class Foo():
...     def bar(self):
...         pass

>>> Foo.bar
<function __main__.Foo.bar>

>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f5fd927cc18>>

bound method means that self is already specified.

Your @use_for_type decorator works at the class level, so on your send_message functions, not bound methods.

Now it's just about figuring out where your code is missing passing self explicitly, and that's for one thing in __call__ - self is the SendMessageMultiMethod object, not the Sender object - and in your decorator:

class SendMessageMultiMethod(object):
    ...

    # note the `self_` parameter
    def __call__(self, self_, message, extra_payload=None):
        ...
        return function(self_, message, extra_payload)


def use_for_type(*types):
    ...

    def register(method):
        ...

        return lambda self, *args, **kwargs: mm(self, *args, **kwargs)

Output:

Dispatching to function <function Sender.send_message at 0x7f1e427e5488> with message 1 and extra payload None...
received call for int/float message 1 with (None,), {}
self is <__main__.Sender object at 0x7f1e4277b0f0>
Dispatching to function <function Sender.send_message at 0x7f1e427e5488> with message 2 and extra payload None...
received call for int/float message 2 with (None,), {}
self is <__main__.Sender object at 0x7f1e4277b0f0>
Dispatching to function <function Sender.send_message at 0x7f1e427e5598> with message True and extra payload None...
received call for bool message True with (None,), {}
self is <__main__.Sender object at 0x7f1e4277b0f0>
Dispatching to function <function Sender.send_message at 0x7f1e427e5488> with message 5.6 and extra payload None...
received call for int/float message 5.6 with (None,), {}
self is <__main__.Sender object at 0x7f1e4277b0f0>
Silly Freak
  • 3,637
  • 1
  • 27
  • 50