Chaining decorators that are defined as classes? - python

For the sake of learning, I'm trying to chain decorators that are defined by classes. I read this question about decorators, which has a lot of good information about chaining them with functions. They also link to the documentation, but I'm trying to figure out simpler examples.
Basically, I'm trying to mimic similar behaviour using classes. Here is my first decorator definition, which works perfectly.
class square_result(object):
def __init__(self, f):
pass
def __call__(self, x, y):
return (x+y)**2
#square_result
def add_two_numbers(x, y):
return x + y
print(add_two_numbers(2,5)) #Outputs 49, as expected
Then, I add another decorator to create this code snippet:
class square_result(object):
def __init__(self, f):
pass
def __call__(self, x, y):
return (x+y)**2
class append_abc(object):
def __init__(self, f):
pass
def __call__(self, *args):
return str(*args) + "abc"
#append_abc
#square_result
def add_two_numbers(x, y):
return x + y
print(add_two_numbers(2,5))
#Ideally, this should print "49abc" but prints "(2,5)abc" instead
what is the proper way of doing this? I guess what I want to do is create a decorator in the form of a class that takes the output of the function it decorates (in this case square_result) and appends "abc" to it.
I know that when I add a decorator to my code, the add_two_numbers() function is compiled and that function object is passed to the square_result class, which does something to create a function-like object which is substituted for the original add_two_numbers(). However, I'm not sure how to chain this.

This does what you want:
class square_result(object):
def __init__(self, f):
pass
def __call__(self, x, y):
return (x+y)**2
class append_abc(object):
def __init__(self, f):
self.f = f
def __call__(self, *args):
return str(self.f(*args)) + "abc"
#append_abc
#square_result
def add_two_numbers(x, y):
return x + y
print(add_two_numbers(2,5))
You need to actually run the inner function in the decorator if you want to use its output in the result of the decorator.
I didn't edit your first decorator, as it does what you want, but it actually isn't useful as a decorator. Since its output is not related in any way to the function it's decorating, it's just replacing the function. If you wanted to replace the function with that class, the way to do it would be
class square_result(object):
def __call__(self, x, y):
return (x+y)**2
# this has no effect at all after the reassignment
def add_two_numbers(x, y):
return x + y
add_two_numbers = square_result()
PEP8 also suggests CamelCase for your class names (SquareResult).

square_result doesn't "decorate" the add_two_numbers result, but overrides it (doing the addition as well as the squaring). It should instead treat the decorated function the same way that append_abc does, by storing it and then making use of the decorated function in its own implementation. Thus:
class square_result(object):
def __init__(self, f):
self.function = f
def __call__(self, *args):
return self.function(*args)**2
class append_abc(object):
def __init__(self, f):
self.function = f
def __call__(self, *args):
return str(self.function(*args)) + "abc"
#append_abc
#square_result
def add_two_numbers(x, y):
return x + y
print(add_two_numbers(2,5))

Related

Correct way of returning new class object (which could also be extended)

