0

I have classes like this:

class Tool(object): def do_async(*args): pass 

for which I want to automatically generate non-async methods that make use of the async methods:

class Tool(object): def do_async(*args): pass def do(*args): result = self.do_async(*args) return magical_parser(result) 

This gets to be particularly tricky because each method needs to be accessible as both an object and class method, which is normally achieved with this magical decorator:

class class_or_instance(object): def __init__(self, fn): self.fn = fn def __get__(self, obj, cls): if obj is not None: f = lambda *args, **kwds: self.fn(obj, *args, **kwds) else: f = lambda *args, **kwds: self.fn(cls, *args, **kwds) functools.update_wrapper(f, self.fn) return f 

How can I make these methods, and make sure they're accessible as both class and object methods? This seems like something that could be done with decorators, but I am not sure how.

(Note that I don't know any of the method names in advance, but I know that all of the methods that need new buddies have _async at the end of their names.)

I think I've gotten fairly close, but this approach does not appropriately set the functions as class/object methods:

def process_asyncs(cls): methods = cls.__dict__.keys() for k in methods: methodname = k.replace("_async","") if 'async' in k and methodname not in methods: @class_or_instance def method(self, verbose=False, *args, **kwargs): response = self.__dict__[k](*args,**kwargs) result = self._parse_result(response, verbose=verbose) return result method.__docstr__ = ("Returns a table object.\n" + cls.__dict__[k].__docstr__) setattr(cls,methodname,MethodType(method, None, cls)) 
2
  • What are the semantics for calling these methods as instance or class methods? Do they even use the instance for anything when called as instance methods? This sounds like a job for module-level functions, or perhaps staticmethod if you're really, absolutely sure these things need to be methods. Commented Aug 4, 2013 at 23:06
  • Yes, normally these would just be module-level functions, but there are some cases in which an instance is needed. Basically, this is for a suite of web querying tools, where normally you can just use the classmethod, but sometimes you need to use the object to log in to the service first. There may be better ways, but this approach has worked well so far. (work in progress: astroquery.readthedocs.org) Commented Aug 4, 2013 at 23:10

1 Answer 1

3

Do not get the other method from the __dict__; use getattr() instead so the descriptor protocol can kick in.

And don't wrap the method function in a MethodType() object as that'd neutralize the descriptor you put on method.

You need to bind k to the function you generate; a closured k would change with the loop:

@class_or_instance def method(self, verbose=False, _async_method_name=k, *args, **kwargs): response = getattr(self, _async_method_name)(*args,**kwargs) result = self._parse_result(response, verbose=verbose) return result cls.__dict__[methodname] = method 

Don't forget to return cls at the end; I've changed this to use a separate function to create a new scope to provide a new local name _async_method_name instead of a keyword parameter; this avoids difficulties with *args and explicit keyword arguments:

def process_asyncs(cls): def create_method(async_method): @class_or_instance def newmethod(self, *args, **kwargs): if 'verbose' in kwargs: verbose = kwargs.pop('verbose') else: verbose = False response = async_method(*args,**kwargs) result = self._parse_result(response, verbose=verbose) return result return newmethod methods = cls.__dict__.keys() for k in methods: methodname = k.replace("_async","") if 'async' in k and methodname not in methods: async_method = getattr(cls, k) setattr(cls, methodname, create_method(async_method)) return cls 
Sign up to request clarification or add additional context in comments.

12 Comments

I had tried that, but got the error: TypeError: 'dictproxy' object does not support item assignment. My classes have __metaclass__=abc.ABCMeta; I don't really know if that's warranted or related to this.
Your posted solution in your question will suffer from the fact async_method is a closure in a loop; all your generated methods will point to one async method.
See Local variables in Python nested functions for a more thorough explanation of what will go wrong with the async_method name in your code, and why I bound k to a keyword argument in my solution.
Yes, by creating a new scope; use a separate function that returns newmethod() when called. See the linked question from my previous comment.
Yup, which is what my original answer did but your edit cached the method instead. :-) A late lookup allows you to find instance attributes too, yes.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.