How to access class variable in decorator function - python

Is it possible to create a decorator function without argument that is able to access class variables. I found similar questions but they always refer to instance variables and not class variables and the variables are generally only accessed when the decorated method is called.
I want to reference the class variable at class definition not after instantiation.
Other solutions such as creating a Meta class come to mind, but I only want to use one class and a decorator without argument.
I am able to implement the required functionality without the use of decorators the following way with the desired results.
class B:
b=3
resetFuns=[]
def __init__(self,x):
self.x=x
self.y=x+self.b
def foo(self):
self.y=self.x+self.b
resetFuns.append(foo)
def reset(self):
for f in self.resetFuns:
f(self)
test=B(4)
print(test.y)
B.b=9
print(test.y)
test.reset()
print(test.y)
7
7
13
But I want to use a decorator similar to this.
class A:
b=3
resetFuns=[] ##class variable I want to access
def __init__(self,x):
self.x=x
self.y=x+self.b
def resetDecorator(func):
resetFuns.append(func) ##can't reference resetFuns like this
return func
#resetDecorator
def foo(self):
self.y=self.x+self.b
def reset(self):
for f in resetFuns:
f(self)

Something like:
def resetDecoratorCreator(resetFuns):
def resetDecorator(func):
resetFuns.append(func)
return func
return resetDecorator
class B:
b=3
resetFuns=[]
resetDecorator = resetDecoratorCreator(resetFuns)
def __init__(self,x):
self.x=x
self.y=x+self.b
#resetDecorator
def foo(self):
self.y=self.x+self.b
def reset(self):
for f in self.resetFuns:
f(self)
will get you what you're looking for.

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.

decorating methods causes method to pass in objects [duplicate]

This question already has answers here:
Python: Bind an Unbound Method?
(5 answers)
Closed 2 years ago.
Problem Description
I want to use a decorator to define a class method, but this requires me to manually give the 'self' object when I shouldn't have to provide that.
def func_wrapper(func):
def call_func(self):
print(self.a)
func()
return call_func
def func():
print('hello')
class test:
def __init__(self, func):
self.a = 0
self.call_func = func_wrapper(func)
mytest = test(func)
#mytest.call_func() #why does this not work?
mytest.call_func(mytest) #this works
I want to be able to mytest.call_func() but this doesn't work, presumably because call_func is bound to the func_wrapper and not mytest. If I manually pass in the object, e.g. mytest.call_func(mytest) this will work, but I don't want to have to manually pass in the object - this creates inconsistent call signatures if one inherited the test class and wrote their own call_func method, because then the method would be properly bound to the class.
Solution Attempts
def func_wrapper2(func, obj):
def call_func():
print(obj.a)
func()
return call_func
class test:
def __init__(self, func):
self.a = 0
self.call_func = func_wrapper2(func, self)
Here is a solution which lets me test.call_func() as desired, but here func_wrapper is not a true decorator as it requires to be passed in the object as well.
Looking on the web I found this blog https://medium.com/#vadimpushtaev/decorator-inside-python-class-1e74d23107f6 which talks about this issue and recommends to define the decorator either in a nested class, or a helper class. However their solution doesn't seem to work and I am getting type errors from passing the wrong number of inputs.
class test2:
class test2helper:
#classmethod
def func_wrapper(func):
print(self.a)
func()
def __init__(self):
self.a = 0
#test2helper.func_wrapper
def call_func(self):
print('hello')
So what is the proper way to use decorators with class methods? Every way to do it seems to cause different issues with how the self is being handled. I am going to use the func_wrapper2 design unless there is a better way to do this.
You are missing one level:
class test2:
class test2helper:
#classmethod
def decorator(cls, func): # this must return a function!
def func_wrapper(self): # ... namely this one, the "wrapper"
print(self.a) # ... where you have access to the instance
func(self) # ... upon which the method is called
return func_wrapper
def __init__(self):
self.a = 0
#test2helper.decorator
def call_func(self):
print('hello')
>>> t = test2()
>>> t.call_func()
0
hello
Or, if you want to go with the earlier attempt without nested class:
def decorator(func): # you are decorating an unbound function!
def func_wrapper(obj):
print(obj.a)
func(obj) # which has to be passed all the arguments
return func_wrapper
class test:
def __init__(self):
self.a = 0
#decorator
def call_func(self):
print('hello')
You can define a class decorator to do what you want:
def class_decorator(cls):
def call_func(self):
print(self.a)
return func()
setattr(cls, 'call_func', call_func)
return cls
def func():
print('hello')
#class_decorator
class Test:
def __init__(self, func):
self.a = 0
mytest = Test(func)
mytest.call_func() # This now works.
Output:
0
hello

Python base class' implicit super() call

Currently I am starting to revise my python's OOP knowledge. I stumbled upon super() definition, which suggests, that it provides a derived class with a set of instance variables and methods from a base class.
So I have this piece of code:
class foo:
bar = 5
def __init__(self, a):
self.x = a
def spam(self):
print(self.x)
class baz(foo):
pass
b = baz(5)
b.spam()
And this executed with no super() calls, no errors, and printed out 5.
Now when I add an __init__ method to the derived class, like this:
class foo:
bar = 5
def __init__(self, a):
self.x = a
def spam(self):
print(self.x)
class baz(foo):
def __init__(self, a):
self.b = a
b = baz(5)
b.spam()
the script gives me an error: AttributeError: 'baz' object has no attribute 'x'.
So this would suggest, that if my class has a default __init__, it also has an explicit super() call. I couldn't actually find any info confirming this, so I just wanted to ask if I am correct.
The problem is that when you define the method __init__ in your subclass baz, you are no longer using the one in the parent class foo. Then, when you call b.spam(), x does not exist because that is define in the __init__ method of the parent class.
You can use the following to fix this if what you want is to call the __init__ method of the parent class and also add your own logic:
class baz(foo):
def __init__(self, a):
super().__init__(10) # you can pass any value you want to assign to x
self.b = a
>>> b = baz(5)
>>> b.spam()
10

