What does inheritance actually do in Python? - python

I'm struggling with how I should interpret class inheritance. What does it actually do? As far as I know, it allows your class to:
Use the inherited class function/methods
Use the inherited class local variables (self variables)
Does this go both ways? If something is inherited, will it be able to read his inheriter as well?
Please give your answer in as much layman's terms as possible. I'm not an native Englisch speaker and I've never had proper programming education. Give me an explenation, not a definiton :]

Let's say you have a class called Animal. In this class you have a method called walk, that prints "Animal is walking"
Now you have 2 other classes:
1. Class Bird that is inherited from Animal. You can now add an additional method to it: fly. This will print that a bird can fly.
2. Class Monkey that is inherited from Animal as well. You can now add an additional method to it: climb. This will print that a monkey can climb trees.
Both Monkey and Bird derive from Animal, so they can walk, they have a same functionality/feature. But both have a distinct feature as well: birds can fly and monkeys can climb trees. So it takes sense, right?
The reverse is false, because not every Animal can fly or can climb trees.
EDIT: I exaplined it to you in terms of methods. But this can apply to variables as well. The Animal class can have a weight field. Which is accessible from both inherited classes: Monkey and Bird.
But Monkey can also have a field called max_power and Bird can have a class variable called max_fly_altitude. This fields are unique for these certain types of animal and NOT for the generic Animal. Which can also be a crocodile. And a crocodile can't fly to have a max_fly_altitude attribute.

Inheritance does not work both ways. Here is what I mean:
If you have a class called Father and another class called Son which inherits from father as class Son(Father), then Son can use all the methods from the Father class but the Father cannot use the methods from the Son class.
Examples
class A:
def get_val(self):
return 5
class B(A):
def num(self):
return 3
>>> a = A()
>>> b = B()
>>> a.get_val()
5
>>> b.num()
3
>>> b.get_val()
5
>>> a.num()
Attribute Error: A instance has no attribute 'num'
Analogy
class Musician:
def get_type(self):
return 'musician'
class Pianist(Musician):
def get_prof(self):
return 'pianist'
>>> m = Musician()
>>> p = Pianist()
>>> m.get_type()
'musician'
>>> p.get_type()
'musician'
>>> p.get_prof()
'pianist'
>>> m.get_prof()
Attribute Error: Musician instance has no attribute 'get_prof'

Related

Class inheritance via super with two arguments

In the below code, i replaced args with numbers to demonstrate what classes are inherited.
class Animal:
def __init__(self, animalName):
print(animalName, 'is an animal.');
class Mammal(Animal):
def __init__(self, mammalName):
print(mammalName, 'is a mammal.')
super().__init__(mammalName)
class CannotFly(Mammal):
def __init__(self, mammalThatCantFly):
print('2', "cannot fly.")
super().__init__('2')
class CannotSwim(Mammal):
def __init__(self, mammalThatCantSwim):
print('1', "cannot swim.")
super().__init__('1')
# Cat inherits CannotSwim and CannotFly
class Cat(CannotSwim, CannotFly):
def __init__(self):
print('I am a cat.');
super().__init__('Cat')
cat = Cat()
returns
I am a cat.
1 cannot swim.
2 cannot fly.
2 is a mammal.
2 is an animal.
Why is it not the below?
I am a cat.
1 cannot swim.
1 is a mammal.
1 is an animal.
2 cannot fly.
2 is a mammal.
2 is an animal.
There are effectively two call streams, no?
You can see the method resolution order (MRO) for Cat:
>>> Cat.mro()
[<class '__main__.Cat'>, <class '__main__.CannotSwim'>, <class '__main__.CannotFly'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>]
Each class appears once in the MRO, due to the C3 linearization algorithm. Very briefly, this constructs the MRO from the inheritance graph using a few simple rules:
Each class in the graph appears once.
Each class precedes any of its parent classes.
When a class has two parents, the left-to-right order of the parents is preserved.
("Linearization", because it produces a linear ordering of the nodes in the inheritance graph.)
super() is misnamed; a better name would have been something lie nextclass, because it does not use the current class's list of parents, but the MRO of the self argument. When you call Cat, you are seeing the following calls.
Cat.__init__
Cat.__init__ uses super to call CannotSwim.__init__
CannotSwim.__init__ uses super to call CannotFly.__init__
CannotFly.__init__ uses super to call Mammal.__init__
Mammal.__init__ uses super to call Animal.__init__
Animal.__init__ uses super to call object.__init__
object.__init__ does not use super (it "owns" __init__), so the chain ends there.
In particular, note #3: CannotSwim causes its "sibling" in the inheritance graph to be used, not its own parent.
Take a look at this post What does 'super' do in Python? - difference between super().__init__() and explicit superclass __init__()
Now what it says is that __init__ is called for every class in the instance's mro.
you can print that by doing print(Cat.__mro__) this will print out
(<class '__main__.Cat'>, <class '__main__.CannotSwim'>, <class '__main__.CannotFly'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>)
As you can see there is the order of the calls. Now, why '2' is used and not '1', see the answer in the comments by hussic

