Inheriting a virtual class method - how to call it from base class? - python

Let B inherit from A. Suppose that some of B's behavior depends on the class attribute cls_x and we want to set up this dependency during construction of B objects. Since it is not a simple operation, we want to wrap it in a class method, which the constructor will call. Example:
class B(A):
cls_x = 'B'
#classmethod
def cm(cls):
return cls.cls_x
def __init__(self):
self.attr = B.cm()
Problem: cm as well as __init__ will always be doing the same things and their behavior must stay the same in each derived class. Thus, we would like to put them both in the base class and not define it in any of the derived classes. The only difference will be the caller of cm - either A or B (or any of B1, B2, each inheriting from A), whatever is being constructed. So what we'd like to have is something like this:
class A:
cls_x = 'A'
#classmethod
def cm(cls):
return cls.cls_x
def __init__(self):
self.attr = ClassOfWhateverIsInstantiated.cm() #how to do this?
class B(A):
cls_x = 'B'
I feel like it's either something very simple I'm missing about Python's inheritance mechanics or the whole issue should be handled entirely differently.
This is different than this question as I do not want to override the class method, but move its implementation to the base class entirely.

Look at it this way: Your question is essentially "How do I get the class of an instance?". The answer to that question is to use the type function:
ClassOfWhateverIsInstantiated = type(self)
But you don't even need to do that, because classmethods can be called directly through an instance:
def __init__(self):
self.attr = self.cm() # just use `self`
This works because classmethods automatically look up the class of the instance for you. From the docs:
[A classmethod] can be called either on the class (such as C.f()) or on an instance
(such as C().f()). The instance is ignored except for its class.

For ClassOfWhateverIsInstantiated you can just use self:
class A:
cls_x = 'A'
#classmethod
def cm(cls):
return cls.cls_x
def __init__(self):
self.attr = self.cm() # 'self' refers to B, if called from B
class B(A):
cls_x = 'B'
a = A()
print(a.cls_x) # = 'A'
print(A.cls_x) # = 'A'
b = B()
print(b.cls_x) # = 'B'
print(B.cls_x) # = 'B'
To understand this, just remember that class B is inheriting the methods of class A. So when __init__() is called during B's instantiation, it's called in the context of class B, to which self refers.

Related

Am I able to call a submethod of a class's attribute from that class using the class as an attribute?

