how to know if I'm decorating a method - python

It seems that mymethod is not yet a method when the decorator is called.
import inspect
class decorator(object):
def __call__(self, call):
if inspect.ismethod(call): #Not working yet
obj = "method"
args = inspect.getargspec(call)[0][1:]
elif inspect.isfunction(call):
obj = "function"
args = inspect.getargspec(call)[0]
elif inspect.isclass(call):
obj = "class"
args = inspect.getargspec(call.__init__)[0][1:]
args="(%s)" % repr(args)[1:-1].replace("'","")
print "Decorate %s %s%s" % (obj, call.__name__, args)
return call
#decorator()
def myfunction (a,b): pass
#decorator()
class myclass():
def __init__(self, a, b): pass
#decorator()
def mymethod(self, a, b): pass
if inspect.isfunction(myclass.mymethod):
print "mymethod is a function"
if inspect.ismethod(myclass.mymethod):
print "mymethod is a method"
Output:
Decorate function myfunction(a, b)
Decorate function mymethod(self, a, b)
Decorate class myclass(a, b)
mymethod is a method
I would know if the first argument is 'self', but there will be a less dirty solution?
Edit: Why?
I want to populate a list of callables and their arguments, if it is a function or a class, and I can pass the expected arguments, then I call it, but if it is a method, I have no "self" argument to pass. Something like:
import inspect
class ToDo(object):
calls=[]
def do(self, **kwargs):
for call in self.calls:
if 'self' in call.args:
print "This will fail."
args = {}
for arg in call.args:
args[arg]=kwargs.get(arg, None)
call.call(**args)
TODO = ToDo()
class decorator(object):
def __call__(self, call):
if inspect.isfunction(call):
args = inspect.getargspec(call)[0]
elif inspect.isclass(call):
args = inspect.getargspec(call.__init__)[0][1:]
self.call = call
self.args = args
TODO.calls.append(self)
return call
TODO.do(a=1, b=2)

You can't really make that distinction. Here's an example:
>>> class A(object):
... pass
...
>>> def foo(x): return 3
...
>>> A.foo = foo
>>> type(foo)
<type 'function'>
>>> type(A.foo)
<type 'instancemethod'>
As you can see, your decorator could apply to foo, since it is a function. But you can then simply create a class attribute that references the function to create a decorated method.
(This example is from Python 2.7; I'm not sure if anything has changed in Python 3 to make the above behave differently.)

You cannot tell method from function but you can check if first argument looks like self:
def __call__(self, func):
def new_func(*args, **kw):
if len(args) and hasattr(args[0], '__dict__') \
and '__class__' in dir(args[0]) and func.__name__ in dir(args[0])\
and '__func__' in dir(getattr(args[0], func.__name__))\
and getattr(args[0], func.__name__).__func__ == self.func:
return my_func(*args[1:], **kw)
else:
return my_func(*args, **kw)
self.func = new_func
return new_func
But that won't work for nested decorators - next decorator will change the function and comparison with self.func won't work.
Another approach - to check if first argument's name of decorated function is self - this is
very strong convention in Python so may be good enough:
def __call__(self, func):
def new_func(*args, **kw):
if len(inspect.getfullargspec(func).args)\
and inspect.getfullargspec(func).args[0] == 'self':
return my_func(*args[1:], **kw)
else:
return my_func(*args, **kw)
self.func = new_func
return new_func

Related

Class decorator for methods from other class [duplicate]

This question already has answers here:
Decorating class methods - how to pass the instance to the decorator?
(3 answers)
Closed 2 years ago.
NOTE:
I've got a related question here:
How to access variables from a Class Decorator from within the method it's applied on?
I'm planning to write a fairly complicated decorator. Therefore, the decorator itself should be a class of its own. I know this is possible in Python (Python 3.8):
import functools
class MyDecoratorClass:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
def __call__(self, *args, **kwargs):
# do stuff before
retval = self.func(*args, **kwargs)
# do stuff after
return retval
#MyDecoratorClass
def foo():
print("foo")
Now my problem starts when I try to apply the decorator on a method instead of just a function - especially if it's a method from another class. Let me show you what I've tried:
 
1. Trial one: identity loss
The decorator MyDecoratorClass below doesn't (or shouldn't) do anything. It's just boilerplate code, ready to be put to use later on. The method foo() from class Foobar prints the object it is called on:
import functools
class MyDecoratorClass:
def __init__(self, method):
functools.update_wrapper(self, method)
self.method = method
def __call__(self, *args, **kwargs):
# do stuff before
retval = self.method(self, *args, **kwargs)
# do stuff after
return retval
class Foobar:
def __init__(self):
# initialize stuff
pass
#MyDecoratorClass
def foo(self):
print(f"foo() called on object {self}")
return
Now what you observe here is that the self in the foo() method gets swapped. It's no longer a Foobar() instance, but a MyDecoratorClass() instance instead:
>>> foobar = Foobar()
>>> foobar.foo()
foo() called from object <__main__.MyDecoratorClass object at 0x000002DAE0B77A60>
In other words, the method foo() loses its original identity. That brings us to the next trial.
 