Access to the methods of the class from which it was instantiated another class

I'm trying to access the methods of the class from which it was instantiated another class, I mean, accessing to the "parent" instance without creating a new instance of it.
class A():
def __init__(self):
...
b_instance = B()
...
class B():
def __init__(self):
...
def function1(self):
...
def function2(self):
C().run() # I need to use class C functionalities
...
class C():
def __init__(self):
...
def run(self):
classB.function1() #I can't access to these methods without instantiating again class B
# I have to execute:
>>> a = A()
>>> a.b_instance.function2()
Sorry if I have not explained well, is a bit confusing. If you need any clarification do not hesitate to ask.
EDIT.
In class C a specific handling of the execution of class B methods is done. Is not possible to instanciate again inside C because class B contains the initialization of hardware.
It's still not clear what exactly you're trying to achieve, but here's one fix:
class A():
def __init__(self):
...
b_instance = B()
...
class B():
def __init__(self):
...
def function1(self):
...
def function2(self):
C().run(self) # pass class B instance to C instance run method
...
class C():
def __init__(self):
...
def run(self, classB): # note additional parameter
classB.function1()
However, note that this represents a very high level of coupling between your various classes, which seems suspicious to me and may indicate a deeper flaw in your design.
This can access the class methods from other classes.
use instance method, class methods and static methods, if you are using various types of functins.
class A():
def __init__(self):
print 'in __init__'
self.b_instance = B() # making an instance of class
#self.b_instance.function2()
class B():
def __init__(self):
print 'in __init__, B'
#staticmethod
def function1():
print 'func1'
def function2(self):
C().run() # I need to use class C functionalities
# if you trying to access `run` method of `class C` make
# it instance bound method"""
class C():
def __init__(self):
pass
def run(self):
print 'in run'
B.function1() #I can't access to these methods without instantiating again class B
#you are passing class instance as `B` while calling function1
# so make it either classmethod `#classmethod` or `static method`
# I have to execute:
a = A()
a.b_instance.function2() # calling b_instance variable of class A

Decorators on abstract methods

In python, is there a way to make a decorator on an abstract method carry through to the derived implementation(s)?
For example, in
import abc
class Foo(object):
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
#some_decorator
def my_method(self, x):
pass
class SubFoo(Foo):
def my_method(self, x):
print x
SubFoo's my_method won't get decorated with some_decorator as far as I can tell. Is there some way I can make this happen without having to individually decorate each derived class of Foo?
I would code it as two different methods just like in standard method factory pattern description.
https://www.oodesign.com/factory-method-pattern.html
class Foo(object):
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
#some_decorator
def my_method(self, x):
self.child_method()
class SubFoo(Foo):
def child_method(self, x):
print x
This is, of course, possible. There is very little that can't be done in Python haha! I'll leave whether it's a good idea up to you...
class MyClass:
def myfunc():
raise NotImplemented()
def __getattribute__(self, name):
if name == "myfunc":
func = getattr(type(self), "myfunc")
return mydecorator(func)
return object.__getattribute__(self, name)
(Not tested for syntax yet, but should give you the idea)
As far as I know, this is not possible and not a good strategy in Python. Here's more explanation.
According to the abc documentation:
When abstractmethod() is applied in combination with other method descriptors, it should be applied as the innermost decorator, as shown in the following usage examples: ...
In other words, we could write your class like this (Python 3 style):
from abc import ABCMeta, abstractmethod
class AbstractClass(metclass=ABCMeta):
#property
#abstactmethod
def info(self):
pass
But then what? If you derive from AbstractClass and try to override the info property without specifying the #property decorator, that would create a great deal of confusion. Remember that properties (and it's only an example) usually use the same name for their class method, for concision's sake:
class Concrete(AbstractMethod):
#property
def info(self):
return
#info.setter
def info(self, new_info):
new_info
In this context, if you didn't repeat the #property and #info.setter decorators, that would create confusion. In Python terms, that won't work either, properties being placed on the class itself, not on the instance. In other words, I guess it could be done, but in the end, it would create confusing code that's not nearly as easy to read as repeating a few decorator lines, in my opinion.
My solution would be extending the superclass' method without overriding it.
import abc
class Foo(object):
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
#some_decorator
def my_method(self, x):
pass
class SubFoo(Foo):
def my_method(self, x):
super().my_method(x) #delegating the call to the superclass
print x
Jinksy's answer did not work for me, but with a small modification it did (I use different names but the idea should be clear):
def my_decorator(func):
def wrapped(self, x, y):
print('start')
result = func(self, x, y)
print('end')
return result
return wrapped
class A(ABC):
#abstractmethod
def f(self, x, y):
pass
#my_decorator
def f_decorated(self, x, y):
return self.f(x, y)
class B(A):
def f(self, x, y):
return x + y
B().f_decorated(1, 3)
[Out:]
start
end
4
Notice that the important difference between this and what Jinksy wrote is that the abstract method is f, and when calling B().f_decorated it is the inherited, non-abstract method that gets called.
As I understand it, f_decorated can be properly defined because the abstractmethod decorator is not interfering with the decorator my_decorator.

Categories

Resources