I am very sorry for the confusing title, I did not know how else to phrase the question.
Let's say I have a class, A. It is described as shown:
class A:
def __init__(self, argument):
self.value = argument
def submethod(self, argumentThatWillBeAClass):
print(dir(argumentThatWillBeAClass))
And then I initialize it as shown below:
classAInstance = A('42.0')
Now, I have a class, B. Let's add a submethod that calls A's submethod with B as an argument.
class B:
def __init__(self, argumentThatIsAClassAInstance):
self.classAInstance = argumentThatIsAClassAInstance
def submethod(self):
self.classAInstance.submethod(self)
Let's initialize it with classInstance:
classBInstance = B(classAInstance)
My desired result is that all the attributes of B are printed when B.submethod is called. Is this possible, and if not, how would I achieve something like this?
Now, I have a class, B. Let's add a submethod that calls A's submethod
with B as an argument.
But that isn't what your code does. On the following line:
self.classAInstance.submethod(self)
You are calling the method (I don't know what you mean by "sub" method, these are all just normal methods) with *an instance of B, not B.
Two different ways you could do this:
self.classAInstance.submethod(type(self))
Or:
self.classAInstance.submethod(B)
The semantics aren't exactly the same, since the first dynamically retreives the instance, if some other class inherits from B, it will call dir on that class. The second always prints dir(B), regardless of inheritance.
So:
class A:
def method(self, klass: type) -> None:
print(dir(klass))
class B:
def __init__(self, a: A) -> None:
self.a = a
def method(self) -> None:
self.a.method(type(self))
b = B(A())
As one potential solution, you can use inheritance. This allows class B to inherit everything from class A
class A:
def __init__(self, argument):
self.value = argument
def submethod(self, argumentThatWillBeAClass):
print(dir(argumentThatWillBeAClass))
class B(A):
def __init__(self, value):
super().__init__(value)
def submethod(self, argumentThatWillBeAClass): # You can override the method and do extra code too.
super().submethod(argumentThatWillBeAClass) # Calls A's submethod function

Conditional Inheritance based on arguments in Python

Being new to OOP, I wanted to know if there is any way of inheriting one of multiple classes based on how the child class is called in Python. The reason I am trying to do this is because I have multiple methods with the same name but in three parent classes which have different functionality. The corresponding class will have to be inherited based on certain conditions at the time of object creation.
For example, I tried to make Class C inherit A or B based on whether any arguments were passed at the time of instantiating, but in vain. Can anyone suggest a better way to do this?
class A:
def __init__(self,a):
self.num = a
def print_output(self):
print('Class A is the parent class, the number is 7',self.num)
class B:
def __init__(self):
self.digits=[]
def print_output(self):
print('Class B is the parent class, no number given')
class C(A if kwargs else B):
def __init__(self,**kwargs):
if kwargs:
super().__init__(kwargs['a'])
else:
super().__init__()
temp1 = C(a=7)
temp2 = C()
temp1.print_output()
temp2.print_output()
The required output would be 'Class A is the parent class, the number is 7' followed by 'Class B is the parent class, no number given'.
Thanks!
Whether you're just starting out with OOP or have been doing it for a while, I would suggest you get a good book on design patterns. A classic is Design Patterns by Gamma. Helm. Johnson and Vlissides.
Instead of using inheritance, you can use composition with delegation. For example:
class A:
def do_something(self):
# some implementation
class B:
def do_something(self):
# some implementation
class C:
def __init__(self, use_A):
# assign an instance of A or B depending on whether argument use_A is True
self.instance = A() if use_A else B()
def do_something(self):
# delegate to A or B instance:
self.instance.do_something()
Update
In response to a comment made by Lev Barenboim, the following demonstrates how you can make composition with delegation appear to be more like regular inheritance so that if class C has has assigned an instance of class A, for example, to self.instance, then attributes of A such as x can be accessed internally as self.x as well as self.instance.x (assuming class C does not define attribute x itself) and likewise if you create an instance of C named c, you can refer to that attribute as c.x as if class C had inherited from class A.
The basis for doing this lies with builtin methods __getattr__ and __getattribute__. __getattr__ can be defined on a class and will be called whenever an attribute is referenced but not defined. __getattribute__ can be called on an object to retrieve an attribute by name.
Note that in the following example, class C no longer even has to define method do_something if all it does is delegate to self.instance:
class A:
def __init__(self, x):
self.x = x
def do_something(self):
print('I am A')
class B:
def __init__(self, x):
self.x = x
def do_something(self):
print('I am B')
class C:
def __init__(self, use_A, x):
# assign an instance of A or B depending on whether argument use_A is True
self.instance = A(x) if use_A else B(x)
# called when an attribute is not found:
def __getattr__(self, name):
# assume it is implemented by self.instance
return self.instance.__getattribute__(name)
# something unique to class C:
def foo(self):
print ('foo called: x =', self.x)
c = C(True, 7)
print(c.x)
c.foo()
c.do_something()
# This will throw an Exception:
print(c.y)
Prints:
7
foo called: x = 7
I am A
Traceback (most recent call last):
File "C:\Ron\test\test.py", line 34, in <module>
print(c.y)
File "C:\Ron\test\test.py", line 23, in __getattr__
return self.instance.__getattribute__(name)
AttributeError: 'A' object has no attribute 'y'
I don't think you can pass values to the condition of the class from inside itself.
Rather, you can define a factory method like this :
class A:
def sayClass(self):
print("Class A")
class B:
def sayClass(self):
print("Class B")
def make_C_from_A_or_B(make_A):
class C(A if make_A else B):
def sayClass(self):
super().sayClass()
print("Class C")
return C()
make_C_from_A_or_B(True).sayClass()
which output :
Class A
Class C
Note: You can find information about the factory pattern with an example I found good enough on this article (about a parser factory)

Private Class Variables in Python?

I'd like to be able to extend a class without inheriting one of the class variables.
Given this scenario:
class A:
aliases=['a','ay']
class B(A):
pass
print(B.aliases)
I would rather get an error that B has not defined the aliases variable rather than have B accidentally called ay.
One could imagine a solution where aliases becomes a member of the instantiated object (self.aliases) and is set in __init__ but I really want to be able to access the aliases using the cls object rather than an instance of the class.
Any suggestions?
Python does not have REALY private attributes. But you can define it with a double underscore (__):
class A:
__aliases=['a','ay']
class B(A):
pass
print(B.__aliases) # yields AttributeError
But you still will be able to access it with:
print(B._A__aliases)
This is kindof a ganky work around but here you go:
class K:
def __init__(self):
self.mems = dir(self)
def defaultMembers():
k = K()
return(k.mems)
class A:
aliases=['a','ay']
class B(A):
def __init__(self):
for k in set(dir(self))-set(defaultMembers()):
print("removing "+k)
setattr(self, k, None)
a = A()
b = B()
print(b.aliases)
#None
print(a.aliases)
#['a','ay']
I guess all you really need is the setattr(self, "aliases", None) still this results in a None and not a non-variable. Unfortunately calsses don't support deletion because I tried to use del first.

Inherit properties from an already constructed parent class in a child class

I'm stuck on this point about class inheritance, and I haven't been able to figure out a clean way forward. I have some base class:
class Foo:
""" Parent class. """
def __init__(self,a,b):
self.a = a
self.b = b
def product(self):
return self.a * self.b
This class contains information I've loaded in from some data file. I want to generate different kinds of information related to this data, using class properties to store that information. For example, if I wanted to create a report that tells me the quotient of a and b, I would want to create something like:
class Bar(Foo):
""" Child class. """
def __init__(self,foo_object):
# What I want to avoid:
self.a = foo_object.a
self.b = foo_object.b
def quotient(self):
return self.a / self.b
Obviously there are many more class properties in my real application. It becomes very tedious to assign all of the properties from the Foo object into the new Bar object.
Is there a more elegant way to "extend" a Foo object into a Bar object once the Foo object has already been created?
EDIT:
Sorry if I didn't state my objective clearly enough; I see that there's some confusion about what I'm asking for. I've already created a Foo object with a number of properties. I want to create a child class of Foo, called Bar, later, that contains those properties but also some additional properties that are specific to one "area" of my application.
So, I want to be able to pass in an already instantiated Foo object, and pass the values of its properties into identical properties in Bar, without doing this manually item-by-item.
I believe Flob's answer is what I'm looking for. Thank you!
You can inherit the Information directly by initiating the parent class inside the child class. Once you have created an instance of the parent class, you can Access all it's properties by using vars(object), which will return a dictionary of all properties assiciated with the object. For example, let's say you have a class Foo:
class Foo:
def __init__(self, a, b):
self.a = a
self.b = b
and test_parent is an instance of this class.
test_parent = Foo(a='Hello', b='World')
now, when creating the Bar child class, do it like this:
class Bar(Foo):
def __init__(self, foo_object):
a, b = vars(foo_object).values() #get all properties of parent class object
Foo.__init__(self, a, b) # initiate parent class
def say_hello(self):
print('{} {}'.format(self.a, self.b))
Create an instance of the Bar class and call say_hello:
test_child = Bar(test_parent)
test_child.say_hello()
Output:
"Hello World"
I'm not sure I understand what you mean by "once the Foo object has already been created".
For initialization of the attributes defined by the parent class, use this:
class Bar(Foo):
""" Child class. """
def __init__(self,a,b):
super().__init__(a,b)
def quotient(self):
return self.a / self.b
Let the __init__() method of the super-class continue to do its job of initializing a and b.
Note that b = B() creates only one object, not two.
In the code you posted, Bar.__init__() seems to take a Foo object as one of its arguments. Technically, that is a case of wrapping a Foo object in a Bar object -- and if you're doing that, there really is no need for Bar to be a child class of Foo:
class Bar:
""" Wrapper class. """
def __init__(self, foo_object):
self.foo_object = foo_object
def quotient(self):
return self.foo_object.a / self.foo_object.b
It is possible to change the class of the object after it is created. Instead of unpacking the contents of the object into a new one, you change the class that it uses to look up methods:
class Foo:
def __init__(self, a, b):
self.a = a
self.b = b
class Bar(Foo):
def quotient(self):
return self.a/self.b
f = Foo(1, 2)
f.__class__ = Bar
f.quotient() # 0.5
This usually isn't necessary (I woudln't recommend using it here). Instead, you could create a Bar object directly:
b = Bar(1, 2)
b.quotient()
Bar inherits the __init__ method of Foo, so you don't have to redefine it.
Another option is to use a function that's not a method:
def quotient(obj):
return obj.a/obj.b
Now you can compute quotient(Foo(1,2)) even though your Foo object doesn't know about the quotient function.
A class can inherit from a base class, but an object cannot inherit from another object. It can either be a copy (with possibly additional attributes) or wraps the original object. The first case is for a true inheritance strategy (the child instance has to be an instance of the base class and must initialize itself), the second if for an aggregation or containment strategy.
But beware, there are some caveats with the second one, mainly because the child object is not an instance of the base class (isinstance(child, Foo) is false). That means that there is no one size fits all way here, and you will have to choose one pattern, inheritance vs aggregation.

