How to inherit a method attribute [python] - python

I am trying to pass some attributes from a method in a parent class to a method in a child class. And after that I wish to use a decorator that uses that attribute.
Suppose I have two classes and a decorator like these:
def my_decorator(fun):
inner(*args, **kwargs):
if fun.some_attr == 'value':
out = fun(*args, **kwargs)
else:
out = None
return out
return inner
class A:
def some_method_in_A(self):
return 1
some_method_in_A.some_attr = 'value'
class B(A):
#my_decorator
def some_method_in_B(self):
super(B, self).some_method_in_A()
What I want is to have some_attr set to 'value' in some_method_in_B. This attribute has to be available for my_decorator.
Is it possible to do it?
This is something that I would have to do for lots of methods in class B so it would be nice to do it in a compact way.

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.

I want to call parent class method which is overridden in child class through child class object in Python

class abc():
def xyz(self):
print("Class abc")
class foo(abc):
def xyz(self):
print("class foo")
x = foo()
I want to call xyz() of the parent class, something like;
x.super().xyz()
With single inheritance like this it's easiest in my opinion to call the method through the class, and pass self explicitly:
abc.xyz(x)
Using super to be more generic this would become (though I cannot think of a good use case):
super(type(x), x).xyz()
Which returns a super object that can be thought of as the parent class but with the child as self.
If you want something exactly like your syntax, just provide a super method for your class (your abc class, so everyone inheriting will have it):
def super(self):
return super(type(self), self)
and now x.super().xyz() will work. It will break though if you make a class inheriting from foo, since you will only be able to go one level up (i.e. back to foo).
There is no "through the object" way I know of to access hidden methods.
Just for kicks, here is a more robust version allowing chaining super calls using a dedicated class keeping tracks of super calls:
class Super:
def __init__(self, obj, counter=0):
self.obj = obj
self.counter = counter
def super(self):
return Super(self.obj, self.counter+1)
def __getattr__(self, att):
return getattr(super(type(self.obj).mro()[self.counter], self.obj), att)
class abc():
def xyz(self):
print("Class abc", type(self))
def super(self):
return Super(self)
class foo(abc):
def xyz(self):
print("class foo")
class buzz(foo):
def xyz(self):
print("class buzz")
buzz().super().xyz()
buzz().super().super().xyz()
results in
class foo
Class abc

What does classmethod do except changing self to cls?