I am trying to find a good way for returning a (new) class object in class method that can be extended as well.
I have a class (classA) which has among other methods, a method that returns a new classA object after some processing
class classA:
def __init__(): ...
def methodX(self, **kwargs):
process data
return classA(new params)
Now, I am extending this class to another classB. I need methodX to do the same, but return classB this time, instead of classA
class classB(classA):
def __init__(self, params):
super().__init__(params)
self.newParams = XYZ
def methodX(self, **kwargs):
???
This may be something trivial but I simply cannot figure it out. In the end I dont want to rewrite the methodX each time the class gets extended.
Thank you for your time.
Use the __class__ attribute like this:
class A:
def __init__(self, **kwargs):
self.kwargs = kwargs
def methodX(self, **kwargs):
#do stuff with kwargs
return self.__class__(**kwargs)
def __repr__(self):
return f'{self.__class__}({self.kwargs})'
class B(A):
pass
a = A(foo='bar')
ax = a.methodX(gee='whiz')
b = B(yee='haw')
bx = b.methodX(cool='beans')
print(a)
print(ax)
print(b)
print(bx)
class classA:
def __init__(self, x):
self.x = x
def createNew(self, y):
t = type(self)
return t(y)
class classB(classA):
def __init__(self, params):
super().__init__(params)
a = classA(1)
newA = a.createNew(2)
b = classB(1)
newB = b.createNew(2)
print(type(newB))
# <class '__main__.classB'>
I want to propose what I think is the cleanest approach, albeit similar to existing answers. The problem feels like a good fit for a class method:
class A:
#classmethod
def method_x(cls, **kwargs):
return cls(<init params>)
Using the #classmethod decorator ensures that the first input (traditionally named cls) will refer to the Class to which the method belongs, rather than the instance.
(usually we call the first method input self and this refers to the instance to which the method belongs)
Because cls refers to A, rather than an instance of A, we can call cls() as we would call A().
However, in a class that inherits from A, cls will instead refer to the child class, as required:
class A:
def __init__(self, x):
self.x = x
#classmethod
def make_new(cls, **kwargs):
y = kwargs["y"]
return cls(y) # returns A(y) here
class B(A):
def __init__(self, x):
super().__init__(x)
self.z = 3 * x
inst = B(1).make_new(y=7)
print(inst.x, inst.z)
And now you can expect that print statement to produce 7 21.
That inst.z exists should confirm for you that the make_new call (which was only defined on A and inherited unaltered by B) has indeed made an instance of B.
However, there's something I must point out. Inheriting the unaltered make_new method only works because the __init__ method on B has the same call signature as the method on A. If this weren't the case then the call to cls might have had to be altered.
This can be circumvented by allowing **kwargs on the __init__ method and passing generic **kwargs into cls() in the parent class:
class A:
def __init__(self, **kwargs):
self.x = kwargs["x"]
#classmethod
def make_new(cls, **kwargs):
return cls(**kwargs)
class B(A):
def __init__(self, x, w):
super().__init__(x=x)
self.w = w
inst = B(1,2).make_new(x="spam", w="spam")
print(inst.x, inst.w)
Here we were able to give B a different (more restrictive!) signature.
This illustrates a general principle, which is that parent classes will typically be more abstract/less specific than their children.
It follows that, if you want two classes that substantially share behaviour but which do quite specific different things, it will be better to create three classes: one rather abstract one that defines the behaviour-in-common, and two children that give you the specific behaviours you want.

How to use repr() for functions stored in variables to retrieve their source code?

I want to be able to repr() on a class in python which has functions stored in the class's variables so I can reproduce the same functions later when I eval() said output.
Here's an example to illustrate my problem:
class Example:
def __init__(self, func):
self.func = func
def __repr__(self):
return repr(self.func)
def add(x, y):
return x + y
example = Example(add)
print(repr(example))
When I run that code, the output I get is:
<function add at 0x7f6ea0e96e18>
Is there any way of making a repr() which would output something that can then be eval()ed to a callable function. Ideally, I'd like an output like:
def add(x, y): return x + y
You can do it with Python's inspect module:
import inspect
class Example:
def __init__(self, func):
self.func = func
def __repr__(self):
return inspect.getsource(self.func)
def add(x, y):
return x + y
example = Example(add)
print(repr(example))
Output:
def add(x, y):
return x + y

Implementing a generic and dynamic facade for python classes

I wanted to implement a sort of facade pattern in python. However because I need to do the same for all methods, I'd like to do it in a generic way. Let me use an example:
class MyObject:
def __init__(self, *args, **kwargs):
# do something with args/kwargs
def method1(self, x):
# do something
def method2(self, x, a):
# do something
def method3(self, x, a, b):
# do something
class MyFacade:
def __init__(self, *args, **kwargs):
self.x = SOMETHING
self.obj = MyObject(*args, **kwargs)
def method1(self):
return self.obj.method1(self.x)
def method2(self, a):
return self.obj.method2(self.x, a)
def method3(self, a, b):
return self.obj.method3(self.x, a, b)
Now because I have several classes like MyObject, I'd like a generic way of creating a MyFacade for each of them without having to write code for each method (they all do more or less the same). Also if MyObject changes, I'd like MyFacade not being impacted and rather handle any interface change in MyObject transparently.
Thanks for the help!
EDIT:
This works but methods inherited from MyInterface raise TypeError because of the extra argument.
class MyObject:
def method1(self, x):
print(x)
def method2(self, x, a):
print(x, a)
def method3(self, x, a, b):
print(x, a, b)
class MyInterface:
def methodX(self):
print("YAY!")
class MyFacade(MyInterface, MyObject):
def __init__(self):
self.x= "WHATEVER"
def __getattribute__(self, item):
result = super().__getattribute__(item)
if callable(result):
return lambda *args, **kwargs: result(self.x, *args, **kwargs)
return result
EDIT:
I modified condition this way and now problem with MyInterface is gone:
if callable(result) and result.__name__ in MyObject.__dict__:
The obvious way of doing this is to use the fact that class and function names are variables and can be assigned so MyFacade could be defined as follows:
class MyFacade:
def __init__(self,obj, *args, **kwargs):
self.x = SOMETHING
self.obj = obj(*args, **kwargs)
def method1():
return self.obj.method1(self.x)
def method2(a):
return self.obj.method2(self.x, a)
def method3(a, b):
return self.obj.method1(self.x, a, b)
and the set-up call would be eg:
fasc = MyFscade(MyOject,*args,**kwargs)