class property that can be accessed and set by all instances (also of subclasses)

I'd like to set a class property that can be shared by all instances of the class or its subclasses. It should be possible for any instance to also set the property.
I tried the following:
class A:
x = 1
#classmethod
def setX(cls, val):
if cls.__bases__:
cls = cls.__bases__[-1]
cls.x = val
This seems to work fine in case of single inheritance. But if I use multiple inheritance, depending on the order of inheritance, it either works or doesn't (i.e., class A is not always the last of bases).
Any ideas for a robust implementation?
Use lexical scoping:
class A(object):
x = 1
#classmethod
def setX(cls, val):
A.x = val
The right way, IMO, is to go with a class property. Since properties are tied to an object's classes __dict__, as opposed to the object's very own __dict__, and since your object happens to be a class, you must attach it to the classes class, its metaclass:
class A(object):
_x = None
class __metaclass__(type):
#property
def x(cls):
return A._x
#x.setter
def x(cls, val):
A._x = val
class B(A):
pass
A.x = 'jim'
B.x = 'joe'
print A.x, B.x
Result:
joe joe
Also, your classes must be new style classes, i.e. they must inherit from object in Python 2.x. And metaclasses are defined differently in Python 3.x:
class MetaA(type):
""" like ___metaclass__ above """
class A(metaclass=MetaA):
_x = None
...

Categories

Resources