There is an answered question about classmethod and property combined together: Using property() on classmethods
I still don't understand the cause of the problem, please help.
My understanding of classmethod was that it simply replaces self with cls. With this in mind I wrote several classmethods during the past few years and now I see I was wrong all that time.
So what is the difference between #classmethod and #cm from the code below?
def cm(func):
def decorated(self, *args, **kwargs):
return func(self.__class__, *args, **kwargs)
return decorated
class C:
V = 0
#property
#classmethod
def inc1(cls):
cls.V += 1
print("V1 =", cls.V)
#property
#cm
def inc3(cls):
cls.V += 3
print("V3 =", cls.V)
c = C()
#c.inc1 # fails with: TypeError: 'classmethod' object is not callable
c.inc3 # works
inc3 with cm works, but inc1 with classmethod does not.
what is the difference between #classmethod and #cm from the code below?
decorator is calling during class creation time before an instance is created.
In your case, since #cm returns func(self.__class__, *args, **kwargs), which is relied on self, it should be used as a instance method.
On the other hand, #classmethod is able to use before an instance is created.
def cm(func):
def decorated(self, *args, **kwargs):
return func(self.__class__, *args, **kwargs)
return decorated
class C:
#classmethod
def inc1(cls):
(blablabla)
#cm
def inc3(cls):
(blablabla)
C().inc1() # works as a instance method
C.inc1() # works as a classmethod
C().inc3() # works as a instance method
C.inc3() # TypeError: unbound method decorated() must be called with C instance as first argument (got nothing instead)
For a combination of classmethod and property, it could be done by return an customized object. Reference
class ClassPropertyDescriptor(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.f.__get__(obj, klass)()
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
class C:
#classproperty
def inc1(cls):
(blablabla)
C.inc1 # works as a classmethod property
[Edit]
Q. What does the classmethod() call do with the method it decorates to achieve that?
The implementation can be done by using descriptor
class ClassMethodDescriptor(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc
def myclassmethod(func):
return ClassMethodDescriptor(func)
class C:
#myclassmethod
def inc1(cls):
(blablabla)
C.inc1() # works as a classmethod
Q. Why is the result not callable?
Because the implementation of ClassMethodDescriptor does not define __call__ function. Once using #property, it will return ClassMethodDescriptor which is not callable.
The difference is that classmethod is not callable, and cm method is callable. This means that when the property(class) makes a call to the inputed func(which it is supposed to do), it works as you'll except for cm, but will not work for classmethod since classmethod does not have a call implemented.
class method does not know anything about instance and does not require it.
instance method knows about it's instance and it's class.
class Foo:
some = 'some'
class Bar(Foo):
def __init__(self):
self.some = 'not some'
#classmethod
def cls_some(cls):
print(cls.some)
def instance_some(self):
print(self.some)
Bar.cls_some()
>>>some
Bar().instance_some()
>>>not some
Also as you can see you don't need an instance to call classmethod.

Python inheritance, method overloading, and decorating

I have a class B that inherits from A :
class A():
def do_something(self, x):
"""Prints x."""
print(x)
class B(A):
def something_else(self, x):
print("This isn't the same.")
I'd like to achieve a few things :
I'd like for B.do_something to inherit the docstring from A.do_something. I think functools.wraps is the recommended solution : is that right ?
Let's say there are some methods of A that return an instance of A. If I call those methods from B, I'd like them to return an instance of B. So far, I'm overloading each function manually.
def method_of_A(self, *args, **kwargs):
return A(super(self.__class__, self).method_of_A(*args, **kwargs))
There's likely a better way - especially given that I have to do this for a large number of classes. Is there same way to check if a function is defined within B and, if not but available in A, have it decorated / wrapped to return an instance of B ? EDIT : I can't make changes to A's codebase.
Are there solutions that are Py2 and Py3 compatible ?
Thanks very much for any suggestions.
Yes, you can use functools.wraps to copy the function name and docstring. You can return an instance of the current class using self.__class__
class A(object):
def func(self):
return self.__class__()
class B(A):
#functools.wraps(A.func)
def func(self):
return super(B, self).func()
>>> b = B()
>>> obj = b.return_object()
>>> print type(obj)
"<class '__main__.B'>"
Is there same way to check if a function is defined within B and, if not but available in A, have it decorated / wrapped to return an instance of B?
You may be able to do this using metaclasses, assuming A isn't already using a custom metaclass that you're not able to inherit from (like if it is only defined in C and hasn't been exposed to python). The way you use metaclasses is slightly different in python 2 and 3.
class MetaB(type):
def __new__(cls, name, bases, attrs):
if A in bases:
for attr, value in A.__dict__.items():
if isinstance(value, types.FunctionType) and attr not in attrs:
new_func = MyMeta.make_wrapper_func(value)
attrs[attr] = new_func
return super(MetaB, cls).__new__(cls, name, bases, attrs)
#staticmethod
def make_wrapper_func(func):
#functools.wraps(func)
def _func(self, *args, **kwargs):
value = func(self, *args, **kwargs)
if isinstance(value, A):
value = self.__class__(value)
return value
return _func
class B(A):
__metaclass__ = MetaB
...
In python 3, metaclasses are used a little differently
class B(A, metaclass=MetaB):
...
This assumes you can create an object of the B() type just by passing an instance of A() to the constructor for it (ie. return self.__class__(value)). That was just a guess. I'd have to know a litte more about your object to know how to translate an A object to a B object, but the general method would be the same. This solution also only works on regular class methods. It's not going to work on some other stuff like classmethods and staticmethods or other types of descriptor objects. You certainly could make it work for all those, your metaclass would just need to be a little more complex.
Let's say there are some methods of A that return an instance of A. If I call those methods from B, I'd like them to return an instance of B. So far, I'm overloading each function manually.
Use a classmethod.
class A(object):
#classmethod
def f(cls):
return cls
when b (instance of B) will call f, it will return B.

Using decorators as class attributes instead of instance attributes

I have the following classes.
Validator is a decorator that receives a class which defines validation criteria for a decorated function. ValidateKeys is the validation criteria for this example. Node2D is a class using validation criteria.
class Validator(object):
def __init__(self, TheValidator, *args, **kwargs):
self.validator = TheValidator(*args,**kwargs)
def __call__(self,f):
def wrapped_f(instance, *args,**kwargs):
self.TheValidator(instance, *args, **kwargs)
return f(instance,*args,**kwargs)
return wrapped_f
class ValidateKeys(object):
def __init__(self,*keysIterable):
self.validkeys = keysIterable
def __call__(self, instance, **kwargs):
for a in kwargs:
if not a in self.validkeys:
raise Exception()
instance.__dict__.update(kwargs)
class Node2D(object):
#property
def coords(self):
return self.__dict__
#coords.setter
def coords(self,Coords):
self.set_coords(**Coords)
#Validator(ValidateKeys, 'x','y')
def set_coords(self,**Coords):
pass
From what I understand, as things are written here, every instance of Node2D will produce a duplicate Validator (as will any other class decorated with Validator) and ValidateKeys.
EDIT: THIS IS WRONG! See answer below.
Note that this is primarily a learning exercise for me and although I would be interested in hearing criticisms/suggestions for improving my over all approach, my primary goal is to learn more about how to use decorators effectively.
Also note that I normally would not use capitalization for a decorator class but am using it here since it makes it easier to read on SO.
My assumption was incorrect.
As things are written, only one instance of Validator and ValidateKeys is created per class. I did not realize that the line #Validator(ValidateKeys, 'x','y') only runs once (at the time of class definition) and not at instance creation.
I should have realized this, since decorator expressions appear at the same level of hierarchy as class attributes, e.g.:
class MyClass():
class_attribute = None #only one class_attribute is created
#decorator #only one decorator (i.e., decorated method) is created
def method():
pass
Test:
class Validator(object):
def __init__(self, TheValidator, *args, **kwargs):
print("New Validator Object")
self.TheValidator = TheValidator(*args,**kwargs)
def __call__(self,f):
def wrapped_f(instance, *args,**kwargs):
self.TheValidator(instance, *args, **kwargs)
return f(instance,*args,**kwargs)
return wrapped_f
class ValidateKeys(object):
def __init__(self,*keysIterable):
print("New ValidateKeys Object")
self.validkeys = keysIterable
def __call__(self, instance, **kwargs):
for a in kwargs:
if not a in self.validkeys:
raise Exception()
instance.__dict__.update(kwargs)
class Node2D(object):
#property
def coords(self):
return self.__dict__
#coords.setter
def coords(self,Coords):
self.set_coords(**Coords)
#Validator(ValidateKeys, 'x','y')
def set_coords(self,**Coords):
pass
n1 = Node2D()
n2 = Node2D()
n1.setcoords(x=1,y=2)
n1.coords
Output:
'New Validator Object' #<-- Seen only once when module is loaded (class defined)
'New ValidateKeys Object' #<-- Seen only once when module is loaded (class defined)
'{'x': 1, 'y': 2}'
I do not have the problem I thought I had. Thanks to all for the help.

Categories

Resources