class SimpleClass(object):
def A(self, *args):
...
def B(self, *args):
...
def C(self, *args):
...
Is there anyway to catch the arguments passed to A, B, C, modify those arguments and pass the modified arguments onto A, B, C without having to modify the existing code ?
Edit:
Answer: use a class decorator, that decorates all methods of the class with another decorator.
The method decorator performs the pre-processing.
Try this:
from types import FunctionType
class SimpleClass(object):
def A(self, *args):
pass
def B(self, *args):
pass
def C(self, *args):
pass
def methods(cls):
"""List of all method in class."""
return [x for x, y in cls.__dict__.items() if type(y) == FunctionType]
def args_decorator(method):
"""Decorator for SimpleClass methods"""
def wrapper(self, *args):
new_args = list(args)
# change new_args here
return method(self, *new_args)
return wrapper
for method in methods(SimpleClass):
setattr(SimpleClass, method, args_decorator(SimpleClass.__dict__[method]))
Though if you can change your SimpleClass I'd suggest using decorator like so:
def args_decorator(method):
def wrapper(self, *args):
new_args = list(args)
# change new_args here
return method(self, *new_args)
return wrapper
class SimpleClass(object):
#args_decorator
def A(self, *args):
pass
#args_decorator
def B(self, *args):
pass
#args_decorator
def C(self, *args):
pass
Related
I am trying to create a decorator that will inject some functionality to wrapped class __init__ method. This is what works.
class Decorator:
def __init__(self, arg):
print(arg)
self.arg = arg
def __call__(self, cls):
print(cls)
class Wrapped(cls):
def __init__(self, first_arg, second_arg, **kwargs):
cls.__init__(self, first_arg, second_arg, **kwargs)
print('in wrapped init', self.variable)
return Wrapped
#Decorator('random_string')
class TestClass:
def __init__(self, first_arg, second_arg, **kwargs):
self.variable = 10
print('TestClass init')
test = TestClass(first_arg='one', second_arg='two')
and produces
random_string
<class '__main__.TestClass'>
TestClass init
in wrapped init 10
for some mysterious reasons code is no longer working after removing decorator param (random string in this case)
#Decorator
class TestClass:
def __init__(self, first_arg, second_arg, **kwargs):
self.variable = 10
print('TestClass init')
Output:
Traceback (most recent call last):
File "/home/python_examples/test_decorators.py", line 24, in <module>
test = TestClass(first_arg='one', second_arg='two')
<class '__main__.TestClass'>
TypeError: __call__() got an unexpected keyword argument 'first_arg'
Two questions:
is this a well-known and valid approach to decorate classes?
why is the never used 'random_string' param crucial?
It's because your decorator class takes arg as a constructor argument. So when you remove your decorator param, make sure that you have removed that parameter from the __init__ method too. Changing this
class Decorator:
def __init__(self, arg):
print(arg)
self.arg = arg
def __call__(self, cls):
print(cls)
class Wrapped(cls):
def __init__(self, first_arg, second_arg, **kwargs):
cls.__init__(self, first_arg, second_arg, **kwargs)
print('in wrapped init', self.variable)
return Wrapped
to this
class Decorator:
def __init__(self):
pass
def __call__(self, cls):
print(cls)
class Wrapped(cls):
def __init__(self, first_arg, second_arg, **kwargs):
cls.__init__(self, first_arg, second_arg, **kwargs)
print('in wrapped init', self.variable)
return Wrapped
will solve your problem.
Actually, this is how it works:
# Assume you have a decorator class and a class named A which is needed to decorate.
#Decorator
class A:
...
# is the same as
class A:
...
A = Decorator()(A)
That's why you need to define __cal__.
And here you can see that if your Decorator accepts some parameters to initialize, you need to use something like A = Decorator(xxx)(A). And the equivalent in decorator syntax is:
#Decorator(xxx)
class A:
...
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
The problem:
I want to get an attribute of class via decorator which is a class but I can not.
The question is how can?
class DecoratorClass:
def __call__(self, fn, *args, **kwargs) -> Callable:
try:
# do something with the TestClass value
return fn
finally:
pass
class TestClass:
def __init__(self):
self.value = 1
#DecoratorClass()
def bar(self):
return 1
How can I reach the the TestClass's value attr via DecoratorClass?
I got the solution :)
class Decoratorclass:
def __call__(self, fn, *args, **kwargs) -> Callable:
def decorated(instance):
try:
# do something with the TestClass value
print(instance.value)
return fn(instance)
finally:
pass
return decorated
class TestClass:
def __init__(self):
self.value = 1
#Decoratorclass()
def bar(self):
return 1
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
What is a simple example of decorating a class by defining the decorator as a class?
I'm trying to achieve what has been implemented in Python 2.6 using PEP 3129 except using classes not functions as Bruce Eckel explains here.
The following works:
class Decorator(object):
def __init__(self, arg):
self.arg = arg
def __call__(self, cls):
def wrappedClass(*args):
return cls(*args)
return type("TestClass", (cls,), dict(newMethod=self.newMethod, classattr=self.arg))
def newMethod(self, value):
return value * 2
#Decorator("decorated class")
class TestClass(object):
def __init__(self):
self.name = "TestClass"
print "init %s"%self.name
def TestMethodInTestClass(self):
print "test method in test class"
def newMethod(self, value):
return value * 3
Except, in the above, wrappedClass is not a class, but a function manipulated to return a class type. I would like to write the same callable as follows:
def __call__(self, cls):
class wrappedClass(cls):
def __init__(self):
... some code here ...
return wrappedClass
How would this be done?
I'm not entirely sure what goes into """... some code here ..."""
If you want to overwrite new_method(), just do it:
class Decorator(object):
def __init__(self, arg):
self.arg = arg
def __call__(self, cls):
class Wrapped(cls):
classattr = self.arg
def new_method(self, value):
return value * 2
return Wrapped
#Decorator("decorated class")
class TestClass(object):
def new_method(self, value):
return value * 3
If you don't want to alter __init__(), you don't need to overwrite it.
After this, the class NormalClass becomes a ClassWrapper instance:
def decorator(decor_arg):
class ClassWrapper:
def __init__(self, cls):
self.other_class = cls
def __call__(self,*cls_ars):
other = self.other_class(*cls_ars)
other.field += decor_arg
return other
return ClassWrapper
#decorator(" is now decorated.")
class NormalClass:
def __init__(self, name):
self.field = name
def __repr__(self):
return str(self.field)
Test:
if __name__ == "__main__":
A = NormalClass('A');
B = NormalClass('B');
print A
print B
print NormalClass.__class__
Output:
A is now decorated. <br>
B is now decorated. <br>
\__main__.classWrapper