3
\$\begingroup\$

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.

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Dec 21, 2014 at 15:27
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

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 on x, and it will be dispatched to method m 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)
answered Dec 21, 2014 at 16:22
\$\endgroup\$
2
  • \$\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\$ Commented Dec 21, 2014 at 17:39
  • 1
    \$\begingroup\$ There's no such thing as too much Python coding ;-) Keep it up! \$\endgroup\$ Commented Dec 21, 2014 at 17:45
1
\$\begingroup\$

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
answered Dec 21, 2014 at 16:48
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.