views:

66

answers:

6

I am currently doing this, to do different things based on an object's type:

    actions = {
        SomeClass: lambda: obj.name
        AnotherClass: lambda: self.normalize(obj.identifier)
        ...[5 more of these]...
    }

    for a in actions.keys():
        if isinstance(obj, a):
            return actions[a]()

Is it possible to cut out the for loop, and do something like this?

actions[something to do with obj]()
A: 

What about

actions[obj.__class__]()
Anurag Uniyal
A: 
actions[type(obj)]()
Jason Baker
+1  A: 
actions[obj.__class__]()

works if obj is really an instance of (say) SomeClass and not of a subclass -- so if that can be the case, the result will be different from your current way of handling it. Also note that this might raise a KeyError if the class has no corresponding action. To handle this case the same way you do now (i.e. do nothing), you could use

actions.get(obj.__class__, lambda: None)

to have a default value returned.

Oh, and listen to S.Lott's comment on your question. In many cases, there are better ways to achieve something like this. You could, for example, have all your classes define a do_whatever(self) and just call obj.do_whatever().

balpha
A: 
results = [func() for cls, func in actions.iteritems() if isinstance(obj, cls)]

There will be zero or more results, if your object isinstance of zero or more of the class-keys.

Using type(obj) as key will only work if your object is of that type. If it's further down the inheritance tree, you'll miss it.

Matt Anderson
+5  A: 
class SomeClass( object ):
....
    def action( self ):
        return self.name

class AnotherClass( object ):
....
    def action( self ):
        return self.normalize( self.identifier )

[5 more classes like the above two]

a.action()

Simpler. Clearer. More extensible. Less Magic. No dictionary. No loop.

S.Lott
+1. Any hint for the case that `action` depends on *two* objects(' classes)? That's a problem I have in a current project, and I couldn't yet think of a better way than `isinstance` tests.
balpha
@balpha: They call that "double dispatch". It's the same basic drill, except class hierarchy A picks a method from the other class. First, research "Double Dispatch". Then ask a separate question if you still have trouble.
S.Lott
+1  A: 

I assume you have a parent class for all of those, or at least a mixin. Put a default return function in the parent or mixin, and then override it in those that are different... It's the only proper way of doing it.

Sure, it makes extra code, but at least it's encapsulated, and scalable. Say you wanna add support for five more classes. Instead of altering that code up there, just add the correct code to the new classes. By the looks of it, it's two lines per class (function definition and return line). That's not bad, is it?

If obj isn't a class which contains a return function, then an exception is raised, which you could catch and ignore with a clean conscience.

class MyMixin:
  def my_return(self, *args):
    return self.name
  ... possibly other things...

class SomeClass(MyMixin):
  ... no alteration to the default ...

class AnotherClass(MyParent, MyMixin):
  def my_return(self, *args):
    return args[0].normalize(self.identifier)
  ... blabla


# now, this is in the caller object...
try:
  rval = obj.my_return(self) # this is the caller object 'self', not the 'self' in the 'obj'
  #dosomething with rval
except Exception:
  pass #no rval for this object type, skipping it...
Tor Valamo
With python's duck typing, the mixin isn't strictly necessary. As long as all your classes implement `my_return` with the correct set of arguments, it should work just fine. However, Tor is correct that having a common parent class is helpful, as it allows you to provide a default behavior that all your classes will follow unless otherwise specified.
jcdyer
that was the assumption i made, that some sort of relationship existed between the specific classes.
Tor Valamo