I've got a decorator that I've implemented as a class:
class Cached(object):
def __init__(self, func):
self.cache = None
self.func = func
def __call__(self, *args, **kwargs):
if self.cache is None or (time.time() - self.cache[0] >= 1000):
res = self.f(*args, **kwargs)
self.cache = (time.time(), res)
else:
res = self.cache[1]
return res
I want to use this decorator to decorate a method of a class, e.g.:
class Foo(object):
def __init__(self, x):
self.x = x
#cached
def bar(self, y):
return self.x + y
As it stands,
f = Foo(10)
f.bar(11)
throws TypeError: foo() takes exactly 2 arguments (1 given). f.bar(f, 11) works, but is the code smell equivalent of summer in New York City during a sanitation worker strike. What am I missing?
ETA: Originally, I was trying to implement Cached as a function:
def cached(cache):
def w1(func):
def w2(*args, **kwargs):
# same
return w2
return w1
but I kept getting weird scoping errors about cache being used before it's defined, which switching to a decorator class fixed.
You need to add this to your decorator class:
def __get__(self, obj, objtype):
"""support instance methods"""
from functools import partial
return partial(self.__call__, obj)
Related
In python I would like to check that a given function is called within a with statement of a given type
class Bar:
def __init__(self, x):
self.x = x
def __enter__(self):
return self
def __exit__(self, *a, **k):
pass
def foo(x):
# assert that the enclosing context is an instance of bar
# assert isinstance('enclosed context', Bar)
print(x*2)
with Bar(1) as bar:
foo(bar.x)
I could do something like enforcing an arg passed into foo and wrapping functions in a decorator i.e.
class Bar:
def __init__(self, x):
self.x = x
def __enter__(self):
return self
def __exit__(self, *a, **k):
pass
def assert_bar(func):
def inner(bar, *a, **k):
assert isinstance(bar, Bar)
return func(*a, **k)
return inner
#assert_bar
def foo(x):
print(x*2)
with Bar(1) as bar:
foo(bar, bar.x)
but then I would have to pass around bar everywhere.
As a result I'm trying to see if there's a way to access the with context
Note: The real world application of this is ensuring that mlflow.pyfunc.log_model is called within an mlflow.ActiveRun context, or it leaves an ActiveRun open, causing problems later on
Here's an ugly way to do it: global state.
class Bar:
active = 0
def __init__(self, x):
self.x = x
def __enter__(self):
Bar.active += 1
return self
def __exit__(self, *a, **k):
Bar.active -= 1
from functools import wraps
def assert_bar(func):
#wraps(func)
def wrapped(*vargs, **kwargs):
if Bar.active <= 0:
# raises even if asserts are disabled
raise AssertionError()
return func(*vargs, **kwargs)
return wrapped
Unfortunately I don't think there is any non-ugly way to do it. If you aren't going to pass around a Bar instance yourself then you must rely on some state existing somewhere else to tell you that a Bar instance exists and is currently being used as a context manager.
The only way you can avoid that global state is to store the state in the instance, which means the decorator needs to be an instance method and the instance needs to exist before the function is declared:
from functools import wraps
class Bar:
def __init__(self, x):
self.x = x
self.active = 0
def __enter__(self):
self.active += 1
return self
def __exit__(self, *a, **k):
self.active -= 1
def assert_this(self, func):
#wraps(func)
def wrapped(*vargs, **kwargs):
if self.active <= 0:
raise AssertionError()
return func(*vargs, **kwargs)
return wrapped
bar = Bar(1)
#bar.assert_this
def foo(x):
print(x + 1)
with bar:
foo(1)
This is still "global state" in the sense that the function foo now holds a reference to the Bar instance that holds the state. But it may be more palatable if foo is only ever going to be a local function.
I would like to extend the behavior of the builtin #property decorator. The desired usage is shown in the code below:
class A:
def __init__(self):
self.xy = 42
#my_property(some_arg="some_value")
def x(self):
return self.xy
print(A().x) # should print 42
First of all, the decorator should retain the property behavior so that no () is needed after the x. Next, I would like to be able to access the arguments a programmer passes to my decorator.
I started off with this:
class my_property(property):
def __init__(self, fn):
super().__init__(fn)
TypeError: __init__() got an unexpected keyword argument 'some_arg'
After adding **kwargs:
class my_property(property):
def __init__(self, fn, **kwargs):
super().__init__(fn)
TypeError: __init__() missing 1 required positional argument: 'fn'
OK, let's do *args instead:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
TypeError: 'my_property' object is not callable
Let's make it callable:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
def __call__(self, *args, **kwargs):
pass
No errors, but prints None instead of 42
And now I am lost. I have not even yet managed to access `some_arg="some_value" and the property behavior seems to be already gone. What is wrong and how to fix it?
It's not clear how you intent to use some_arg, but to pass a parameter to a decorator you need to have "two layers" of decorators
#my_decorator(arg)
def foo():
return
under the hood this translates to my_decorator(arg)(foo) (i.e. my_decorator(arg) must return another decorator that is called with foo). The inner decorator in this case should be your custom implementation of property
def my_property(some_arg):
class inner(object):
def __init__(self, func):
print(some_arg) # do something with some_arg
self.func = func
def __get__(self, obj, type_=None):
return self.func(obj)
return inner
Now you can use it like this:
class MyClass:
def __init__(self, x):
self.x = x
#my_property('test!')
def foo(self):
return self.x
obj = MyClass(42) # > test!
obj.foo # > 42
Read more about descriptors here
I'm trying to decorate all methods in class and i succeded with this code, but i'm also trying to log calls to operators like * + - / , is there any way to decorate them or something like getattr(self,"*") to log the calls ?
class Logger(object):
def __init__(self, bool):
self.bool = bool
def __call__(self, cls):
class DecoratedClass(cls):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
if not(self.bool):
return
methods = [func for func in dir(cls)
if callable(getattr(cls, func))
and not func.startswith("__class")]
for func in methods:
old_func = getattr(cls, func)
def decorated_function(fname, fn):
def loggedFunction(*args, **kwargs):
print("Calling {0} from {3} with params {1} and kwargs {2}".format(fname.upper(), args, kwargs, cls))
return fn(*args, **kwargs)
return loggedFunction
setattr(cls, func, decorated_function(func, old_func))
return DecoratedClass
#Logger(True)
class DummyClass():
def __init__(self,foo):
self.foo = foo
def bar(self):
print(self.foo)
def __mul__(self,other):
print("Hello",other)
if __name__ == '__main__':
a = DummyClass('hola')
a.method()
a.__mul__(a) #this is logged
print(a*a) #this is not logged by decorator
Thanks to Ćukasz, here is a working script.
A difficulty I encountered is to handle multiple instances and avoid to decorate multiple times the same class methods. To handle this problem, I keep track of the decorated class methods (cls.__logged).
Another difficulty is to deal with the magic methods like __setattr__, __getattribute__, __repr__, ... My solution is to ignore them, except for a list that you must define at start (loggable_magic_methods).
from functools import wraps
loggable_magic_methods = ['__mul__',]
def is_magic_method(method):
return method.startswith('__')
class Logger(object):
def __init__(self, bool):
self.bool = bool
def __call__(self, cls):
class LoggedClass(cls):
cls.__logged = []
def __init__(instance, *args, **kwargs):
super().__init__(*args, **kwargs)
if not(self.bool):
return
methods = [funcname for funcname in dir(instance)
if callable(getattr(instance, funcname))
and (funcname in loggable_magic_methods or not is_magic_method(funcname))]
def logged(method):
#wraps(method)
def wrapper(*args, **kwargs):
print (method.__name__, args, kwargs, cls)
return method(*args, **kwargs)
return wrapper
for funcname in methods:
if funcname in cls.__logged:
continue
if is_magic_method(funcname):
setattr(cls, funcname, logged(getattr(cls, funcname)))
cls.__logged.append(funcname)
else:
setattr(instance, funcname, logged(getattr(instance, funcname)))
return LoggedClass
#Logger(True)
class DummyClass():
def __init__(self, foo, coef):
self.foo = foo
self.coef = coef
def bar(self):
print(self.foo)
def __mul__(self, other):
print(self.foo)
print(other.foo)
return self.coef * other.coef
if __name__ == '__main__':
a = DummyClass('hola', 1)
a.bar()
print()
print(a.__mul__(a))
print()
print(a*a)
print()
b = DummyClass('gracias', 2)
b.bar()
print()
print(b.__mul__(a))
print()
print(b*a)
Currently you are patching values on instance. Your usage of cls in __init__ signature is false friend - actually it's old plain self in this case.
If you want to override magic methods, interpreter looks for them on class objects, not on instances.
Minimal example:
class DummyClass:
def __init__(self, foo):
self.foo = foo
def __mul__(self, other):
return self.foo * other.foo
def logged(method):
def wrapper(*args, **kwargs):
print (method.__name__, args, kwargs)
return method(*args, **kwargs)
return wrapper
DummyClass.__mul__ = logged(DummyClass.__mul__)
a = DummyClass(1)
b = DummyClass(2)
assert a * a == 1
assert a * b == 2
assert b * b == 4
Each call is logged.
>>> a = DummyClass(1)
>>> b = DummyClass(2)
>>> assert a * a == 1
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BFEB8>) {}
>>> assert a * b == 2
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BF080>) {}
>>> assert b * b == 4
__mul__ (<__main__.DummyClass object at 0x00000000011BF080>, <__main__.DummyClass object at 0x00000000011BF080>) {}
I'll leave a task of rewriting monkey-patching approach to you.
I'm trying to use a subclass for the int class to attach an additional label ("headline"). All works if I access the individual object, but if I collect several in a list, they all have the same property, while I would expect them to have the one I specified when creating the object.
I also tried with methods instead of properties to no further results.
I'm using Python 3.4.3.
import unittest
class LabeledInt(int):
def __new__(cls, *args, **kwargs):
cls._headline = args[1]
return super(LabeledInt, cls).__new__(cls, args[0])
#property
def headline(self):
return self._headline
class SomeNumbers:
def __init__(self, arg):
self.arg = arg
#property
def something(self):
return LabeledInt(self.arg, "Something")
#property
def something_squared(self):
return LabeledInt(self.arg ** 2, "Squared")
#property
def something_exponential(self):
return LabeledInt(self.arg ** self.arg, "Exp.")
def all_numbers(self):
array = [
LabeledInt(self.arg, "Something"),
LabeledInt(self.arg ** 2, "Squared"),
LabeledInt(self.arg ** self.arg, "Exp.")
]
return array
S = SomeNumbers(2)
class Test(unittest.TestCase):
def test_something(self):
self.assertEqual(2, S.something)
self.assertEqual("Something", S.something.headline)
def test_something_squard(self):
self.assertEqual(4, S.something_squared)
self.assertEqual("Squared", S.something_squared.headline)
def test_exp(self):
self.assertEqual(4, S.something_exponential)
self.assertEqual("Exp.", S.something_exponential.headline)
def test_all_numbers_1(self):
self.assertEqual(2, S.all_numbers()[0])
def test_all_numbers_2(self):
self.assertEqual("Something", S.all_numbers()[0].headline)
def test_all_numbers_3(self):
self.assertEqual(4, S.all_numbers()[1])
def test_all_numbers_4(self):
self.assertEqual("Squared", S.all_numbers()[1].headline)
def test_all_numbers_5(self):
self.assertEqual(4, S.all_numbers()[2])
def test_all_numbers_6(self):
self.assertEqual("Exp.", S.all_numbers()[2].headline)
for n in S.all_numbers():
print(n.headline)
>>>
Exp.
Exp.
Exp.
Tests "test_all_numbers_2" und "...4" fail.
Why does this happen? And what's the best way around it? Thanks a lot.
class LabeledInt(int):
def __new__(cls, *args, **kwargs):
cls._headline = args[1]
# ^^^
return super(LabeledInt, cls).__new__(cls, args[0])
You are setting the attribute of the class, not of the instance. Try this:
class LabeledInt(int):
def __new__(cls, *args, **kwargs):
self = super(LabeledInt, cls).__new__(cls, args[0])
self._headline = args[1]
# ^^^^
return self
PS: don't use *args and **kwargs if you are neither using them, nor passing them around. Also, Python 3's super() doesn't need arguments anymore. Consider using this code:
class LabeledInt(int):
def __new__(cls, value, headline):
self = super().__new__(cls, value)
self._headline = headline
return self
This question already has answers here:
How can I decorate an instance method with a decorator class?
(2 answers)
Closed 4 years ago.
I'm trying to memoize using a decorator with the decorator being a class not a function, but I'm getting the error
TypeError: seqLength() takes exactly 2 arguments (1 given)
I'm guessing this has something to do with the classes, but not sure what's wrong from there.
The code:
import sys
class memoize(object):
'''memoize decorator'''
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(self, *args)
self.cache[args] = value
return value
class collatz(object):
def __init__(self, n):
self.max = 1
self.n = n
#memoize
def seqLength(self, n):
if n>1:
if n%2 == 0:
return 1+self.seqLength(n/2)
else:
return 1+self.seqLength(3*n+1)
else:
return 1
def maxLength(self):
for n in xrange(1, self.n):
l = self.seqLength(n)
if l > self.max:
self.max = n
return self.max
n = int(sys.argv[1])
c = collatz(n)
print c.maxLength()
This is confusing, syntactically. It's not clear if self.func is part of your memoize or a separate function that's part of some other object of some other class. (You mean the latter, BTW)
value = self.func(self, *args)
Do this to make it clear that the_func is just a function, not a member of the memoize class.
the_func= self.func
value= the_func( *args )
That kind of thing prevents confusion over the class to which self. is bound.
Also, please spell it Memoize. With a leading capital letter. It is a class definition, after all.
Using a class as a decorator is tricky, because you have to implement the descriptor protocol correctly (the currently accepted answer doesn't.) A much, much easier solution is to use a wrapper function, because they automatically implement the descriptor protocol correctly. The wrapper equivalent of your class would be:
import functools
def memoize(func):
cache = {}
#functools.wraps(func)
def wrapper(*args):
try:
return cache[args]
except KeyError:
value = func(*args)
cache[args] = value
return value
return wrapper
When have so much state you want to encapsulate it in a class anyway, you can still use a wrapper function, for example like so:
import functools
class _Memoize(object):
'''memoize decorator helper class'''
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(*args)
self.cache[args] = value
return value
def memoize(func):
o = _Memoize(func)
#functools.wraps(func)
def wrapper(*args):
return o(*args)
return wrapper
A decorator is just syntactic sugar for foo = decorator(foo), so in this case you're ending up making the self of seqLength be memoize instead of collatz. You need to use descriptors. This code works for me:
class memoize(object):
'''memoize descriptor'''
def __init__(self, func):
self.func = func
def __get__(self, obj, type=None):
return self.memoize_inst(obj, self.func)
class memoize_inst(object):
def __init__(self, inst, fget):
self.inst = inst
self.fget = fget
self.cache = {}
def __call__(self, *args):
# if cache hit, done
if args in self.cache:
return self.cache[args]
# otherwise populate cache and return
self.cache[args] = self.fget(self.inst, *args)
return self.cache[args]
More on descriptors:
http://docs.python.org/howto/descriptor.html#descriptor-example