Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scope of derived class in base class - Inheritance in python

Tags:

I know that by inheriting the base class. All the functions in base class would be accessible in the derived class as well. But how does it work the other way, meaning can a function that is defined in the child class be accessible in the base class.

I tried the above out with an example. And it works just fine. But how can that be. I am not able to get the logic behind the working.

class fish:

    def color(self):
        # _colour is a property of the child class. How can base class access this?
        return self._colour  


class catfish(fish):

    _colour = "Blue Cat fish"

    def speed(self):
        return "Around 100mph"

    def agility(self):
        return "Low on the agility"

class tunafish(fish):

    _colour = "Yellow Tuna fish"

    def speed(self):
        return "Around 290mph"

    def agility(self):
        return "High on the agility"

catfish_obj = catfish()
tunafish_obj = tunafish()

print(catfish_obj.color())
print(tunafish_obj.color())

I understand that the instance is being passed and all through self, but the details of the child class should logically not be accessible in the base class, correct?!

like image 209
Abhishek Avatar asked Mar 24 '18 15:03

Abhishek


People also ask

What does derived class inherit from the base class?

The derived class inherits all members and member functions of a base class. The derived class can have more functionality with respect to the Base class and can easily access the Base class. A Derived class is also called a child class or subclass.

Can you assign a derived class object to a base class object?

In C++, a derived class object can be assigned to a base class object, but the other way is not possible.

What does not a derived class inherit from the base class?

Following are the properties which a derived class doesn't inherit from its parent class : 1) The base class's constructors and destructor. 2) The base class's friend functions. 3) Overloaded operators of the base class.

Does base class have access to derived class?

// As base-class pointer cannot access the derived class variable.


1 Answers

You are accessing attributes on an instance, not on a class. Your self reference is never an instance of the fish class, only of one of the two derived classes, and those derived classes set the _colour attribute.

If you created an instance of fish() itself, you'd get an attribute error, because that instance will not have the attribute set.

You may perhaps think that in base classes, self becomes an instance of the base class; that's not the case.

Instead, attributes on an instance are looked up on the instance directly, and on its class and base classes. So self._colour looks at the instance, at type(instance) and at all further objects in the type(instance).__mro__, the Method Resolution Order that sets all classes in a hierarchy in a linear order.

You could print out the type() of your object:

>>> class fish:
...     def color(self):
...         print(type(self))
...         return self._colour
...
# your other class definitions
>>> print(catfish_obj.color())
<class '__main__.catfish'>
Blue Cat fish
>>> print(tunafish_obj.color())
<class '__main__.tunafish'>
Yellow Tuna fish

The self references are instances of the derived classes, passed into an inherited method. So self._colour will first look at the attributes directly set on self, then at type(self), and there _colour is found.

Perhaps it would help to see how Python methods work. Methods are just thin wrappers around functions, created when you look up the attribute on an instance:

>>> tunafish_obj.color  # access the method but not calling it
<bound method fish.color of <__main__.tunafish object at 0x110ba5278>>
>>> tunafish.color      # same attribute name, but on the class
<function fish.color at 0x110ba3510>
>>> tunafish.color.__get__(tunafish_obj, tunafish)  # what tunafish_obj.color actually does
<bound method fish.color of <__main__.tunafish object at 0x110ba5278>>
>>> tunafish_obj.color.__self__   # methods have attributes like __self__
<__main__.tunafish object at 0x110ba5278>
>>> tunafish_obj.color.__func__   # and __func__. Recognise the numbers?
<function fish.color at 0x110ba3510>

Look closely at the names of the objects I access, and what happens when I call the __get__ method on a function. Python uses a process called binding when you access certain attributes on an instance; when you access an attribute this way, and that points to an object with a __get__ method, then that object is called a descriptor, and __get__ is called to bind the object to whatever you looked the object up on. See the descriptor howto.

Accessing color on the instance, produces a bound method object, but the description of the object tells us it came from fish, it's named a *bound method fish.color of instance reference. Accessing the same name on the class gives us the fish.color function, and I can manually bind it to create a method again.

Finally, the method has an attribute __self__, which is the original instance, and __func__ which is the original function. And there's the magic, when you call a bound method, the method object just calls __func__(__self__, ....), so passing in the instance it was bound to.

When that function was inherited, (found on the fish class, so fish.color), it is still passed an instance of the derived class, and still has everything the derived class has.

Python is very dynamic, and very flexible. You can take any old function and put it on a class, and it can be bound into a method. Or you can take any unbound function, and manually pass in an object with the right attributes, and it'll just work. Python doesn't care, really. So you can pass in a new, indepdent type of object and still have the fish.color function work:

>>> fish.color  # original, unbound function on the base class
<function fish.color at 0x110ba3510>
>>> class FakeFish:
...     _colour = 'Fake!'
...
>>> fish.color(FakeFish)  # passing in a class! Uh-oh?
<class 'type'>
'Fake!'

So even passing in a class object, completely unrelated to the fish hierarchy, but with the expected attribute, still works.

To most Python code, if it walks like a duck, and quacks like a duck, the code will accept it as a duck. Call it duck typing.

like image 175
Martijn Pieters Avatar answered Sep 23 '22 12:09

Martijn Pieters