Creating a StringAutoEnum class with a clean and concise syntax for auto-values

I'm trying to implement a StringAutoEnum class which can set its member values to a lowercase version of the member name without having to call auto() for every single value, as this is verbose and I simply don't like it.
Assuming this base StringAutoEnum class:
import aenum
class StringAutoEnum(aenum.Enum):
def _generate_next_value_(name, start, count, last_values):
return name.lower()
The ideal case would be something like this:
class Animal(StringAutoEnum):
DOG, CAT, SNAKE
Such that the following is True:
Animal.DOG.value == "dog"
Animal.CAT.value == "cat"
Animal.SNAKE.value == "snake"
Obviously, the above class definition is not even valid python code (unless DOG, CAT, and SNAKE have already been defined in advance), so doing it this way is (as far as I'm aware), out of the question.
So, a few alternatives I've considered are the following:
Assignment to multiple variables:
class Animal(StringAutoEnum):
DOG = CAT = SNAKE = auto()
Iterable unpacking:
class Animal(StringAutoEnum):
DOG, CAT, SNAKE = auto()
To reiterate, I do NOT want to have to do this:
class Animal(StringAutoEnum):
DOG, CAT, SNAKE = auto(), auto(), auto()
And I also don't want to have to use the functional API.
The issue with assignment to multiple variables seems to be that auto() is only evaluated once for the first variable name, so all 3 members of the Enum end up with the name 'DOG' and the value 'dog'. I haven't been able to find a workaround for this.
The issue with iterable unpacking is that I can't think of any way of knowing in advance how many variables are on the left side of the assignment operator.
This means that while this will work and will do exactly what I want:
class Auto(aenum.auto):
def __iter__(self):
return iter([Auto() for _ in range(3)])
class Animal(StringAutoEnum):
DOG, CAT, SNAKE = Auto()
This unfortunately will not:
class Animal(StringAutoEnum):
DOG, CAT, SNAKE, EMU = Auto()
And I cannot think of any way of solving it this way for assignment to an arbitrary number of members.
Could anyone think of an alternative way to solve this problem that is clean and concise? Alternatively, could someone propose a workable implementation of assignment to multiple variables/iterable unpacking that works?
Thanks in advance!
Since you are using aenum you have the option of AutoEnum -- which fully embraces the magic of metaclasses:
from aenum import AutoEnum
class StringAutoEnum(AutoEnum):
#
def _generate_next_value_(name, start, count, last_values):
return name.lower()
class Animal(StringAutoEnum):
DOG, CAT, SNAKE
--> list(Animal)
[<Color.Dog: 'dog'>, <Color.CAT: 'cat'>, <Color.SNAKE: 'snake'>]
If that's too magical, the next best solution is using auto() for each value (or using the functional API).
This can be done with standard libraries just as easily:
from enum import Enum
class StringAutoEnum(str, Enum):
#staticmethod
def _generate_next_value_(name, *_):
return name.lower()
Animal = StringAutoEnum('Animal', 'DOG CAT SNAKE')
>>> list(Animal)
[<Animal.DOG: 'dog'>, <Animal.CAT: 'cat'>, <Animal.SNAKE: 'snake'>]
The second argument to the new class can also be an iterable instead of a string.
Using an str mixin type is not required but lets you work with the enum values (in this case the lower case names) directly as strings without casting them first.

Grandchild inheriting from Parent class - Python

I am learning all about Python classes and I have a lot of ground to cover.
I came across an example that got me a bit confused.
These are the parent classes
Class X
Class Y
Class Z
Child classes are:
Class A (X,Y)
Class B (Y,Z)
Grandchild class is:
Class M (A,B,Z)
Doesn't Class M inherit Class Z through inheriting from Class B or what would the reason be for this type of structure? Class M would just ignore the second time Class Z is inherited wouldn't it be, or am I missing something?
Class M would just inherit the Class Z attributes twice (redundant) wouldn't it be, or am I missing something?
No, there are no "duplicated" attributes, Python performs a linearization they can the Method Resolution Order (MRO) as is, for instance, explained here. You are however correct that here adding Z to the list does not change anything.
They first construct MRO's for the parents, so:
MRO(X) = (X,object)
MRO(Y) = (Y,object)
MRO(Z) = (Z,object)
MRO(A) = (A,X,Y,object)
MRO(B) = (B,Y,Z,object)
and then they construct an MRO for M by merging:
MRO(M) = (M,)+merge((A,X,Y,object),(B,Y,Z,object),(Z,object))
= (M,A,X,B,Y,Z,object)
Now each time you call a method, Python will first check if the attribute is in the internal dictionary self.__dict__ of that object). If not, Python will walk throught the MRO and attempt to find an attribute with that name. From the moment it finds one, it will stop searching.
Finally super() is a proxy-object that does the same resolution, but starts in the MRO at the stage of the class. So in this case if you have:
class B:
def foo():
super().bar()
and you construct an object m = M() and call m.foo() then - given the foo() of B is called, super().bar will first attempt to find a bar in Y, if that fails, it will look for a bar in Z and finally in object.
Attributes are not inherited twice. If you add an attribute like:
self.qux = 1425
then it is simply added to the internal self.__dict__ dictionary of that object.
Stating Z explicitly however can be beneficial: if the designer of B is not sure whether Z is a real requirement. In that case you know for sure that Z will still be in the MRO if B is altered.
Apart from what #Willem has mentioned, I would like to add that, you are talking about multiple inheritance problem. For python, object instantiation is a bit different compared other languages like Java. Here, object instatiation is divided into two parts :- Object creation(using __new__ method) and object initialization(using __init__ method). Moreover, it's not necessary that child class will always have parent class's attributes. Child class get parent class's attribute, only if parent class constructor is invoked from child class(explicitly).
>>> class A(object):
def __init__(self):
self.a = 23
>>> class B(A):
def __init__(self):
self.b = 33
>>> class C(A):
def __init__(self):
self.c = 44
super(C, self).__init__()
>>> a = A()
>>> b = B()
>>> c = C()
>>> print (a.a) 23
>>> print (b.a) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'B' object has no attribute 'a'
>>> print (c.a) 23
In the above code snipped, B is not invoking A's __init__ method and so, it doesn't have a as member variable, despite the fact that it's inheriting from A class. Same thing is not the case for language like Java, where there's a fixed template of attributes, that a class will have. This's how python is different from other languages.
Attributes that an object have, are stored in __dict__ member of object and it's __getattribute__ magic method in object class, which implements attribute lookup according to mro specified by willem. You can use vars() and dir() method for introspection of instance.

