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?!
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.
In C++, a derived class object can be assigned to a base class object, but the other way is not possible.
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.
// As base-class pointer cannot access the derived class variable.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With