Propagating class decorators to inherited classes - python

import inspect
import functools
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in inspect.getmembers(cls, inspect.isroutine):
if name.startswith('test'):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
def test_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__, args, kwargs)
res = func(*args, **kwargs)
return res
return wrapper
#for_all_test_methods(test_decorator)
class Potato(object):
def test_method(self):
print('in method')
class Spud(Potato):
def test_derived(self):
print('in derived')
Now if I create a spud instance the test_method which it has inherited remains decorated, but it has an undecorated method test_derived. Unfortunately, if I add the class decorator onto Spud aswell, then his test_method gets decorated twice!
How do I correctly propagate decorators from the parent class onto the children?

You cannot avoid decorating derived classes; you can find subclasses of a class after subclasses have been decorated, but not auto-decorate them. Use a metaclass instead of you need that sort of behaviour.
You can do one of two things:
Detect already-decorated methods; if there is a __wrapped__ attribute you have a wrapper:
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in inspect.getmembers(cls, inspect.isroutine):
if name.startswith('test') and not hasattr(value, '__wrapped__'):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
Limit the class decorator to direct methods only:
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in cls.__dict__.iteritems():
if name.startswith('test') and inspect.isroutine(value)):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate

Here is how you can accomplish this by using a metaclass instead of decorating the class:
import inspect
import functools
def test_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__, args, kwargs)
res = func(*args, **kwargs)
return res
return wrapper
def make_test_deco_type(decorator):
class TestDecoType(type):
def __new__(cls, clsname, bases, dct):
for name, value in dct.items():
if name.startswith('test') and inspect.isroutine(value):
dct[name] = decorator(value)
return super().__new__(cls, clsname, bases, dct)
return TestDecoType
class Potato(object, metaclass=make_test_deco_type(test_decorator)):
def test_method(self):
print('in method')
class Spud(Potato):
def test_derived(self):
print('in derived')
On Python 2.x you would use __metaclass__ = make_test_deco_type(test_decorator) as the first line of the class body instead of having the metaclass=... portion of the class statement. You would also need to replace super() with super(TestDecoType, cls).

Related

Using metaclass to keep track of instances in python

I need to keep tracks of instances of some classes (and do other stuff with those classes). I would like to not have to declare any extra code in the classes in question, thus everything should ideally be handled in the metaclass.
What I can't figure out is how to add a weak reference to each new instance of those classes. For example:
class Parallelizable(type):
def __new__(cls, name, bases, attr):
meta = super().__new__(cls, name, bases, attr)
# storing the instances in this WeakSet
meta._instances = weakref.WeakSet()
return meta
#property
def instances(cls):
return [x for x in cls._instances]
class Foo(metaclass=Parallelizable)
def __init__(self, name):
super().__init__()
self.name = name
# I would like to avoid having to do that - instead have the metaclass manage it somehow
self._instances.add(self)
Any ideas? I can't seem to find a hook on the metaclass side to get into the __init__ of Foo....
The method on the metaclass that is called when each new instance of its "afiliated" classes is __call__. If you put the code to record the instances in there, that is all the work you need:
from weakref import WeakSet
# A convenient class-level descriptor to retrieve the instances:
class Instances:
def __get__(self, instance, cls):
return [x for x in cls._instances]
class Parallelizable(type):
def __init__(cls, name, bases, attrs, **kw):
super().__init__(name, bases, attrs, **kw)
cls._instances = WeakSet()
cls.instances = Instances()
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
cls._instances.add(instance)
return instance
The same code will work without the descriptor at all - it is just a nice way to have a class attribute that would report the instances. But if the WeakSet is enough, this code suffices:
from weakref import WeakSet
class Parallelizable(type):
def __init__(cls, name, bases, attrs, **kw):
super().__init__(name, bases, attrs, **kw)
cls.instances = WeakSet()
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
cls.instances.add(instance)
return instance
You could decorate the attrs['__init__'] method in Parallizable.__new__:
import weakref
import functools
class Parallelizable(type):
def __new__(meta, name, bases, attrs):
attrs['__init__'] = Parallelizable.register(attrs['__init__'])
cls = super().__new__(meta, name, bases, attrs)
cls._instances = weakref.WeakSet()
return cls
#classmethod
def register(cls, method):
#functools.wraps(method)
def newmethod(self, *args, **kwargs):
method(self, *args, **kwargs)
self._instances.add(self)
return newmethod
#property
def instances(cls):
return [x for x in cls._instances]
class Foo(metaclass=Parallelizable):
def __init__(self, name):
"Foo.__init__ doc string"
super().__init__()
self.name = name
# Notice that Foo.__init__'s docstring is preserved even though the method has been decorated
help(Foo.__init__)
# Help on function __init__ in module __main__:
#
# __init__(self, name)
# Foo.__init__ doc string
stilton = Foo('Stilton')
gruyere = Foo('Gruyere')
print([inst.name for inst in Foo.instances])
# ['Gruyere', 'Stilton']
del stilton
print([inst.name for inst in Foo.instances])
# ['Gruyere']
How about this, its a class to inherit from, instead of a metaclass. I think its simpler but achieves the same point:
class AutoDiscovered:
instances = []
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls)
cls.instances.append(obj)
return obj
Usage:
class Foo(AutoDiscovered):
pass
a = Foo()
b = Foo()
print(Foo.instances) # [<__main__.Foo object at 0x7fdabd345430>, <__main__.Foo object at 0x7fdabd345370>]

setattr strange behaviour when there is class inheritance

