This class is designed to call a similar function of multiple objects of multiple classes using a single interface (it is not for calling functions that return a value).
A potential usage will be sending same data to multiple writers (log writer, HTML generator and styled printer to console).
Tested with Python 3.4.2
I've also included a test/example in the below code:
"""
caller module:
Contains Caller class - call multiple other classes or modules with
similar function definitions, (it is not for calling functions that
returns a value)
Author : Bhathiya Perera
"""
class Caller():
"""call other classes or modules with
similar function definitions"""
def __init__(self, *receivers):
"""Initialize
Parameters :
receivers - va-arg receivers (can be objects, modules,
another caller , ....)
"""
self._names = []
self._receivers = receivers
def __getattr__(self, name):
"""Get attribute of a given name
This will return 'self' therefore it can be called later
"""
self._names.append(name)
return self
def __call__(self, *args, **kw):
"""This class is callable with any arguments or key-value arguments
It will then be posted to all receivers
"""
if len(self._names) == 0:
raise Exception("Cannot call")
method_name = self._names.pop()
for receiver in self._receivers:
method = getattr(receiver, method_name)
method(*args, **kw)
return self
if __name__ == "__main__":
# -------------------------------------------------
# Test its usage
class Receiver1():
def a(self, arg):
print ("Receiver1 - a", arg)
def b(self, arg):
print ("Receiver1 - b", arg)
class Receiver2():
def a(self, arg):
print ("Receiver2 - a", arg)
def b(self, arg):
print ("Receiver2 - b", arg)
class Receiver3():
def a(self, arg):
print ("Receiver3 - a", arg)
def b(self, arg):
print ("Receiver3 - b", arg)
c = Caller(Receiver3())
d = Caller(Receiver1(), Receiver2(), c)
d.a("hello a")
d.b("hello b")
print ("-----")
d.a.b.a.b.a.b.a("a")("b")("c")("d")("e")("f")("g")
If it was executed as a module it will print:
Receiver1 - a hello a Receiver2 - a hello a Receiver3 - a hello a Receiver1 - b hello b Receiver2 - b hello b Receiver3 - b hello b ----- Receiver1 - a a Receiver2 - a a Receiver3 - a a Receiver1 - b b Receiver2 - b b Receiver3 - b b Receiver1 - a c Receiver2 - a c Receiver3 - a c Receiver1 - b d Receiver2 - b d Receiver3 - b d Receiver1 - a e Receiver2 - a e Receiver3 - a e Receiver1 - b f Receiver2 - b f Receiver3 - b f Receiver1 - a g Receiver2 - a g Receiver3 - a g
Review for Python conventions and anything else.
2 Answers 2
If I understand correctly,
the purpose of Caller
seems to be to call a function on multiple objects.
It works like this:
- Construct a
Caller x
by passing 1 or more objects in the constructor - Call any method
m
onx
, and it will be dispatched to methodm
of all objects
Right? The docstring doesn't explain this very well.
CallDispatcher
might be a better name.
My impression is that you're discovering powerful features of Python, and you're trying to use them because you can, not because you have a concrete purpose. Sort of like an exercise, not for real-life use.
It seems to me that __getattr__
is seriously abused.
I don't have experience overriding this method,
but I would guess it's designed to implement getters dynamically.
First of all,
adding getters dynamically seems like a hack that should be used with extreme care,
including a good justification that it's the best option.
Secondly,
instead of behaving as a getter,
this implementation mutates the object and returns self
.
I see you did that to make chaining and currying possible,
it's interesting, but it seems a misuse of the language.
Another thing I don't like about the approach is the heavy dependence on duck typing.
The objects that can be used with Caller
don't have to follow a well-defined interface,
they can be anything.
The user just has to make sure that when they call a .hello
function on a Caller
object,
all the objects inside have a .hello
function defined.
It's great that Python let's us do this kind of thing,
but it doesn't mean that we should.
I prefer to have well-defined and well-documented interfaces,
with a list of legitimate methods that I'm allowed to call.
Python conventions
Instead of:
class Caller():
The class should be declared as:
class Caller:
Instead of:
if len(self._names) == 0:
The Pythonic way:
if not self._names:
Instead of:
print ("Receiver3 - b", arg)
There should be no space before the opening paren:
print("Receiver3 - b", arg)
-
\$\begingroup\$ Thanks, I have been doing too much Python coding without getting any reviews. Also I have trouble writing module level doc-string, they just end up being a copy of Classes' doc strings. \$\endgroup\$JaDogg– JaDogg2014年12月21日 17:39:37 +00:00Commented Dec 21, 2014 at 17:39
-
1\$\begingroup\$ There's no such thing as too much Python coding ;-) Keep it up! \$\endgroup\$janos– janos2014年12月21日 17:45:51 +00:00Commented Dec 21, 2014 at 17:45
The idea of using __getattr__
to build a stack of methods to call feels strange to me, and your usage example d.a.b.a.b.a.b.a("a")("b")("c")("d")("e")("f")("g")
does nothing to convince me otherwise. Also, if I do this
a = d.a
b = d.b
a("a")
b("b")
I get this result:
Receiver1 - b a
Receiver2 - b a
Receiver3 - b a
Receiver1 - a b
Receiver2 - a b
Receiver3 - a b
Instead, I suggest returning just a callable from __getattr__
, eg. like this:
class Caller():
def __init__(self, *receivers):
self._receivers = receivers
def __getattr__(self, method_name):
def f(*args, **kw):
for receiver in self._receivers:
method = getattr(receiver, method_name)
method(*args, **kw)
return f
Explore related questions
See similar questions with these tags.