2. Trial two: keep identity, but crash
I attempt to preserve the original identity of the foo() method:
import functools
class MyDecoratorClass:
def __init__(self, method):
functools.update_wrapper(self, method)
self.method = method
def __call__(self, *args, **kwargs):
# do stuff before
retval = self.method(self.method.__self__, *args, **kwargs)
# do stuff after
return retval
class Foobar:
def __init__(self):
# initialize stuff
pass
#MyDecoratorClass
def foo(self):
print(f"foo() called on object {self}")
return
Now let's test:
>>> foobar = Foobar()
>>> foobar.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __call__
AttributeError: 'function' object has no attribute '__self__'
Yikes!
EDIT
Thank you #AlexHall and #juanpa.arrivillaga for your solutions. They both work. However, there is a subtle difference between them.
Let's first take a look at this one:
def __get__(self, obj, objtype) -> object:
temp = type(self)(self.method.__get__(obj, objtype))
print(temp)
return temp
I've introduced a temporary variable, just to print what __get__() returns. Each time you access the method foo(), this __get__() function returns a new MyDecoratorClass() instance:
>>> f = Foobar()
>>> func1 = f.foo
>>> func2 = f.foo
>>> print(func1 == func2)
>>> print(func1 is func2)
<__main__.MyDecoratorClass object at 0x000001B7E974D3A0>
<__main__.MyDecoratorClass object at 0x000001B7E96C5520>
False
False
The second approach (from #juanpa.arrivillaga) is different:
def __get__(self, obj, objtype) -> object:
temp = types.MethodType(self, obj)
print(temp)
return temp
The output:
>>> f = Foobar()
>>> func1 = f.foo
>>> func2 = f.foo
>>> print(func1 == func2)
>>> print(func1 is func2)
<bound method Foobar.foo of <__main__.Foobar object at 0x000002824BBEF4C0>>
<bound method Foobar.foo of <__main__.Foobar object at 0x000002824BBEF4C0>>
True
False
There is a subtle difference, but I'm not sure why.
Functions are descriptors and that's what allows them to auto-bind self. The easiest way to deal with this is to implement decorators using functions so that this is handled for you. Otherwise you need to explicitly invoke the descriptor. Here's one way:
import functools
class MyDecoratorClass:
def __init__(self, method):
functools.update_wrapper(self, method)
self.method = method
def __get__(self, instance, owner):
return type(self)(self.method.__get__(instance, owner))
def __call__(self, *args, **kwargs):
# do stuff before
retval = self.method(*args, **kwargs)
# do stuff after
return retval
class Foobar:
def __init__(self):
# initialize stuff
pass
#MyDecoratorClass
def foo(self, x, y):
print(f"{[self, x, y]=}")
#MyDecoratorClass
def bar(spam):
print(f"{[spam]=}")
Foobar().foo(1, 2)
bar(3)
Here the __get__ method creates a new instance of MyDecoratorClass with the bound method (previously self.method was just a function since no instance existed yet). Also note that __call__ just calls self.method(*args, **kwargs) - if self.method is now a bound method, the self of FooBar is already implied.
You can implement the descriptor protocol, an example of how functions do it (but in pure python) is available in the Descriptor HOWTO, translated to your case:
import functools
import types
class MyDecoratorClass:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
def __call__(self, *args, **kwargs):
# do stuff before
retval = self.func(*args, **kwargs)
# do stuff after
return retval
def __get__(self, obj, objtype=None):
if obj is None:
return self
return types.MethodType(self, obj)
Note, return types.MethodType(self, obj) is essentially equivalent to
return lambda *args, **kwargs : self.func(obj, *args, **kwargs)
Note from Kristof
Could it be that you meant this:
return types.MethodType(self, obj) is essentially equivalent to
return lambda *args, **kwargs : self(obj, *args, **kwargs)
Note that I replaced self.func(..) with self(..). I tried, and only this way I can ensure that the statements at # do stuff before and # do stuff after actually run.

A lean interface for making python decorator classes

I've been looking to make an object-oriented setup to make decorator factories. A simple version can be found in this stackoverflow answer. But I'm not totally satisfied with the simplicity of the interface.
THe kind of interface I envision would use special class Decora that I can use as such:
class MultResult(Decora):
mult: int = 1
i_am_normal = Literal(True) # will not be included in the __init__
def __call__(self, *args, **kwargs): # code for the wrapped functoin
return super().__call__(*args, **kwargs) * self.mult
That would result in the following behavior:
>>> #MultResult(mult=2)
... def f(x, y=0):
... return x + y
...
>>>
>>> signature(MultResult) # The decorator has a proper signature
<Signature (func=None, *, mult: int = 1)>
>>> signature(f) # The decorated has a proper signature
<Signature (x, y=0)>
>>> f(10)
20
>>>
>>> ff = MultResult(lambda x, y=0: x + y) # default mult=1 works
>>> assert ff(10) == 10
>>>
>>> #MultResult # can also use like this
... def fff(x, y=0):
... return x + y
...
>>> assert fff(10) == 10
>>>
>>> MultResult(any_arg=False) # should refuse that arg!
Traceback (most recent call last):
...
TypeError: TypeError: __new__() got unexpected keyword arguments: {'any_arg'}
We can start with a base such as this:
from functools import partial, update_wrapper
class Decorator:
"""A transparent decorator -- to be subclassed"""
def __new__(cls, func=None, **kwargs):
if func is None:
return partial(cls, **kwargs)
else:
self = super().__new__(cls)
self.func = func
for attr_name, attr_val in kwargs.items():
setattr(self, attr_name, attr_val)
return update_wrapper(self, func)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
It allows one to define decorator factories by subclassing and defining a __new__ method (that should call its parent).
Then, one can use __init_subclass__ to extract the desired arguments of that __new__ directly from attributes of the subclass, as such:
from inspect import Signature, Parameter
PK, KO = Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY
class Literal:
"""An object to indicate that the value should be considered literally"""
def __init__(self, val):
self.val = val
class Decora(Decorator):
_injected_deco_params = ()
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if '__new__' not in cls.__dict__: # if __new__ hasn't been defined in the subclass...
params = ([Parameter('self', PK), Parameter('func', PK, default=None)])
cls_annots = getattr(cls, '__annotations__', {})
injected_deco_params = set()
for attr_name in (a for a in cls.__dict__ if not a.startswith('__')):
attr_obj = cls.__dict__[attr_name] # get the attribute
if not isinstance(attr_obj, Literal):
setattr(cls, attr_name, attr_obj) # what we would have done anyway...
# ... but also add a parameter to the list of params
params.append(Parameter(attr_name, KO, default=attr_obj,
annotation=cls_annots.get(attr_name, Parameter.empty)))
injected_deco_params.add(attr_name)
else: # it is a Literal, so
setattr(cls, attr_name, attr_obj.val) # just assign the literal value
cls._injected_deco_params = injected_deco_params
def __new__(cls, func=None, **kwargs):
if cls._injected_deco_params and not set(kwargs).issubset(cls._injected_deco_params):
raise TypeError("TypeError: __new__() got unexpected keyword arguments: "
f"{kwargs.keys() - cls._injected_deco_params}")
if func is None:
return partial(cls, **kwargs)
else:
return Decorator.__new__(cls, func, **kwargs)
__new__.__signature__ = Signature(params)
cls.__new__ = __new__
It passes the tests listed in the question, but I'm not super experienced with this kind of object oriented magic, so would be curious to see alternative solutions.

How to decorate an instance method with another instance method? [duplicate]

Can one write something like:
class Test(object):
def _decorator(self, foo):
foo()
#self._decorator
def bar(self):
pass
This fails: self in #self is unknown
I also tried:
#Test._decorator(self)
which also fails: Test unknown
I would like to temporarily change some instance variables
in the decorator and then run the decorated method, before
changing them back.
Would something like this do what you need?
class Test(object):
def _decorator(foo):
def magic( self ) :
print "start magic"
foo( self )
print "end magic"
return magic
#_decorator
def bar( self ) :
print "normal call"
test = Test()
test.bar()
This avoids the call to self to access the decorator and leaves it hidden in the class namespace as a regular method.
>>> import stackoverflow
>>> test = stackoverflow.Test()
>>> test.bar()
start magic
normal call
end magic
>>>
edited to answer question in comments:
How to use the hidden decorator in another class
class Test(object):
def _decorator(foo):
def magic( self ) :
print "start magic"
foo( self )
print "end magic"
return magic
#_decorator
def bar( self ) :
print "normal call"
_decorator = staticmethod( _decorator )
class TestB( Test ):
#Test._decorator
def bar( self ):
print "override bar in"
super( TestB, self ).bar()
print "override bar out"
print "Normal:"
test = Test()
test.bar()
print
print "Inherited:"
b = TestB()
b.bar()
print
Output:
Normal:
start magic
normal call
end magic
Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic
What you're wanting to do isn't possible. Take, for instance, whether or not the code below looks valid:
class Test(object):
def _decorator(self, foo):
foo()
def bar(self):
pass
bar = self._decorator(bar)
It, of course, isn't valid since self isn't defined at that point. The same goes for Test as it won't be defined until the class itself is defined (which its in the process of). I'm showing you this code snippet because this is what your decorator snippet transforms into.
So, as you can see, accessing the instance in a decorator like that isn't really possible since decorators are applied during the definition of whatever function/method they are attached to and not during instantiation.
If you need class-level access, try this:
class Test(object):
#classmethod
def _decorator(cls, foo):
foo()
def bar(self):
pass
Test.bar = Test._decorator(Test.bar)
import functools
class Example:
def wrapper(func):
#functools.wraps(func)
def wrap(self, *args, **kwargs):
print("inside wrap")
return func(self, *args, **kwargs)
return wrap
#wrapper
def method(self):
print("METHOD")
wrapper = staticmethod(wrapper)
e = Example()
e.method()
This is one way to access(and have used) self from inside a decorator defined inside the same class:
class Thing(object):
def __init__(self, name):
self.name = name
def debug_name(function):
def debug_wrapper(*args):
self = args[0]
print 'self.name = ' + self.name
print 'running function {}()'.format(function.__name__)
function(*args)
print 'self.name = ' + self.name
return debug_wrapper
#debug_name
def set_name(self, new_name):
self.name = new_name
Output (tested on Python 2.7.10):
>>> a = Thing('A')
>>> a.name
'A'
>>> a.set_name('B')
self.name = A
running function set_name()
self.name = B
>>> a.name
'B'
The example above is silly, but it works.
Here's an expansion on Michael Speer's answer to take it a few steps further:
An instance method decorator which takes arguments and acts on a function with arguments and a return value.
class Test(object):
"Prints if x == y. Throws an error otherwise."
def __init__(self, x):
self.x = x
def _outer_decorator(y):
def _decorator(foo):
def magic(self, *args, **kwargs) :
print("start magic")
if self.x == y:
return foo(self, *args, **kwargs)
else:
raise ValueError("x ({}) != y ({})".format(self.x, y))
print("end magic")
return magic
return _decorator
#_outer_decorator(y=3)
def bar(self, *args, **kwargs) :
print("normal call")
print("args: {}".format(args))
print("kwargs: {}".format(kwargs))
return 27
And then
In [2]:
test = Test(3)
test.bar(
13,
'Test',
q=9,
lollipop=[1,2,3]
)
​
start magic
normal call
args: (13, 'Test')
kwargs: {'q': 9, 'lollipop': [1, 2, 3]}
Out[2]:
27
In [3]:
test = Test(4)
test.bar(
13,
'Test',
q=9,
lollipop=[1,2,3]
)
​
start magic
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-3-576146b3d37e> in <module>()
4 'Test',
5 q=9,
----> 6 lollipop=[1,2,3]
7 )
<ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs)
11 return foo(self, *args, **kwargs)
12 else:
---> 13 raise ValueError("x ({}) != y ({})".format(self.x, y))
14 print("end magic")
15 return magic
ValueError: x (4) != y (3)
I found this question while researching a very similar problem. My solution is to split the problem into two parts. First, you need to capture the data that you want to associate with the class methods. In this case, handler_for will associate a Unix command with handler for that command's output.
class OutputAnalysis(object):
"analyze the output of diagnostic commands"
def handler_for(name):
"decorator to associate a function with a command"
def wrapper(func):
func.handler_for = name
return func
return wrapper
# associate mount_p with 'mount_-p.txt'
#handler_for('mount -p')
def mount_p(self, slurped):
pass
Now that we've associated some data with each class method, we need to gather that data and store it in a class attribute.
OutputAnalysis.cmd_handler = {}
for value in OutputAnalysis.__dict__.itervalues():
try:
OutputAnalysis.cmd_handler[value.handler_for] = value
except AttributeError:
pass
I use this type of decorator in some debugging situations, it allows overriding class properties by decorating, without having to find the calling function.
class myclass(object):
def __init__(self):
self.property = "HELLO"
#adecorator(property="GOODBYE")
def method(self):
print self.property
Here is the decorator code
class adecorator (object):
def __init__ (self, *args, **kwargs):
# store arguments passed to the decorator
self.args = args
self.kwargs = kwargs
def __call__(self, func):
def newf(*args, **kwargs):
#the 'self' for a method function is passed as args[0]
slf = args[0]
# replace and store the attributes
saved = {}
for k,v in self.kwargs.items():
if hasattr(slf, k):
saved[k] = getattr(slf,k)
setattr(slf, k, v)
# call the method
ret = func(*args, **kwargs)
#put things back
for k,v in saved.items():
setattr(slf, k, v)
return ret
newf.__doc__ = func.__doc__
return newf
Note: because I've used a class decorator you'll need to use #adecorator() with the brackets on to decorate functions, even if you don't pass any arguments to the decorator class constructor.
The simple way to do it.
All you need is to put the decorator method outside the class.
You can still use it inside.
def my_decorator(func):
#this is the key line. There's the aditional self parameter
def wrap(self, *args, **kwargs):
# you can use self here as if you were inside the class
return func(self, *args, **kwargs)
return wrap
class Test(object):
#my_decorator
def bar(self):
pass
Declare in inner class.
This solution is pretty solid and recommended.
class Test(object):
class Decorators(object):
#staticmethod
def decorator(foo):
def magic(self, *args, **kwargs) :
print("start magic")
foo(self, *args, **kwargs)
print("end magic")
return magic
#Decorators.decorator
def bar( self ) :
print("normal call")
test = Test()
test.bar()
The result:
>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>>
Decorators seem better suited to modify the functionality of an entire object (including function objects) versus the functionality of an object method which in general will depend on instance attributes. For example:
def mod_bar(cls):
# returns modified class
def decorate(fcn):
# returns decorated function
def new_fcn(self):
print self.start_str
print fcn(self)
print self.end_str
return new_fcn
cls.bar = decorate(cls.bar)
return cls
#mod_bar
class Test(object):
def __init__(self):
self.start_str = "starting dec"
self.end_str = "ending dec"
def bar(self):
return "bar"
The output is:
>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec
I have a Implementation of Decorators that Might Help
import functools
import datetime
class Decorator(object):
def __init__(self):
pass
def execution_time(func):
#functools.wraps(func)
def wrap(self, *args, **kwargs):
""" Wrapper Function """
start = datetime.datetime.now()
Tem = func(self, *args, **kwargs)
end = datetime.datetime.now()
print("Exection Time:{}".format(end-start))
return Tem
return wrap
class Test(Decorator):
def __init__(self):
self._MethodName = Test.funca.__name__
#Decorator.execution_time
def funca(self):
print("Running Function : {}".format(self._MethodName))
return True
if __name__ == "__main__":
obj = Test()
data = obj.funca()
print(data)
You can decorate the decorator:
import decorator
class Test(object):
#decorator.decorator
def _decorator(foo, self):
foo(self)
#_decorator
def bar(self):
pass

