Note that @parallel does work fine if I define meth1 outside of the class, and introspection T.meth1? indicates that the decorator has been applied to it. So I don't understand what else I should do; any ideas?

4 answers

I've posted an initial patch at ticket 11461 which fixes the problem that Niles reported. The key is to use the descriptor protocol ( __get__ ) to "wrap" the correct function depending on how the function is accessed. This will also work with classmethods and staticmethods provided that the parallel decorator is applied after either of those two. For example,

Thanks Karl! That did help a lot, and I think I have a workaround. First, to be clear, I think the basic answer to my question is:

"No; a method always implicitly prepends its arguments with self (the class instance it is bound to), and @parallel only works properly if the first argument is a list or tuple. Therefore the two are fundamentally incompatible."

Of course maybe someone clever can think of a way to improve @parallel. I spent some time on this, but I couldn't come up with a reliable way to test whether the first argument was the class instance to which the method was bound.

UPDATE: This is now ticket 11461 :)

But I did think of a reasonable workaround: Write a method for the class which will produce the parallelizable function. This function can still take self as its first argument and thereby access any other attributes in the class; the only difference will be that the user will have to explicitly give the class instance as the first argument. Here's a demo:

Comments

Good work. I still think that someone clever could do it, as you say. For instance, one could check whether the first thing is a class instance and the rest is a list of the appropriate type, in a try/except block ... Maybe you should ask this on sage-devel. It would certainly be a natural improvement.

# Construct the wrapper parallel version of the function we're wrapping.
# We may rework this so g is a class instance, which has the plus that
# we can query g for how it works, etc.
def g(*args, **kwds):
if len(args) > 0 and isinstance(args[0], (list, types.GeneratorType)):
return self.p_iter(f, (normalize_input(a) for a in args[0]))
else:
return f(*args, **kwds)
return g

If I insert a print statement (print args) before the if/else as well as print statements about which branch I take, I get

Does this help? It's never even reaching the point of the iterator, because the first item in args is not iterable, but the class itself. I don't know how to fix this, except by adding a case in the if statement, but I have no idea whether that would break anything else.

Here are some ideas for making the @parallel decorator usable on methods.

I did not check the code. But usually, a decorator takes as its argument a function f. Now, it is easy to check whether f is a plain function or a method: Use ismethod() or ismethoddescriptor() from the inspect module.

On top of that, one may even think of making @parallel usable on wrapped methods, such as

@parallel
@cached_method
def my_method(self, L,**args):

In that case, the function put into the @parallel decorator would be an instance of a CachedMethodCaller. Those things could be tested with the function isclassinstance() from sage.misc.sageinspect. Perhaps that particular example does not make sense, but it may still be a case to be taken into account.

Comments

I'm not sure this will work -- I tried a similar approach checking for the `.__self__` attribute and using it to determine if the first argument was equal to the class instance. But the code failed, saying that `f` did not have a `.__self__` attribute. I know that methods do have this attribute, so I think what's being passed to Parallel is some kind of unbound version of the method . . . and now I'm really lost. In any case, I guess we should take further discussion to the new ticket :) http://trac.sagemath.org/sage_trac/ticket/11461