Basic Descriptor/Decorator
You just need to keep in mind which function you should decorate. Your function is being created in __get__
, so it won't help to use the wrapper as a decorator, instead, you need to apply it in the __get__
method. As an aside, you can use either functools.update_wrapper
or decorators.decorator
for this. They work very similarly, except that you have to keep the result of decorators.decorator
whereas functools.update_wrapper
returns None
. Both have signature f(wrapper, wrapped)
.
from functools import update_wrapper
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)
# update the function to have the correct metadata
update_wrapper(f, self.fn)
return f
class A(object):
@class_or_instance
def func1(self,*args):
"""some docstring"""
pass
Now if you do:
print A.func1.__doc__
You'll see "some docstring". Yay!
Cached property decorator
The key here is that you can only affect what gets returned. Since class_or_instance
doesn't actually serve as the function, it doesn't really matter what you do with it. Keep in mind that this method causes the function to be rebound every time. I suggest you add a little bit of magic instead and bind/cache the function after the first call, which really just involves adding a setattr
call.
from functools import update_wrapper
import types
class class_or_instance(object):
# having optional func in case is passed something that doesn't have a correct __name__
# (like a lambda function)
def __init__(self, name_or_func):
self.fn = fn
self.name = fn.__name__
def __get__(self, obj, cls):
print "GET!!!"
if obj is not None:
f = lambda *args, **kwds: self.fn(obj, *args, **kwds)
update_wrapper(f, self.fn)
setattr(obj, self.name, types.MethodType(f, obj, obj.__class__))
else:
f = lambda *args, **kwds: self.fn(cls, *args, **kwds)
update_wrapper(f, self.fn)
return f
And then we can test it out...neato:
A.func1 #GET!!!
obj = A()
obj.func1 #GET!!!
obj.func1 is obj.func1 # True
A.func1 # GET!!!
obj2 = A()
obj2.func1 is not obj.fun1 # True + GET!!!