Access instance variable from one instance to another

I am learning python. Suppose I have my Class named Dog, and two instances named Fido and Molly. I want to change an attribute of second instance by overloading + operator with __add__ so when I type Fido+Molly
the Molly.touched attribute will be set. How is accessing from one instance to another instance's attributes deployed in python?
I think you are interested in how the __add__ special method works. In essence it works as x.__add__(y) where x and y can be instance objects. See an example of it's implementation here.
You would need to overload __add__() in Dog to return something that updates the y.touched attribute. Example:
class Dog(object):
def __init__(self, touched):
self.touched = touched
def __add__(self, other):
other.touched = other.touched + self.touched
#return None # optional, explicit
fido = Dog(1)
molly = Dog(2)
fido + molly
molly.touched
# 3

Can I instantiate a subclass object from the superclass

I have the following example code:
class A(object):
def __init__(self, id):
self.myid = id
def foo(self, x):
print 'foo', self.myid*x
class B(A):
def __init__(self, id):
self.myid = id
self.mybid = id*2
def bar(self, x):
print 'bar', self.myid, self.mybid, x
When used, the following could be generated:
>>> a = A(2)
>>> a.foo(10)
foo 20
>>>
>>> b = B(3)
>>> b.foo(10)
foo 30
>>> b.bar(12)
bar 3 6 12
Now lets say I have some more subclasses class C(A): and class D(A):. I also know that the id will always fit in either B, C or D, but never in 2 of them at the same time.
Now I would like to call A(23) and get an object of the correct subclass. Something like this:
>>> type(A(2))
<class '__main__.B'>
>>> type(A(22))
<class '__main__.D'>
>>> type(A(31))
<class '__main__.C'>
>>> type(A(12))
<class '__main__.B'>
Is this impossible or is it possible but just bad design? How should problems like this be solved?
You should rather implement Abstract Factory pattern, and your factory would then build any object you like, depending on provided parameters. That way your code will remain clean and extensible.
Any hack you could use to make it directly can be removed when you upgrade your interpreter version, since no one expects backwards compatibility to preserve such things.
EDIT: After a while I'm not sure if you should use Abstract Factory, or Factory Method pattern. It depends on the details of your code, so suit your needs.
Generally it's not such a good idea when a superclass has any knowledge of the subclasses.
Think about what you want to do from an OO point of view.
The superclass is providing common behaviour for all objects of that type, e.g. Animal. Then the subclass provides the specialisation of the behaviour, e.g. Dog.
Think of it in terms of an "isa" relationship, i.e. a Dog is an Animal.
An Animal is a Dog doesn't really make sense.
HTH
cheers,
Rob
I don't think you can change the type of the object, but you can create another class that will work like a factory for the subclasses. Something like this:
class LetterFactory(object):
#staticmethod
def getLetterObject(n):
if n == 1:
return A(n)
elif n == 2:
return B(n)
else:
return C(n)
a = LetterFactory.getLetterObject(1)
b = LetterFactory.getLetterObject(2)
...

Categories

Resources