Getting rid of explicit super

I'd like to implement something like this
def after(f):
def inner(*args, **kwargs):
super().f(*args, **kwargs)
f(*args, **kwargs)
return inner
class A:
def f(self):
print ('hello')
class B(A):
#after
def f(self):
print ('world')
b = B()
b.f()
that is I would like to get rid of explicit super in some of my classes and replace it with #before / #after decorator (maybe with parameters).
That is, in this example, I would like hello world to be printed.
the idea is to increase the readability of the code, as in some classes I often use multiple inheritance, so I often override methods and often have to use super().
I think I could use inspect to determine the class instance that calls the decorator (although not sure about performance if I have many class instances).
is there a way to do this without sacrificing performance?
You can make your decorator work, you just need it to make it a descriptor class, rather than a function. You need to implement the __set_name__ method to get a reference to the class you've been added to. With the class reference, you can make a two-argument super call:
import functools
class after:
def __init__(self, method):
self.method = method
def __set_name__(self, owner, name):
self.owner = owner
self.name = name # using self.method.__name__ might be better?
def __get__(self, instance, owner):
if instance is None:
return self
return functools.partial(self, instance)
def __call__(self, instance, *args, **kwargs):
assert(self.owner is not None and self.name is not None)
getattr(super(self.owner, instance), self.name)(*args, **kwargs)
return self.method(instance, *args, **kwargs)
You could do a before too, which would be nearly the same, just with the last two lines in the reverse order (and some fiddling to handle the return value).
I'd note that this decorator is quite a bit less generally useful than calling super the normal way since you can't usefully interact with the value returned by the overridden method, or change the arguments being passed in to it. There's no before or after decorated method that can replicate these classes:
class Foo:
def foo(self, x, y):
return x + y
class Bar(Foo):
def foo(self, x, y, z):
return super().foo(x//2, y+1) * z

Working around decorator and getattr

I have problem solving this question, I have the following class:
class test:
#auth
def method1(self, x):
return x
#auth
def method2(self, x, y):
return x+y
def method3(self, z):
return z
I applied the decorator in both methods, follow:
class auth:
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
self.f(*args, **kwargs)
So far no problem, however I need (NEED) to use the following code:
def run():
klass = globals()["test"]()
method1 = getattr(klass, "method1")
print(method1.__code__.co_varnames)
# should print (self, x)
method2 = getattr(klass, "method2")
print(method2.__code__.co_varnames)
# should print (self, x, y)
method3 = getattr(klass, "method3")
print(method3.__code__.co_varnames)
# i get (self, z) < without decorator
But I get now:
AttributeError: 'auth' object has no attribute '__code__'
What makes sense if we think that the signature of method "method1 and method2" is now "auth".
So how do I get the arguments with or without decorators.
I started reading about the "inspect" but there are many reports about being slow.
The "original" method is stored in the f attribute of the auth object. Instead of method1.__code__.co_varnames use method1.f.__code__.co_varnames
Annotations just contain an object and are not the object itsself, it is an object of class auth and not function. To access the function itsself, you can write methodN.f.__code__.co_varnames or assign a copy of the __dict__ object of the function to the auth-object in __init__ itsself.
class auth:
def __init__(self, f):
self.__dict__.update(f.__dict__)
# now the initialisations
self.f = f
def __call__(self, *args, **kwargs):
self.f(*args, **kwargs)
Edit:
You should initialize the members/call super after updating the dict, because f could be overriden by the update, eg. you define another decorator-class and it has also a member f.

Categories

Resources