2

I would like to expose the methods of a class as functions (after decoration) in my local scope. For example if I had a class and decorator:

def some_decorator(f):
    ...transform f...
    return decorated_f

class C(object):
    def __init__(self,x):
        self.x = x
    def f(self,y):
        "Some doc string"
        return self.x + y
    def g(self,y,z):
        "Some other doc string"
        return self.x + y + z

and if I didn't care about automizing the process I could add the following code to my module the following:

@some_decorator
def f(C_instance,x):
    "Some doc string."
    return C_instance.f(x)

@some_decorator
def g(C_instance,x,y):
    "Some other doc string."
    return C_instance.g(x,y)    

to the effect that the following evaluate to True

c = C(0)
f(c,1) == c.f(1)

But I would like to be able to do this automatically. Something like:

my_funs = expose_methods(MyClass)
for funname, fun in my_funs.iteritems():
    locals()[funname] = some_decorator(fun)

foo = MyClass(data)
some_functionality(foo,*args) == foo.some_functionality(*args)

would do the trick (although it feels a little wrong declaring local variables this way). How can I do this in a way so that all the relevant attributes of the method correctly transform into the function versions? I would appreciate any suggestions.

P.S.

I am aware that I can decorate methods of class instances, but this is not really what I am after. It is more about (1) a preference for the function version syntax (2) the fact that my decorators make functions map over collections of objects in fancy and optimized ways. Getting behavior (2) by decorating methods would require my collections classes to inherit attributes from the objects they contain, which is orthogonal to the collection semantics.

Gabriel Mitchell
  • 979
  • 5
  • 17
  • Modifying the dictionary returned by `locals()` is undefined. I wonder why you are using `locals()` instead of `globals()` here anyway. As far as I understand, the functions are supposed to go to the global scope of the module. – Sven Marnach Feb 29 '12 at 19:34

2 Answers2

3

Are you aware that you can use the unbound methods directly?

obj.foo(arg) is equivalent to ObjClass.foo(obj, arg)

class MyClass(object):
    def foo(self, arg):
        ...

obj = MyClass()
print obj.foo(3) == MyClass.foo(obj, 3) # True

See also Class method differences in Python: bound, unbound and static and the documentation.

Community
  • 1
  • 1
codeape
  • 97,830
  • 24
  • 159
  • 188
  • Haha, yes. Well I guess I didn't know that. Or perhaps I did but got so deep into the problem that I didn't realize the simple solution. Thanks. – Gabriel Mitchell Feb 29 '12 at 20:10
  • @GabrielMitchell: If you are using Python 2.x, you should be aware that the first argument to such an unbound method must be of type `MyClass`, which might interfere with passing collections of `MyClass` instances. (You can define a magic decorator that changes this behaviour, though.) – Sven Marnach Feb 29 '12 at 20:15
  • @SvenMarnach Yeah, I am on 2.7. One of my decorators parses the args to find instances of the relevant collections as part of determining what actually gets called. Is there something about special about 3.x that would obviate the need for this? – Gabriel Mitchell Feb 29 '12 at 20:45
  • @GabrielMitchell: The change in Python 3.x is really simple -- it does not check the type of the first argument, while Python 2.x does. This makes the whole concept of an "unbound method" unnecessary in 3.x -- they are just plain functions. (I think I did not really get your question, so my answer might miss the point.) – Sven Marnach Feb 29 '12 at 20:48
  • The Python 3 change is great, IMO. I always felt that the type checking in unbound methods goes against the whole dynamic/duck typing style of the language. – codeape Mar 01 '12 at 12:20
0

You say you have a preference for the function syntax. You could just define all your methods outside the class instead, and they would work exactly as you desire.

class C(object):
    def __init__(self,x):
        self.x = x

def f(c,y):
    "Some doc string"
    return c.x + y
def g(c,y,z):
    "Some other doc string"
    return c.x + y + z

If you want them on the class as well, you can always:

for func in f, g:
    setattr(C, func.__name__, func)

Or with locals introspection instead of function name introspection:

for name in 'f', 'g':
    setattr(C, name, locals()[name])

Or with no introspection, which is arguably a lot simpler and easier to manage unless you have quite a lot of these methods/functions:

C.f = f
C.g = g

This also avoids the potential issue mentioned in the comments on codeape's answer about Python checking that the first argument of an unbound method is an instance of the class.

Ben
  • 68,572
  • 20
  • 126
  • 174