I have an issue with setattr when using it in inheritance with classes and decorators.
#!/usr/bin/env python3
import inspect
def make_class_decorator(function_decorator):
def class_decorator(cls):
for attr_name in inspect.getmembers(cls, inspect.isroutine):
attr_name = attr_name[0]
if str(attr_name).startswith('__') and str(attr_name).endswith('__'): continue
attr_value = getattr(cls, str(attr_name))
setattr(cls, attr_name, function_decorator(cls, attr_value))
return cls
return class_decorator
#make_class_decorator
def auto_debug_logging(cls, called_function):
def wrapped(*args, **kwargs):
print('wrapped')
return called_function(*args, **kwargs)
wrapped.__name__ = called_function.__name__
return wrapped
def xclassmethod(called_function):
def wrapped(*args, **kwargs):
print('wrappedx: ' + called_function.__name__)
return builtins.classmethod(called_function(*args, **kwargs))
return wrapped
class X:
#xclassmethod
def get_i(cls):
cls._inst = cls()
return cls._inst
#auto_debug_logging
class A(X):
pass
#auto_debug_logging
class B(A):
def test(self):
print('test')
B.get_i().test()
Example code shows that all functions are wrapped, and prints "wrapped" before actual call. However, when I call B.get_i().test I'm getting following error:
AttributeError: 'A' object has no attribute 'test'
It seems that setattr somehow sets the original B class to A, as when I comment out that the issue disappears.

Does the default type.__call__ do more than call __new__ and __init__?

I'm writing a metaclass, and I want an additional method to be called between __new__ and __init__.
If I were calling the method before __new__ or after __init__ I could write e.g.
class Meta(type):
def __call__(cls):
ret = type.__call__()
ret.extraMethod()
My temptation is to write
class Meta(type):
def __call__(cls):
ret = cls.__new__(cls)
ret.extraMethod()
ret.__init__()
return ret
and just reproduce the functionality of type.__call__ myself. But I'm afraid there might be some subtlety to type.__call__ I have omitted, which will lead to unexpected behavior when my metaclass is implemented.
I cannot call extraMethod from __init__ or __new__ because I want users of my metaclass to be able to override __init__ and __new__ as in normal Python classes, but to still execute important set-up code in extraMethod.
Thanks!
If you really wish to do exactly what you said I can suggest you the following solution:
def call_after(callback, is_method=False):
def _decorator(func):
def _func(*args, **kwargs):
result = func(*args, **kwargs)
callback_args = (result, ) if is_method else ()
callback(*callback_args)
return result
return _func
return _decorator
class Meta(type):
def __new__(mcs, class_name, mro, attributes):
new_class = super().__new__(mcs, class_name, mro, attributes)
new_class.__new__ = call_after(
new_class.custom_method,
is_method=True
)(new_class.__new__)
return new_class
class Example(object, metaclass=Meta):
def __new__(cls, *args, **kwargs):
print('new')
return super().__new__(cls, *args, **kwargs)
def __init__(self):
print('init')
def custom_method(self):
print('custom_method')
if __name__ == '__main__':
Example()
This code will generate the following result:
new
custom_method
init

How to decorate a parent class and make child classes use it? python

What I want is to create a class decorator to decorate a class and works on subclasses too.
Imagine this class:
class CustomBaseTest(TransactionTestCase):
def __init__(self, *args, **kwargs):
...
def more_custom_helpers(self):
...
and the real Test:
class FooTest(CustomBaseTest):
def decorate_this_foo_is_ok(self):
....
def decorate_this_fails(self):
...
what I want is to use a decorator in CustomBaseTest that finds all methods that starts with 'decoratte_this_' and execute custom code after and before. I already have the decorator, something like this:
def class_decorator(klass):
is_method_test = lambda m: not m.startswith('_') and m.startswith('decorate_this_') and isinstance(getattr(klass, m), MethodType)
test_methods = filter(is_method_test, dir(klass))
for method_name in test_methods:
class_method = getattr(klass, method_name)
def helper(mname, method):
#wraps(method)
... some logic here
retval = method(*a, **kw)
... more logic here
return retval
return wrapper
fn = MethodType(helper(method_name, class_method), None, klass)
setattr(klass, method_name, fn)
return klass
do you know if is possible to do that? and how?
thanks!!!
Thanks to #Markku and #BrenBarn.
Here is the solution.
First we have a simple decorator:
from functools import wraps
def my_decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
# do some stuff
retval = func(*args, **kwargs)
# do more stuff
return retval
return wrapper
And the metaclass:
class ProfileMetaByClass(type):
def __init__(cls, name, bases, dct):
for method_name, method in dct.items():
if method_name.startswith('decorate_this_'):
setattr(cls, method_name, my_decorator(method))
type.__init__(cls, name, bases, dct)
And that worked for me!

Methods decorated with a decorator class do not have the "self" argument frozen

I have a decorator declared as a class:
class predicated(object):
def __init__(self, fn):
self.fn = fn
self.fpred = lambda *args, **kwargs: True
def predicate(self, predicate):
self.fpred = predicate
return self
def validate(self, *args, **kwargs):
return self.fpred(*args, **kwargs)
def __call__(self, *args, **kwargs):
if not self.validate(*args, **kwargs):
raise PredicateNotMatchedError("predicate was not matched")
return self.fn(*args, **kwargs)
... and when I use it to wrap a method in a class, calling that method does not seem to set the instance of the object as the first argument. While this behavior is not exactly unexpected, how would I go about getting self to be frozen when the method becomes an instance method?
Simplified example:
class test_decorator(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
class Foo(object):
#test_decorator
def some_method(self):
print(self)
Foo().some_method()
Expected instance of foo, instead get an error saying 0 arguments were passed.
Figured it out - needed to define a __get__ method in order to create a MethodType binding like so:
def __get__(self, obj, objtype=None):
return MethodType(self, obj, objtype)
which creates a MethodType object when invoking the method on an object that freezes the self argument.

Categories

Resources