Python - using decorator.py to save the docstring method

I am interested in invoking an instance method of both a class method and an instance method. This can be done using the class_or_instance decorator as follows:

class class_or_instance(object): def __init__(self, fn): self.fn = fn def __get__(self, obj, cls): if obj is not None: return lambda *args, **kwds: self.fn(obj, *args, **kwds) else: return lambda *args, **kwds: self.fn(cls, *args, **kwds) class A(object): @class_or_instance def func1(self,*args): # method body 

Now I can call func1 either as A.func1(*args) or A().func1(*args) . However, the docstring func1 disappears. One way to deal with this is to use the decorator from decorator.py , but I had problems getting it working with a decorator, which is more of a class than a function. Any suggestions on how to do this?

EDIT: functools.wraps() in this case will not work correctly. See related question in stackoverflow

+4
source share
1 answer

Main descriptor / decorator

You just need to keep in mind which function you should decorate. Your function is created in __get__ , so it will not help to use the wrapper as a decorator, instead you need to use it in the __get__ method. You can use functools.update_wrapper or decorators.decorator for this. They work very similarly, except that you must save the result of decorators.decorator , while functools.update_wrapper returns None . Both have the 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 will see "some docstring". Hooray!


Cache Properties Keeper

The key point here is that you can only influence what comes back. Since class_or_instance is not actually a function, it really doesn't matter what you do with it. Keep in mind that this method causes the function to bounce every time. I suggest you add a little magic and bind / cache the function after the first call, which is really connected with the addition of the 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 check it ... 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!!! 
+3
source

Source: https://habr.com/ru/post/1486736/


All Articles