Types and Polymorphism
00:00 Welcome to your next lesson in Object-Oriented Programming in Python versus Java. In this lesson, we’ll take a look at Python’s type system, comparing it to Java’s.
00:13
Java is strictly typed. That means that before the program can be compiled, all of the types must match up. So, for example, if I created an interface in Java called Device
requiring that anything that implements Device
needs to provide a .getVoltage()
method and I created a Car
class which did so—created a private field and a method to return it, and we’re not concerned with other aspects of the Car
class today—and then created a standalone Rhino
object, and then tried to use those with a method called .charge()
, and this .charge()
method simply is going to take a Device
and call its .getVoltage()
method.
01:08
And then if I wrote a main()
method to create a Car
, create a Rhino
, and try to charge the Car
, that would work.
01:16
But if I tried to charge the Rhino
, I would get a compiler error. Java would not let me even attempt to run this program because a Rhino
isn’t a Device
and doesn’t have a .getVoltage()
method. And in fact, if I did something silly and actually gave the Rhino
a .getVoltage()
method…
01:50
Let’s say this returns 10
, what the heck. Even if I tried that, because Rhino
still isn’t a Device
, we can’t charge it.
02:01
Java checks matching types at compile time. So if you have a Device
called a Car
—for example, is a Device
—then when you call the .getVoltage()
method on it, it will use the Car
’s .getVoltage()
. Rhino
isn’t a Device
, and so .getVoltage()
makes no context and we can’t even compile that program.
02:30 Python is a little bit different. It uses something called duck typing. If a variable walks like a duck and quacks like a duck, then it’s a duck. Typing is checked at runtime—means if you try to call a method on an object, it will check to see if that method is there and if it is, it’ll run it!
02:50 Even if perhaps the first time a particular function was written, it wasn’t intended to do it on that particular object, for that particular type of object. So, again, we can put everything in a single Python module.
03:07
So here is my Device
, which has ._voltage
, and we can return it and we can access it. It’s non-public, but we can get to it if we need to.
03:18
I’m going to create a Car
and a Phone
that are both devices, and I’m going to create a Rhino
that isn’t. Because we’re just using class stubs—we’re not actually putting any meaningful code in them—since we don’t have the the braces that Java uses, we use the word pass
for a similar purpose.
03:39
It shows us that we really don’t have a body to this particular definition. It works for methods, it works for functions. But without the braces syntax, Python needs something to indicate “This is supposed to be empty,” and they use the word pass
to indicate that.
03:57
So, if I import car
, and let’s say I create a charge()
method, and I’ll do this right here, interactively… So I’m going to define charge()
and charge()
is going to work on a parameter, which we will call device
.
04:14
We’re going to check to see if the device
has the attribute that we’re looking for, namely ._voltage
. So if hasattr()
(has attribute), I’m just going to check to see if device
has an attribute ._voltage
.
04:31
And if it does, we will print f"Charging a"
, so many volts, "volt device."
. If we don’t have that attribute, we’ll display a warning message, print(f"I can't charge a {}")
, and then we’re going to extract the class name from the device
, and that’s done through a couple of dot operator operations.
05:07
We’ll see more about this in an upcoming lesson. But .__class__
, and then two underscores, .__name__
, and this will produce for us the class of our device
. And so if I make my_car
—
05:32
and we didn’t put any parameters in this particular version—I can charge it. Yay. If I create a Phone
…
05:49
It’s in the car
module, so we have to say car.Phone()
.
05:57
Phone
has the attribute that we need, so we’re good. If I make a Rhino
06:09 and then try to charge it,
06:15
I get the warning message that I can't charge a Rhino
, which makes sense. However, if I wanted to do something silly and say that Rhino
has a ._voltage
attribute…
06:39
Make it 10
again, for whatever reasons I might want to do that. So if I then reload that module and recreate that method, or that function—this is a function, not a method—
07:01
and if our device
has the attribute ._voltage
, we’ll print that we are charging a
07:21
Otherwise we’ll say again, f"I can't charge a {}"
…
07:35
There we go. And now I won’t make the Car
or the Phone
but if I make my_rhino
, because Python does its checks at runtime, it will check to see—even though we didn’t define Rhino
as a Device
—it has a ._voltage
!
08:02
And so if we try to charge my_rhino
, this is now going to work. So there is a look a little bit at Python’s implementation of polymorphism. The parameter that we passed to it had its own version of the non-public attribute ._voltage
, and as a result, was able to be used in this charge()
method. And again, the phrase that’s often referred to is duck typing. If a variable walks like a duck and quacks like a duck, then it’s a duck.
08:38 If it has the features that we need at runtime to make an instruction make sense, then it’ll work. In your next lesson, we’ll take a look at some of the default methods that all objects inherit in Python.
Become a Member to join the conversation.