Member function decorator and self argument

The following minimal example of a decorator on a member function:
def wrap_function(func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
return wrapper
class Foo:
#wrap_function
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
outputs:
(<__main__.Foo object at 0x7fb294939898>, 'hi')
{}
So self is one of the args.
However when using a wrapper class:
class WrappedFunction:
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
print(args)
print(kwargs)
def wrap_function(func):
return WrappedFunction(func)
class Foo:
#wrap_function
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
the output is:
('hi',)
{}
So the self, that references the Foo object, is not accessible in the body of __call__ of the WrappedFunction object.
How can I make it accessible there?
You're losing the reference to your bounded instance by wrapping the function logic (but not the instance) and redirecting it to a class instance - at that point, the class instance's own self applies instead of the wrapped instance method as it gets lost in the intermediary decorator (wrap_function()).
You either have to wrap the call to the wrapped function and pass *args/**kwargs to it, or just make a proper wrapper class instead of adding an intermediary wrapper:
class WrappedFunction(object):
def __call__(self, func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
# NOTE: `WrappedFunction` instance is available in `self`
return wrapper
class Foo:
#WrappedFunction() # wrap directly, without an intermediary
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
# (<__main__.Foo object at 0x000001A2216CDBA8>, 'hi')
# {}
Sadly, but this might be the only solution as you need it in the __call__ function.
Would suggest checking this out: What is the difference between __init__ and __call__ in Python?
def wrap_function(func):
def wrapper(*args, **kwargs):
x = WrappedFunction(func)
x(*args, **kwargs)
return wrapper

Calling Python instance methods in function decorators

Is there a clean way to have a decorator call an instance method on a class only at the time an instance of the class is instantiated?
class C:
def instance_method(self):
print('Method called')
def decorator(f):
print('Locals in decorator %s ' % locals())
def wrap(f):
print('Locals in wrapper %s' % locals())
self.instance_method()
return f
return wrap
#decorator
def function(self):
pass
c = C()
c.function()
I know this doesn't work because self is undefined at the point decorator is called (since it isn't called as an instance method as there is no available reference to the class). I then came up with this solution:
class C:
def instance_method(self):
print('Method called')
def decorator():
print('Locals in decorator %s ' % locals())
def wrap(f):
def wrapped_f(*args):
print('Locals in wrapper %s' % locals())
args[0].instance_method()
return f
return wrapped_f
return wrap
#decorator()
def function(self):
pass
c = C()
c.function()
This uses the fact that I know the first argument to any instance method will be self. The problem with the way this wrapper is defined is that the instance method is called every time the function is executed, which I don't want. I then came up with the following slight modification which works:
class C:
def instance_method(self):
print('Method called')
def decorator(called=[]):
print('Locals in decorator %s ' % locals())
def wrap(f):
def wrapped_f(*args):
print('Locals in wrapper %s' % locals())
if f.__name__ not in called:
called.append(f.__name__)
args[0].instance_method()
return f
return wrapped_f
return wrap
#decorator()
def function(self):
pass
c = C()
c.function()
c.function()
Now the function only gets called once, but I don't like the fact that this check has to happen every time the function gets called. I'm guessing there's no way around it, but if anyone has any suggestions, I'd love to hear them! Thanks :)
I came up with this as a possible alternative solution. I like it because there is only one call that happens when the function is defined, and one when the class is instantiated. The only downside is a tiny bit of extra memory consumption for the function attribute.
from types import FunctionType
class C:
def __init__(self):
for name,f in C.__dict__.iteritems():
if type(f) == FunctionType and hasattr(f, 'setup'):
self.instance_method()
def instance_method(self):
print('Method called')
def decorator(f):
setattr(f, 'setup', True)
return f
#decorator
def function(self):
pass
c = C()
c.function()
c.function()
I think you're asking something fundamentally impossible. The decorator will be created at the same time as the class, but the instance method doesn't exist until the instance does, which is later. So the decorator can't handle instance-specific functionality.
Another way to think about this is that the decorator is a functor: it transforms functions into other functions. But it doesn't say anything about the arguments of these functions; it works at a higher level than that. So invoking an instance method on an argument of function is not something that should be done by a decorator; it's something that should be done by function.
The way to get around this is necessarily hackish. Your method looks OK, as hacks go.
It can be achieved by using callables as decorators.
class ADecorator(object):
func = None
def __new__(cls, func):
dec = object.__new__(cls)
dec.__init__(func)
def wrapper(*args, **kw):
return dec(*args, **kw)
return wrapper
def __init__(self, func, *args, **kw):
self.func = func
self.act = self.do_first
def do_rest(self, *args, **kw):
pass
def do_first(self, *args, **kw):
args[0].a()
self.act = self.do_rest
def __call__(self, *args, **kw):
return self.act(*args, **kw)
class A(object):
def a(self):
print "Original A.a()"
#ADecorator
def function(self):
pass
a = A()
a.function()
a.function()
How should multiple instances of the class C behave? Should instance_method only be called once, no matter which instance calls function? Or should each instance call instance_method once?
Your called=[] default argument makes the decorator remember that something with string name function has been called. What if decorator is used on two different classes which both have a method named function? Then
c=C()
d=D()
c.function()
d.function()
will only call c.instance_method and prevent d.instance_method from getting called. Strange, and probably not what you want.
Below, I use self._instance_method_called to record if self.instance_method has been called. This makes each instance of C call instance_method at most once.
If you want instance_method to be called at most once regardless of which instance of C calls function, then simply define _instance_method_called as a class attribute instead of an instance attribute.
def decorator():
print('Locals in decorator %s ' % locals())
def wrap(f):
def wrapped(self,*args):
print('Locals in wrapper %s' % locals())
if not self._instance_method_called:
self.instance_method()
self._instance_method_called=True
return f
return wrapped
return wrap
class C:
def __init__(self):
self._instance_method_called=False
def instance_method(self): print('Method called')
#decorator()
def function(self):
pass
c = C()
# Locals in decorator {}
c.function()
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1aec>, 'args': (), 'f': <function function at 0xb76eed14>}
# Method called
c.function()
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1aec>, 'args': (), 'f': <function function at 0xb76eed14>}
d = C()
d.function()
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1bcc>, 'args': (), 'f': <function function at 0xb76eed14>}
# Method called
d.function()
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1bcc>, 'args': (), 'f': <function function at 0xb76eed14>}
Edit: To get rid of the if statement:
def decorator():
print('Locals in decorator %s ' % locals())
def wrap(f):
def rest(self,*args):
print('Locals in wrapper %s' % locals())
return f
def first(self,*args):
print('Locals in wrapper %s' % locals())
self.instance_method()
setattr(self.__class__,f.func_name,rest)
return f
return first
return wrap
class C:
def instance_method(self): print('Method called')
#decorator()
def function(self):
pass

Categories