Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

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 Carfor example, is a Devicethen 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:16 device.

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 Deviceit 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.