问题
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?
回答1:
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>
来源:https://stackoverflow.com/questions/48483650/python-lost-self-while-decorating-class-multimethods