Coding 101+: Level Up Your Python with Object-Oriented Programming | Alvin Wan | Skillshare

Coding 101+: Level Up Your Python with Object-Oriented Programming

Alvin Wan, AI PhD Student at UC Berkeley

Coding 101+: Level Up Your Python with Object-Oriented Programming

Alvin Wan, AI PhD Student at UC Berkeley

Play Speed
  • 0.5x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 2x
18 Lessons (1h 29m)
    • 1. Introduction

      2:22
    • 2. Project

      3:03
    • 3. Concept: OOP Paradigm

      3:55
    • 4. Practice: Ice Cream

      9:54
    • 5. (Bonus) Practice: Light Switch

      6:10
    • 6. (Bonus) Mystery: Sync'ed Lights

      5:13
    • 7. Concept: Abstraction

      5:03
    • 8. Practice: Ice Cream Truck

      7:57
    • 9. (Bonus) Practice: Sync'ed Lights

      6:35
    • 10. Concept: Inheritance

      4:17
    • 11. Practice: Deluxe Ice Cream Truck

      4:37
    • 12. (Bonus) Practice: Flickering Light

      4:07
    • 13. (Bonus) Mystery: MRO

      3:20
    • 14. Concept: Inheritance+

      5:36
    • 15. Practice: Melting Ice Cream

      5:20
    • 16. (Bonus) Practice: Timed Lights

      5:40
    • 17. (Bonus) Mystery: Fragile Base Case

      3:01
    • 18. Conclusion

      2:55
195 students are watching this class
  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels
  • Beg/Int level
  • Int/Adv level

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.

563

Students

--

Projects

About This Class

Ever heard of the term “Object-Oriented Programming”? Haven’t heard of it, but want to level up your coding skills?

This class covers a must-know topic for every programmer: Object-Oriented Programming (OOP). We’ll cover several concepts and takeaways:

  • The paradigm of thinking, for Object-Oriented Programming
  • A simplistic ice cream truck simulator using OOP concepts
  • A minimal simulation for light switches in a home, also using OOP concepts
  • What classes and instances are
  • How to write your first class
  • How to keep code readable (abstraction), maintainable (inheritance), and flexible (mixins, composition)
  • 1 hour of content with bite-sized, 5-minute lessons + 30 minutes of bonus content

The class is highly interactive, as we’ll be coding together. By the end of this class, you’ll be prepared to write and read code using Object-Oriented Programming. More importantly, you’ll level up your code with a new tool for organizing your code.

Interested in creative coding? Check out my VR101 (AFrame) class.

Interested in data science or machine learning? Check out my Coding 101 (Python), SQL 101 (Database Design), Data 101 (Analytics), or Computer Vision 101 (Applied ML) classes.

Acknowledgments: B-roll in introductory video filmed by creators on Pexels (Anthony Shkraba, Mikhail Nilov, Mart Production, Karolina Grabowska, Vitaly Vlasov, pixabay, pressmaster, Andy Barbour, pavel danilyuk, Roman Odintsov, German Korb, cottonbro)

Meet Your Teacher

Teacher Profile Image

Alvin Wan

AI PhD Student at UC Berkeley

Top Teacher

Let me help! I'm a computer science PhD student at UC Berkeley, where I've taught for 5 years. I've designed a few courses to get you started -- not just to teach the basics, but also to get you excited to learn more. Check out the courses below! Or scroll down for a guide to getting started.

Website | Github | YouTube | Twitter | Research

 

Featured Reviews

"Alvin Wan is a fantastic teacher. The instruction format was just what I was looking for. This is fun due to the format... Due to Alvin's teaching method I'm not only grasping the content I'm having fun learning."

Rick M., Coding 101: Python for... See full profile

Class Ratings

Expectations Met?
  • Exceeded!
    0%
  • Yes
    0%
  • Somewhat
    0%
  • Not really
    0%
Reviews Archive

In October 2018, we updated our review system to improve the way we collect feedback. Below are the reviews written before that update.

Your creative journey starts here.

  • Unlimited access to every class
  • Supportive online creative community
  • Learn offline with Skillshare’s app

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.

Transcripts

1. Introduction: I wish I'd known this secret a long time ago. I'll tell you now, there are two ways to grow as a coder. One, you can take years to practice making 100 mistakes for every single coding principle you adopt. Two, you can take days, even hours learning these same principles from another coder. Here's the kicker. One of the most important thing that a senior coder can teach you is ways of thinking about code. One of those ways is Object-Oriented Programming, or OOP for short, the secret sauce for taking your code to the next level. Hi, I'm Alvin. At UC Berkeley, I've taught over 5,000 students. From classes in discrete mathematics to machine learning. I'll be passing down to you wisdom that professors, senior engineers, and mentors gave to me so that you can grow faster as a coder than I ever did. In this class, you'll learn how to think critically as a programmer. This critical thinking will then allow you to model the world around you as a series of different objects interacting with one another. This critical thinking will also allow you to rate readable, maintainable, and flexible code. OOP is used everywhere, for games, for machine learning, for data science. By the end of this course, you'll understand why and how. You'll be able to mimic in code a bike shop processing repairs, an ice cream truck managing orders, or an air traffic controller directing airplanes. This Coding 101 plus class is written for beginners that are either interested in one, leveling up their code or two, frustrated trying to learn Object-Oriented Programming. If that's you, don't worry. I've had my fair share of confusion in the past and I'm hoping to help you with your confusion now. Note that this class does expect some coding familiarity with Python. If you aren't already familiar, make sure to take my Coding 101: Python for Beginners course to get caught up. Your takeaways from this class won't be weird syntax or specific Python rules. Instead, it's a way of thinking. You just need the intuition to apply these concepts, even if you forget what the concepts are called. On top of these concepts, I hope to help you build confidence; confidence reading, writing, talking about OOP concepts. Now, let's get started building with Object-Oriented Programming. 2. Project: In this class, your project is to build a simulation using the concepts you've learned. As examples, you'll be building an ice cream truck managing orders and a minimal model of light switches in a home. Your simulation could be for anything. A bike shop processing repairs, a warehouse processing bulk shipments, or an air traffic controller managing aircraft. Pick whatever scenario you want. Just make sure that your scenario has many different types of interacting objects. The simulation you build is powerful. In the future, you can add an interface to make it a management app, use this system to predict the future for a very complex system or build a game with it. In this class, we won't cover the bells and whistles, for example, how to add an interface. Instead, we'll focus on the core, an interacting series of objects, and how to make that code flexible, scalable, and maintainable. This is a great way to understand the power of object-oriented programming and how to use it effectively. As you may have heard from one of my previous classes, my motto is to give you the excitement to learn more, not necessarily hand you a laundry list of different Python utilities. The same applies in this class. But here I also want to give you the intuition for writing better, cleaner code. To do that, we'll code up each concept, not once but twice. We'll do this specifically in four different phases. First, we'll cover the OOP paradigm, the philosophy and foundation of object-oriented programming. Second, inheritance, ways to make OOP less redundant, improving intelligibility. Finally, mixins and composition, ways to make OOP easily extensible and flexible. As we practice each of these concepts, I want you to get a feel of what is wrong and what is right in designing code. Forgetting these exact terms and definitions won't make much of a difference, what you want is the intuition for writing cleaner, better code. Each of the four phases is split up into three different parts. The 1st part is the concept, introducing the ideas, terms, and definitions. The second is practice. You will then use these concepts in two different examples. Some phases will be accompanied by a mystery lesson. This mystery lesson covers a special OOP concept. These mysteries are actual bugs that students have encountered before. I hope that these lessons can save you some pain and confusion in the future. You may have heard of other OOP concepts like polymorphism or encapsulation. I mentioned these terms briefly but not in detail, as I believe that the core of OOP lies with the terms that we will cover, abstraction, inheritance, mixins, and composition. There are a large number of lessons, but I've designed each lesson to be relatively short and bite-sized so that you can hopefully take each of these lessons on the go, on your commute, on bathroom break, anytime really. I hope you're excited to start building your simulation. Let's get started. 3. Concept: OOP Paradigm: In this lesson, I'll introduce the object-oriented programming paradigm. There won't be any code in this lesson. The goal is simply to introduce the OOP way of thinking. We'll cover three concepts in this lesson. The 1st concept is the difference between a class and an instance. Here we have a template ice cream. Later, we'll talk about what this template actually contains. For now, think of this template as your expectation of an ice cream. You expect to be able to eat it, you expect a scoop of ice cream and an ice cream cone, and maybe you expect vanilla flavor by default. However, this template, your expectation is imaginary. Here we have an actual ice cream serving that you can eat. Notice you can spawn multiple servings from a single template. Also notice that you can eat each serving at different rates. One serving may have one scoop left and the other may have no scoops left. Now let's rename template and serving. Instead of template, we'll call this a class. The ice cream class is your expectation of an ice cream, what it contains, and how you can interact with it. We'll dissect this in more detail in the next section. Instead of serving, we'll call these instances. An ice cream instance is an actual ice cream you can eat. Like before, notice you can spawn multiple instances from a single class. You can also eat each instance at a different rate. One instance may have one scoop left and the other may have no scoops left. In OOP terminology, we say we've defined the ice cream class. In this lesson, I'll give you several fundamental building blocks for how to design these classes. We'll start with defining methods. In this section, we'll talk about class methods. We actually use methods in Coding 101 Python for beginners, but in this class, we'll be defining methods. To understand method, let's go back to our ice cream example. Methods help us define what can I do with ice cream. Well, we can eat and add scoops to our ice cream, and that's it. These two actions are our methods. We will code these methods later. In OOP terminology, we design our ice cream class to include the eat and add scoop methods. Again, we design our ice cream class to include the eat and add scoop methods. That concludes methods. But now, how do we implement these methods? Well, each of our ice cream instances needs to know how many scoops are left. That brings us to attributes. Attributes are pieces of information. We'll first focus on information that is specific to an instance or a serving of ice cream. Back to our ice cream example, what information is specific to each ice cream instance? First, the number of scoops left may differ between ice cream instances. Second, the flavor of ice cream may differ between ice cream instances. In OOP terminology, we design our ice cream class to include the eat and add scoop methods. It also includes attributes for the number of scoops left and the ice cream flavor, and that's it. These are the building blocks of object-oriented programming. To recap, we covered the difference between classes or templates and instances. We also discussed the rule of methods describing how to interact with an instance. We mentioned attributes which hold information about an instance. I know there were a lot of terms in this lesson, but don't worry, we'll continue to use these terms again and again in the upcoming lessons. By the end of this class, you'll be sick of hearing of the words class, instance, method, and attribute. But you'll also understand these terms much more deeply. For a copy of these slides and more resources, make sure to check out the course website. This concludes our lesson, the Object-Oriented Programming Paradigm. In the next lesson, we'll start simulating an ice cream truck using these concepts. 4. Practice: Ice Cream: In this lesson, we'll write our very first piece of code using object-oriented programming. Start by navigating to repl.it/languages/python3. I suggest pausing the video here and creating an account if you haven't already. Whereas you can code without an account, an account allows you to save your code. Then, I suggest placing your Skillshare and Repl.it windows side by side as shown here. That way, you can code along with me. The first step is defining your first class. Here is how. Any character in black is required. It must be typed exactly that way. We start off with the class keyword in black, then we add the class name denoted in blue. Like many other Python concepts you've learned, add a colon, then add two spaces and for now, use pass. Pass is just a placeholder for other code we will write later. Let's code this now. In this lesson, we'll create an ice cream class. Let's use the format we see on the left, type in class, IceCream. Hit colon. Once you type Enter, Repl.it will automatically create two spaces for you. Now, type in pass. I'm going to hit Escape to dismiss this modal. That's it for your very first class. Let's use this class now to create an instance. We call this process of creating an instance from a class instantiation. Here is how to instantiate. Instantiation is really only the right half of this format, type the class name in blue, then add two parentheses, and that's all you need to instantiate. We'll also assign this instance on the right to a variable on the left. Just like with defining any other variable, we have the variable name in pink on the left, a black equal sign with optional spaces and then the value. In this case, the value is the instance. Let's code this now. Before starting, make sure to run your file by hitting "Run", the green arrow at the top. Next, we'll use the interactive prompt to test our code. Your interactive prompt may be on the right-hand side of your screen. I've changed the layout so that mine is on the bottom. First, type your variable name, and then instantiate typing your class name and the parentheses. Let's now see what the variable ice cream contains and the contains our instance or our object. The next concept is a method. We'll now add an action or behavior to our class. This is what we have so far. To add a method, we simply define a function inside the class. This should look familiar, the def keyword we've seen before. We use def to define functions as well. The only difference is that our first argument to the method must be self denoted in black here. Here, we have the method name in blue, the method contents in orange, and spaces in gray. Let's code this now. Inside of your editor, we're going to now delete this pass and add the eat method, so def eat self colon, just like we see on the left-hand side. Now, we're going to type in print, and I'm going to write Yum. That's it for defining your eat method. Let's now test or use this method. To test or use this method, use the dot expressions recovered in the Python for beginners course, type the instance variable name, which is ice in our example, add a dot shown in black, then add the method name in blue. Finally, use two parentheses to actually call the method just like we call functions. Let's code this now. Again, don't forget to run the file by clicking on the green arrow. First, instantiate, so ice_cream equals IceCream with parentheses. Then, call the eat method. We'll use the template on the left-hand side, ice_cream, our variable name.eat. Hit Enter and we get Yum, which is our print statement in the body of the eat method. Now, we introduce a concept called the constructor. The code in the constructor is run when the class is instantiated. Let me show you by example. Here's an example constructor. Notice that the first line is all black. You must copy that exactly for now. As always, you can place whatever code you want in orange in the body of the method. Here, our constructor simply prints high. Let's code this now. We're going to add a constructor to our ice cream class. Here, we'll have def_init_self colon, just like we see on the left-hand side there, and then we're going to print something memorable. In this case, we'll say print Created ice cream. I'm going to add a blink line here just to keep our code clean and readable. Again, run your file by hitting the green arrow. We're now going to instantiate our ice cream class, ice_cream equals to IceCream and here, you can see Created ice cream. Now, for our final concept, an attribute. Assigning an attribute is very similar to defining a variable. Here, we have the attribute name in pink and the value in orange. Notice, however, that we have the self. in black. This is important. This is how we attach the attribute to the current ice instance. Self will always refer to the current instance. Let's code this now. Delete the contents of a constructor. Instead, we're going to add self.scoops equals to 3. This assigns the number of scoops to three. Let's test this. Run your file by clicking on the green arrow at the very top. Then, instantiate your ice cream class. Check the number of scoops. Here, we'll have the instance.scoops. Here, we have three as expected. That wasn't the last part of the lesson. We actually have two more snippets of code to review. Let's change our eat method to account for the number of scoops eaten. This was our previous method definition with the method name in blue and the method contents in orange. Now, we add an argument in green. We can then use that green input argument, in this case, y in our method. here we use y to compute y times y. We also have the method name in blue, the method contents in orange and spaces. Let's code this now. In our eat method, we'll add an input argument called scoops. This will allow the ice cream user to eat however many scoops they like at once. Now, let's update the number of scoops that the ice cream has left. Instead of print Yum, add self.scoops equals to the original total, but minus the number of scoops we wish to eat. I'm also going to introduce another way of writing this line of code that is a lot shorter. We can also replace this with self.scoops minus equals scoops. These two lines are exactly the same, so I'm going to delete the first one here. This line says decrease the number of scoops by the number of scoops we wish to eat. Let's review how to call a method. From left to right, we have the instance variable name in pink, a dot, and the method name in blue then the input argument in green. Let's try this now. Go ahead and click on the green button at the very top to run your file. Then, type in ice_cream equals IceCream. That instantiates. Then, we're going to check how many scoops are currently contained, which are three. Then, we're going to eat or call the eat method with the argument two as we see on the left-hand side. This will eat two scoops, and let's check how many scoops are left. We have one scoop as expected. That concludes our fundamentals for writing object-oriented programming code. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. You may be thinking, "Holy cow, there's a lot of new terms. That's pretty overwhelming." If that's you, don't worry. I was there too at some point. Feel free to skip to the next lesson, which is practice to continue to build familiarity with these OOP terms. Alternatively, if you're interested in some challenging bonus practice problems, continue the lesson. Now, on to our bonus section practice. Let's practice using the terminology in this lesson by adding two more code snippets. First, to review, this eat method takes in a scoop argument and subtracts that many scoops from the total number of scoops. Now, to practice, create a method called add that takes in a scoops argument and adds that many scoops to the total. Pause the video here and try this now. If you don't know where to start, that's okay. You'll get this with more repetition. Let's now write the method, add. Right underneath our each method, we will define add. Again, the first argument is always self. Then, add the scoops argument. We should then add that number of scoops to the total. This concludes our add method. Let's practice another one. This will test your knowledge from the Python coding 101 class. Time to review if statements if you've forgotten them. In the eat method, check if the number of scoops left is less than the number of scoops the user is trying to eat. If so, print not enough bytes left; if not, deduct the number of scoops from the total as usual. Again, pause the video here to try. If you don't know how to do this, don't worry. With more practice will come more familiarity. Now, let's augment our eat method. We're going to check if the number of scoops is less than the number of scoops the user is trying to eat. If so, print not enough bytes left. Otherwise, deduct a number of scoops in total as usual. That concludes our augmented eat method. That's it for this lesson. Again, in the next lesson, we'll have some practice to continue to build familiarity with these OOP terms. 5. (Bonus) Practice: Light Switch: Welcome to bonus practice. In this lesson, we'll create a simulation for a series of lights. Just like before, navigate to repl.it/languages/python3. Then I suggest placing your Skillshare and repl.it windows side-by-side as shown here. First up, is how you should define your class. We have the class keyword in black, class name in blue, colon, two spaces and the body of the class in orange, which is the pass placeholder for now. Let's code this. In this lesson, we'll simulate a light switch. So let's call our class Light. Class Light: "Enter", which will create two spaces for us and go ahead and type "Pass". Let's now create an instance of our class or instantiate our class. On the right-hand side, we have the class name in blue and parentheses in black. That's all we need to instantiate the class. However, we should assign this instance to a variable. We have the variable name in pink, equals sign in black with optional spaces and the class name in blue. Let's now code this. Go ahead and hit the green arrow at the very top to run your code. Again, we're going to now type in our interpreter. For you, your interpreter may be on the right-hand side of your screen, mine is on the bottom half. We're going to now define the variable which is light, and the class name with parentheses to instantiate. Let's see what the light variable contains and it contains an instance or an object. Let's now add the ability to toggle the light on and off. Here's the class in our example, and here's the method add. We have the def keyword in black, method name in blue and the body of the method in orange, along with the spaces in gray. We are now going to define a method toggle using this format. Replace the pass keyword and instead type def toggle again with the self argument and print "Toggle on and off!". I'm going to hit "Escape" to dismiss this model. Now, we should see how to test this program. These are the dot expressions you've seen before. We have the instance variable name in pink, a dot in black, the method name in blue and the parentheses in black again. These parentheses are needed to code the method. Let's now test this program. First, run the code by clicking on the green arrow at the very, very top. Instantiate your light, then call the toggle method, light.toggle and here we get our print statement. In this next section, we're going to cover the constructor. Here is an example of constructor with the first line completely in black and the body of the constructor in orange. We have the expression here and the spaces in gray. Let's now add a constructor. We're going to define init with the self argument and then we're going to print "Creating light". Like before, I'm going to add a space for readability. Again, run your code by clicking on the green arrow at the very top and type in "Light equals Light" to instantiate. Here, we get the print statement that we wrote before. We're now going to add attributes to track the light's internal state, whether it's on or off. This is the format for defining attributes. We have a self dot in black, the attribute name in pink and the value in orange. Self always refers to the current instance. Let's code this now. First, in the constructor, we set On to False. Then inside the toggle method, we're going to check if the light is currently on. So if self.on: then we set the light to False and if it's currently not on, then we turn it on. We can actually quickly summarize these four lines of code using just one line of code by typing in self.on equals to not self.on. If self.on is true, not true will give us false. If self.on is true, not true will give us false. So not allows us to simply switch between true and false. Again, this line is exactly the same as these four lines. I'll delete these first four lines and this is our toggle method. Let's now test our code. Again, hit the green arrow to run your code and we're going to instantiate our light. We're going to check if it's on or not. It's currently not on. Let's toggle the light and check if it's on again and it is indeed on. Our code works. For a copy of these slides and the finished code for this lesson, make sure you check out the course website. That concludes our practice lesson. I hope you're feeling somehow familiar with the terminology so far. There's no need to memorize the formats we've covered. First, you can always look it up. Second, by virtue of repeatedly seeing these formats again and again, by the end of this course, you'll be more familiar with them than you are now. In the next lesson, we'll visit a mysterious bug and how to fix it. If you'd like to practice with more challenging problems, continue watching this lesson for a practice. Like in the last lesson, we now have a bonus practice. Create a method called is_on that returns whether or not the light is on. Pause the video and give this a try now, and here's the solution. We're going to add another method inside of the Light class called is_on. Again, you need your self argument, and we're going to simply return soft.on. I'm going to hit "Escape" to dismiss this model and go ahead and run your code by clicking on the green arrow. I'm going to instantiate the light and here we're going to check, is the light on by typing in light.is_on. Make sure to code your method with parentheses and indeed it's false. Now, let's toggle the light so that it is on and check, is it on? Indeed, this is true. Our method works. Like we said before, this concludes our lesson and in the next lesson, we'll visit a mysterious bug and how to fix it. 6. (Bonus) Mystery: Sync'ed Lights: In this lesson, we'll cover a mysterious bug and how to fix it. Note these mysteries address some of the most confusing parts of OOP. If you're feeling overwhelmed, I suggest skipping these mystery sections on the first pass, revisiting them on a second pass to the course. Just like before, navigate to repl.it/languages/python3. Then I suggest placing your Skillshare and Repl.it windows side by side as shown here. First up, is the class. Try to create a class called Light. Pause your video here if you don't want a hint. Here is now your hint. This is the format for a class definition. Let's now write the solution. We're going to define a class Light. For this next step, we'll instantiate these Lights. Instantiate the Light classes two times, name your instances a and b. Pause your video here if you don't want a hint. Now, here is your hint. This is the format for instantiation and assigning the instance to a variable. Let's now write the solution. It's okay if you didn't know what to write. Here, we're going to type in a is equal to Light and b is equal to Light. Next, let me introduce a new way of defining an attribute for your class. Here is your class. We're going to define what is called the class attribute by defining a variable in the body of this class. Let's now update our Light code. Instead of a pass we're going to type in here on is equal to False. To access this class attribute, we can directly reference the class and its attribute by using a dot expression. Hit the "Green arrow" to run your code. Now, we're going to use the interpreter. The interpreter for me is in the bottom half of the screen, but maybe on the right-hand side for you. Go ahead and type in the class name.on. We can see that the default is False as we defined here. Note that if you define an instance, you can also access this attribute as well. For example, if we type a.on we'll get False and b.on will also be False. Now it's time for us to introduce the mystery. Let's try modifying one of these Light attributes. Previously, we modified these attributes in a method. We can modify attribute values outside of the methods too. Change Light a to be on. We can set a.on equals to True. As expected if we check a.on, we get True. Now, what do you think happens to Light b, it should still be off. We can check to find out Light b is indeed still off. Now, it's time for the mystery. Let's change the class attribute to True. Here we'll type in Light.on is equal to True. Now, what do you think happens to Light b? It should still be off, we can check, but Light.b is on. What happened here? We have to visualize to understand what happened. We started off defining the Light class attribute to be off. We then defined a new instance as expected, this new instance is still off. We then changed the class attribute to True so that the default is on. This is what we expected it to happen. We expected that the instantiated Light is not affected. However, we saw that the instantiated Light was affected. It was also on, which is right here, b.on was True as well. Here's what actually happened. We defined the Light class attribute, which is off by default. When we instantiated Light, the Light did not have its own attribute for on or off. Instead, the Light instance still had a class attribute. As a result, when we change the class attribute, the instances on attribute changed as well. Takeaway is that class attributes are always shared. Let's now explore another mystery. We're going to now set Light.on equals to False. What do you think happens to Light b, as you may expect? b is also now False. What do you think happened to Light a? If we type in a.on, Light a is actually still on. What? How does Light a escape the shared attribute? Let's break this down with another visualization. On the left-hand side, we have pictured what we think happened. We have the class in black and the two instances below. As we learned in the previous section, class attributes are shared. Let's use arrows instead. Earlier, we actually manually turned Light a on. In this case, setting the attribute directly actually converts the class attribute into an instance attribute. Light a is now decoupled and has its own state. We then set the class attribute to True and set the class attribute back to False again. The entire time, Light a stays on and is unaffected. Takeaway is that class attributes are shared but instance attributes are not. This also leads us to a tip to avoid this confusion. The tip is to not modify class attributes in your program. This will save you the headache we illustrated. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. This concludes the lesson. In the next lesson we'll introduce a new concept. 7. Concept: Abstraction: Welcome to another conceptual lesson, abstraction. There won't be any code in this lesson. Instead, we'll discuss an object oriented programming concept called abstraction, which improves the readability and maintainability of your code. Let me explain what abstraction is using our ice cream example. This is our slide from before. Notice we've outlined the expectations of any ice cream instance we encounter. For any ice cream instance, we know what the ice cream instance does and what the data each ice cream instance has. For example, we can eat an ice cream instance. But more importantly, we can eat an ice cream instance without knowing how the eat method is implemented. The last part is key. As a user of ice cream instances, we can abstract away and ignore how these methods work. We just need a rough description of what the method does. We call this notion of abstracting away details abstraction. To repeat that definition more succinctly, the idea of abstraction is to remove unnecessary details for the user. In our example, the user is the ice cream eater. The person eating knows they can eat the ice cream or add scoops. The unnecessary details how the ice cream handles eating are abstracted away. This improves the readability of your code. We're now going to discuss what abstraction is hiding away. What is this unnecessary information? This next section exemplifies a related concept called encapsulation. In the center we have our two methods eat and add. On the right in blue we have method descriptions for external users that each method allows users to eat until no more ice cream is left. The add method allows external users to add more ice cream. On the left in red, we have the internal workings of each method internally or maintain account for the number of scoops left. Not too surprisingly, eat simply subtracts a scoop, and add simply adds a scoop, the red internal details are hidden away from external users. To enforce this, we use encapsulation. We won't emphasize encapsulation too much. Just know that in encapsulation involves restricting access to internal information, like enforcing that scoops cannot be modified by outsiders. In some encapsulation hides information from the user. If you're confused about the difference between abstraction and encapsulation, that's okay. Not knowing the difference even after completing this course is okay. Using abstraction and encapsulation, whichever one it is, is the important part. Now we'll discuss a benefit of abstraction, which is to remove redundancy in code. This redundancy removal is how abstraction actually improves maintainability of your code. Here's how abstraction removes redundancy. On the right, we have an ice cream cone. It's supports the add method, which adds a scoop of ice cream. On the left, we have an ice cream truck. It makes sense that when ordering from the ice cream truck, we also had a scoop of ice cream. However, what if we want to modify scoop adding behavior? Now, we can only add a scoop, if the total number of scoops is less than three. We also need to add this restriction wherever we add a scoop of ice cream, like in the ice cream truck. Next, what if we add another modification? If there are no scoops left, add three scoops at once. We also need to add this wherever we add a scoop of ice cream, like in the ice cream truck, this quickly becomes a maintenance nightmare. Here's why. What happens when these two copies of scoop adding code go out of sync? Then, the ice cream starts to behave in unexpected ways. You might have some ice cream with more than three scoops, for example. These unexpected behaviors are what cause bugs in production, so watch out. But how do we fix this? We only want one copy of the scoop adding code. Quite simply, any code that adds ice cream scoops should call the ice cream classes add method. Nobody else should directly modify the number of scoops in an ice cream cone. Enforcing this abstraction allows us to reduce redundancy in code. Tip. Check for redundant code. If your copy pasting or writing redundant code, you're doing it wrong. We'll elaborate on why and how when we begin coding. Abstraction removes unnecessary information. For example, you don't need to know how ice cream eating works to call the eat method. This improves the readability of your code, making it easier and quicker to understand. Encapsulation hides or restricts access to information. For example, you can't access the number of ice cream scoops left directly. Finally, abstraction removes redundancy and code, subtraction also improves maintainability of your code. Don't forget abstraction. Here's a summary of the concepts we've covered so far. For a copy of these slides and more resources, make sure to check out the course website. This concludes our lesson, abstraction. In the next lesson, we will simulate an ice cream truck that correctly implements abstraction. 8. Practice: Ice Cream Truck: In this lesson, we'll practice abstraction by creating an ice cream truck class. Once you see this page, fault the project to get your own editable copy. To do this, click on the project name in the top left to get a drop-down, click on the ellipsis to get another drop-down, and finally, click on the Fork button. Then I suggest placing your Skillshare and repl.it windows side-by-side as shown here. You should then see code similar to the ones that I see on the right. If you scroll down, you'll see the ice cream class that we wrote in the last lesson. The first step is to amend our add scoops functionality. If we have more than three scoops, our ice cream should topple over, leaving us with zero scoops. Inside of our add method, we should first check if there are more than three scoops. Here we'll type in if self.scoop is greater than three, then set the number of scoops to zero. We're also going to add a print statement here to explain what happened to the user. Here we'll print, 'Too many scoops! Dropped ice cream.' Before moving on though, here's a tip. Avoid magic numbers. Magic numbers are any numbers written directly in the code like this three and this is zero. Zeros are simple enough to reason about, but why three? We know three is the maximum number of scoops. But in more complex programs, magic numbers become harder for other coders to understand. Instead, we should replace this three with a variable called max scoops, which has the value three. Let's do this now. For our variable, we will define a class attribute called max scoops. Let's do this now on code. Inside of your ice cream class, we're going to define max scoops equals to three. We can then use this class attribute below instead of the number 3. Here we're going to write instead of number 3 in the add method, self.max scoops. Let's now use this ice cream class and see how to respect abstraction in practice. Click on the green arrow at the top to run your file. Now, instantiate your ice cream. Then let's say we want to add scoops, we can directly modify the number of scoops, ice cream.scoops plus equals to two. But remember, we want to set the number of scoops to zero. If we exceed three scoops, we could start coding this. If ice cream scoops greater than three, but this should seem familiar. We just wrote this code in the add method, this familiarity, this redundancy, should be a red flag. Furthermore, we should not be modifying the scoops attribute directly. We're not properly following abstraction. So instead, we should be using the add method that we already wrote. Here instead we should be writing ice cream.add(2) and here we'll notice that our functionality actually kicks in. We have more than three scoops in the ice cream now, so we topple over and we only have zero scoops. We could repeat the same mistake. Say you want to decrease the number of scoops. We could type ice cream.scoops minus equals to three. But then wait, we want to check if there are enough scoops left before subtracting. So we can type in, 'If ice cream.scoops less than three", and again, they should seem familiar. We just wrote this logic in the eat method. This is our red flag. We should use the eat method. Instead of typing this, we will now written ice cream.eat(3). This will give us, not enough bites left. Tip, check for redundant code. If you're copy pasting or writing redundant code, you're probably doing it wrong, and here's why. Having redundant code makes code difficult to maintain. Specifically, an update to one copy may not get propagated to the other copy of that code. You then have slowly diverging copies of code that both attempt to do the same thing, so check for redundant code, make sure you're respecting abstraction. We will now outline a new ice cream truck class. As a refresher, here is the format. Define your new class with the class name, two spaces and pass for the body of the class. We're going to define a class ice cream truck and pass for now. Then define your methods. You'll need one method order so that customers can order their ice cream cones. For this method, we need to know how many scoops to add initially. Delete "Pass" and now add "Order" with the self argument and the number of scoops to add initially. Again, we'll type in pass and fill out the body of our method later. We will also have a second method called add, so that customers can order refills for their ice cream cones. For this method, we need the ice cream to add two and how many scoops to add. Define the add method, but the self argument, and then the ice cream to add to and the number of scoops to add. Again, use pass and we'll fill out the method later. Let's now flesh out the ice cream truck class. Inside of the order method we'll create a new ice cream instance. Here we have ice cream equals to a new ice cream instance. We're now going to add scoops to it. We've learned our lesson from before, we will use the add method from ice cream instead of modifying the scoops attribute directly. Now type ice cream.add and the number of scoops. Finally, return the ice cream to the customer. We will also finish the add method. This method is pretty simple. Add scoops to the ice cream, so ice cream.add scoops. Let's now track how many scoops we sell. We will need to add a constructor that initializes the number of scoops sold to zero. Here we're going to define a new constructor with the self argument and set the number of scoops sold equal to zero. Next, we will then update the number of scoops sold in the order method. In the order method we'll add the number of scoops sold. Finally, in the add method, we'll do this one more time, we'll fill out the number of scoops sold. But wait, we just wrote this line of code up above. If you compare these two lines, 37 and 38 with the two lines above, 32 and 33, you'll see that they look identical. This is a red flag again. We have redundant code, we could be breaking abstraction. Turns out we are. This method add for the ice cream truck handles adding scoops to an ice cream, so we should be using the ice cream trucks add method to add scoops to an ice cream cone. Instead, let's delete lines 32 and 33 and instead replace this with self.add ice cream scoops. We're now respecting the abstraction for our ice cream truck. Tip, understanding the contract a class provides can help you organize code within the class. Here, understanding that the add method handles adding scoops and accounting for a number of scoops sold means we can use this add method instead of doing both these steps manually, saving us from writing redundant code. Let's now use this ice cream truck class. Learning from our previous mistakes, we know now to respect abstraction. Again, hit the green arrow at the top of your file to run your code. Note that there is now a new level of abstraction. We shouldn't create or add to ice cream cones directly, instead, we should order from the ice cream truck. Let's now create a new truck. Order ice cream with three scoops. Here we have ice cream one is equal to truck.order with three ice cream scoops, then eat some of the ice cream. Now let's order even more ice cream from the truck. We're going to type in truck.add ice cream one and add one scoop. Let's see how many scoops the ice cream trucks sold. Looks good. The initial order was for three scoops and we added one more scoop, making for a total of four scoops sold. For a copy of these slides and the finished code for this lesson make sure to check out the course website. In this lesson, we've covered some abstraction practice using an ice cream truck. In particular, you've seen how I failed to use abstraction and how to fix that. Remember the three tips from this lesson, avoid magic numbers, check for redundant code and be clear about expectations. The next lesson is bonus practice. 9. (Bonus) Practice: Sync'ed Lights: In this lesson, we'll practice more of the abstraction concept we've discussed. This time, we'll build on our light simulation by synchronizing some of the light switches. Navigate to this URL to get started. Once you see this page, fork the project to get your own editable copy. To do this, click on the project name in the top left to get a drop-down. Click on the ellipsis to get another drop-down and finally, click on the Fork button. Then I suggest placing your Skillshare and Repl.it windows side-by-side as shown here. Before we begin, I want to introduce a new concept you haven't seen yet, a default argument in Python. Let's start by defining a subtraction function. As a reminder, here's the format for defining a function. For once, this format slide is actually the exact code we'll write. Let's write our code now. Your code on the right-hand side should match mine. If you scroll down, you'll see the light class that we defined in previous lessons. Right above this light class, we're going to define the sub method that we see on the left-hand side. Go on and type out, define sub (x, y) and return x minus y. Let's now use this objective function. We'll call sub with input seven and four. Go on and hit the green arrow at the very top to run your code. Then type in sub 7, 4 to get three. Now what happens if you forget the second argument? What if we type sub seven and hit ''Enter''? Well, you'll get an error and your error will look like this. It tells you that you're missing your second argument. Let's define another subtraction function sub two. This time we will assign y a default argument of zero. Let's write our code now. We'll define sub2 x but now we type y equal to zero and everything else remains the same. This equal sign in the function definition gives y a default value. It doesn't always assign y to zero. The function only assigns y to zero if you don't give a second input argument. Let's see this in action. First, run the file by hitting the green arrow at the very top. Let's see what happens when we pass both arguments like before. Type in sub2 7, 4 and as expected, we get three. Here, x is assigned to seven and y is assigned to four. Now, let's see what happens if we only pass in one argument. Type in sub2 7 and it turns out there is no error and we get seven out. What happened here? This is because x was assigned to seven and y was automatically assigned to zero. We got 7 minus 0. Let's now cover a related concept called keyword arguments. This is best explained by example. Let's rerun the previous version with two input numbers. Here we'll have sub2 7,4. Here, seven is assigned to x and the second input four is assigned to y. You hit ''Enter'', we get 7 minus 4 which is 3. We can also explicitly tell Python which input is x and which input is y. Here we can type in sub2, x equals seven and y is equal to four. We can hit ''Enter'' and we still get three. We call these x equals to seven, y is equal to four keyword arguments. You can specify keyword arguments in any order. Here we can switch the x and the y keyword arguments sub2, y is equal to four, x is equal to seven. Hit ''Enter'' and you still get three. Notice that if you don't use keyword arguments and you switch the inputs, then you'll get something different. I'm going to clear my output so that you can see the bottom of my screen and here we're going to type in sub2 4, 7 switching the order without using keyword arguments and here the output is different. We get negative 3 because we now have 4 minus 7 instead of 7 minus 4. Now that you've covered default arguments and keyword arguments, let's use them in our new class. We're now going to finally work on the abstraction concept. We're going to add the ability to synchronize two lights so that toggling like Number 1 also toggles like Number 2. Go ahead and scroll to your light class. We'll add an argument to the constructor called sync. This argument will have a default value of none with a capital N. None just means empty. The intention is to set sync to another light to synchronize with. You'll see what I mean in a second. Here we're going to define light after the self argument comma sync equals to none. We can then set the input argument sync as an instanced attribute also called sync. Here we'll write self.sync equals to sync. Let's now use the sync attribute if it isn't empty or in other words, if the sync attribute is not none, then toggle the sync to light. Go on and scroll down to your toggle method. Here we're going to type in if self.sync sync is not none, then toggle the sync light. Here you could be tempted to simply toggle the sync light yourself by writing in self.sync.on equals to not self.sync.on. However, you'd be breaking abstraction. What if that light is synced with another light? Then we'd have to check if self.sync.sync is not none and so on and so forth. Wow, that's redundant code. So like always, redundant code is a big red flag. Instead, we have to respect abstraction, self.sync as a light and we can toggle lights by calling their toggle method. Instead we'll write self.sync.toggle. This concludes our synchronized lights. Now, we're going to instantiate and use the synchronized light. Go ahead and hit the green arrow at the very top to run your code and now let's instantiate our very first light. Light 1 is equal to light. Instantiate your second light but this time set the sync input argument to the first light. This ensures that if we toggle light Number 2, light Number 1 also toggles. Here define light Number 2 is equal to light, set sync equal to the first light. Let's see if light Number one is on. We're going to type in light1. is on. It looks like our first light is not on. Now, let's toggle light Number 2.This should toggle both light Number 1 and Number 2 on. Here we have light2.toggle and let's check that light Number 1 is now on. Light Number 1 is on and it's now on. Great, it works. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. This concludes our additional abstraction practice. 10. Concept: Inheritance: Welcome to another conceptual lesson, inheritance. To understand inheritance, we need to understand an "is a" relationship. For example, ice cream is an example of food. We know this because you can eat food and eat ice cream. However, ice cream can also have some extra methods that other food may not, like melt. Because ice cream is an example of food, we define ice cream to be a child class of food. We can also say that ice cream's subclass as food. We call food the parent class. Defining this "is a" relationship gives us an interesting benefit called inheritance. Now, here's how inheritance works. For all food, we expect to have the eat method. Since ice cream is a child class of food, ice cream automatically inherits the eat method. You can add as many child classes of food as you want and all of them will inherit the same eat functionality. Let's repeat this analysis with another example. An ice cream truck is a truck. In particular, an ice cream truck has all the properties a truck has and more. As a result, we define ice cream truck to be a child class of truck. This gives us a nifty benefit. We expect all trucks to have the drive functionality or method. Since ice cream truck is a child class of truck, it inherits the drive method too. Finally, ice cream truck can have some additional methods that other trucks don't, like order. In summary, A is a B if A has all the properties that B has; for example, ice cream is a food. Since ice cream has all the properties of food, this means that ice cream inherits all of food's functionality. By default, eating ice cream is like eating any other food. However, what if that's not true? What if eating ice cream is drastically different from eating other food? In other words, we want to override the default inherited functionality. This is our next concept in inheritance, overriding. Previously, we said that ice cream would inherit the eat method from food since ice cream is a food. However, let's say the ice cream eating is unique and unlike general food eating. We would need to override the eat method, defining a custom eat method for ice cream specifically. We might want to override eat because we represent the amount eaten in different ways. With ice cream, we measure amount left in scoops. With other food, we use a percentage. In summary, the child class inherits method from a parent class by default. However, the child class can override or redefine those methods. These two concepts are the core takeaways. We'll cover the last two bonus concepts after a brief summary of what we've learned so far. Our first concept, the "is a" relationship, tells us which classes subclass each other. Since ice cream is food, ice cream subclasses food. Ice cream then inherits methods from food. However, we can also have ice cream override inherited methods from food, if need be. For a copy of these slides and more resources, make sure to check out the course website, and that concludes our lesson on inheritance. There were a lot of key ideas in this lesson, but the most important takeaways or the "is a" relationship and overriding. Don't worry if the details are muddy. We'll make these ideas more concrete in the next lesson, when we begin coding. We'll now cover two more brief bonus concepts. If you're feeling overwhelmed or don't want bonus content right now, feel free to skip to the next lesson. An interface, like the food interface on the right, simply outlines expectations but doesn't provide default implementations. Here, food specifies an eat and an add method, but doesn't actually implement either. Ice cream implements the food interface with usable implementations for both methods. Our last concept is an abstract. An abstract also defines expectations for a subclass. However, it may implement some methods and leave other methods unimplemented. Here, the food abstract implements the eat method and specifies an add method without implementing it. Ice cream subclasses the food abstract and fills in the add method implementation, and that concludes the lesson on inheritance, including the bonus concepts. In the next lesson, we'll begin coding some of these inheritance concepts. 11. Practice: Deluxe Ice Cream Truck: To practice Inheritance, we will make a DeluxeIceCreamTruck. This DeluxeIceCreamTruck, gives you one free scoop with your ice cream order. Navigate to this URL to get started. Once you see this page, fork the project to get your own editable copy. To do this, click on the project name on the top-left to get a drop-down. Click on the "Ellipsis" to get another drop-down. Finally, click on the "Fork" button. Then, I suggest placing your Skillshare and repl.it windows side-by-side as shown here. You'll jump right into writing a subclass. Here's the format. We have our normal class definition, except we include the parent class in purple in parentheses, like we see here. In this example, ice is the child class and H_2O is the parent class. Let's use this format for our DeluxeIceCreamTruck now. After forking the other repl.it, you should see the code like mine on the right. Underneath all of the existing code, we're now going to define our DeluxeIceCreamTruck. First, create DeluxeIceCreamTruck class, then subclass ice cream truck, as we discussed, type in class DeLuxeIceCreamTruck, and subclass the ice cream truck with a colon. We can add pass here as a placeholder. Now, you'll need to override the order method since the DeluxeIceCreamTruck offers a free scoop with each order. This is the format for defining a method. Overriding a method doesn't take any special syntax. Simply redefine the method in the child class. Here we're going to define your order method. Delete the pass and define order. Again, you need self as the first argument and it accepts one argument, scoops. Like before, we create a new ice cream. Then we call the self.add method, and pass in number of scoops. But wait, this is exactly like before. This is your red flag. Like we've been saying, redundant code means something is wrong. How can we reuse our original order method up above? We're interested specifically in calling our parent class method. Here's how to do that. To call the parent class's method, just use the super instance. Let me show you in code now. We're going to call super.order and pass in the scoops argument. Here we'll instead of these two lines type in super.order and pass in scoops. Here, this calls the parent method order. This method returns the ice cream instance. So set that instance to a variable. Here we'll have ice cream equals super.order. Then we'll add a free scoop of ice cream without updating the number of scoops sold. Here we'll write, ice_cream.add1. In case you're wondering, why don't we use the ice cream trucks add method? The add method that was inherited from the parent class. However, we don't use the trucks add method, because the trucks add method also updates the number of scoops sold. In this specific example, we are giving away a free scoop of ice cream and do not want to update a number of scoops sold. That's how we have ice cream.add1. Finally, return the ice cream to the customer. One quick note. One good practice is to always invoke the parent class constructor in your own constructor. We'll do this now for other constructors now. Here in the constructor for ice cream truck, we'll call super.init. Then we'll repeat this one more time for the ice cream class. For the last segment of this lesson, use the DeluxeIceCreamTruck instance. We're going to click on the green button at the top to run our code. First, instantiate the DeluxeIceCreamTruck. Here we'll have truck is equal to DeluxeIceCreamTruck. We'll then order an ice cream with two scoops, ice cream is equal to truck.order2, and boom, the ice cream we get back now has three scoops. It includes the one free scoop we added. We can check by writing ice_cream.scoops, and we have three scoops. We can also check how many scoops the ice cream truck has sold. Here we'll type in truck.sold, and we only expect two. Just as expected, we get two. The truck only sold two scoops, and the third scoop was free. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. This concludes the DeLuxeIceCreamTruck practice for Inheritance. The next lesson is a bonus practice lesson to build familiarity with these ideas. 12. (Bonus) Practice: Flickering Light: In this lesson, we'll practice inheritance once again. This time, we'll make an old light that flickers every other time you turn it on. Navigate to this URL to get started. Once you see this page, fork the project to get your own editable copy. To do this, click on the project name in the top-left to get a drop-down. Click on the "Ellipsis" to get another drop-down, and finally, click on the "Fork". Then I suggest placing your Skillshare and Repl.it windows side-by-side as shown here. We'll hop right in subclass our original light class. Here's the format for defining a subclass. In your code, you should see a light class that we defined in the previous lesson. Scroll down to the very bottom of your file. Here we'll define an old light which subclasses the original light class. We'll type in pass here for now. You'll now override the toggle method in the parent class since our light needs to flicker every other time the light is turned on. We'll now define our constructor which sets flicker to false. This means the default for all lights will be to not flicker starting off. Define the constructor with the self argument with sync equals to none. Recall that this default argument was in our original classes constructor. We'll then need to write on is equal to false. Just like before, we'll write sync is equal to sync. These two lines look redundant, and you're right, they are, this should be a red flag, we'll adjust this in a second. Finally, let's set flicker to false, that way the light is not flickering by default. You'll now override the toggle method in the parent class, since our light needs to flicker every other time the light is turned on. Here's the format for defining a new method. Right underneath our constructor, we'll now define a toggle method. Like before, we need to change the light from on to off or from off to on. We'll write here self.on equals to not self.on. However, we then need to check for the synced light. If self.sync is not none this should seem fishy. We have redundant code. This is a red flag. This code simply repeats the parent's toggle method. The solution is to call the parent method. We need to call the parent method. Here's how to call the parent method. We simply use the super instance as a substitute for our parent instance. Back in your code, we're going to actually delete these two lines and instead write super().toggle(). Before we continue, we'll need to actually call the parent constructor from every other classes constructor. Here, we need to call the parent constructor. The parent constructor actually takes in a sync argument. In the original light, we'll also call its parent constructor. Now, we can finish the toggle method. We check if the light is turned on. If it is, toggle light flickering or not, that way every other time the light is flickering. If the light is on, then we change whether or not it's flickering. This completes our old flickering light. Finally, let's use this old light. Go ahead and click the green arrow on the top to run your code. Then instantiate your old light. Light is equal to old light(). Check if it's flickering or not, light.flicker. Indeed, it's not flickering. Let's now turn on the light. Let's check if the light is now flickering. It should be and it in fact is flickering. Let's turn the light off then on again, and let's check again if the light is flickering. This time, it should not be. Indeed, it is not flickering. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. This concludes our practice for the inheritance concept using old flickering lights. In the next lesson, we'll talk about a mysterious error in object-oriented programming, why it happens, and how to fix it. 13. (Bonus) Mystery: MRO: In this lesson, we'll cover mysterious error in Python. This error is due to a concept called method resolution order. As before, note these mysteries addressed some of the most confusing parts of OOP. If you're feeling overwhelmed, I suggest skipping these mystery sessions on the first pass to this course, revisiting them on a second pass. Just like before, navigate to repl.it/languages/python3, as we'll be starting from scratch. Then I suggest placing your Skillshare and Repl.it windows side-by-side as shown here. First, let me explain a concept called multiple inheritance. Before, we saw that one class may subclass another class. For example, ice cream could subclass food, because ice cream is a type of food. What if ice cream is also a drink? Because the ice cream could melt? Then we may want ice cream to subclass both food and drink. Let's see what this looks like in code. This is the format. Here, class C subclasses both A and B. To read this, read from left to right. Methods in C can override methods in A. Methods in A can override methods in B. Just to keep track, we'll summarize this as A greater than B, meaning A takes precedence over B using this notation above. Let's now introduce the mysterious error. Here's the format again. Let's code it. Create class A with a placeholder body. Then create a second class B, which subclass is A. Class B subclassing A and a placeholder for the body. Finally, create a third class C, which subclasses both A and B. Make sure to write your classes in exactly this order. A, B, then pass for your placeholder. Now attempt to run your code using the green arrow, run at the top. Here's the mysterious error, method resolution error. What does this mean, and how did this happen? Let's break this down. To understand this error, we'll need to visualize it. First, B subclasses A on lines 4 and 5. B takes precedence over A. We denote this as B greater than A. However, C subclasses both A and B, as we wrote here. According to our notation, A, B, this means that A takes precedence over B. We'll denote this as A greater than B. This is the contradiction. B is greater than A and yet A is greater than B, so which one, A or B takes precedence? This is what Python is complaining about. It doesn't know which class takes priority. The natural next question is, how do we fix this? Re-examining the visualization we saw earlier, we can really remove any of these errors to fix the problem. But one of these errors is easier to remove than the others. We can redefine A and B so that B doesn't subclass A. This takes a paradigm shift in how we understand inheritance. In the next lesson, we'll discuss exactly what that paradigm shift is. For now, for a copy of these slides and the finished code for this lesson, make sure to check out the course website. This concludes a method resolution order mystery. We've discussed when the error happens, why it happens, and introduced one way to fix the problem. In the next lesson, we'll cover this fixed in more detail. 14. Concept: Inheritance+: The goal of this lesson is to put you ahead of the curve to make your code far more flexible than others. There are two concepts that will deepen your object-oriented programming understanding, mixins and composition. Previously, we talked a lot about the "is a" relationship in inheritance. A common paradigm is to say, for example, that a bus is a vehicle, is transportation. But what happens when you have a bus that's been converted into a home? It's still a bus, but it's not really a vehicle anymore. In this lesson, we'll refine our understanding of inheritance in object-oriented programming. To understand why we need refining, let's dive into the circle-ellipse problem. This section will require you to think hard and depends heavily on your understanding of the previous lessons. If you need to, grab water or take a stretch break first. First, recall from school that a circle is an ellipse. To jog your memory for why this is: an ellipse has two axes, where each axis can have a different length. A circle is a special case where both axes are the same length. So a circle is a special type of ellipse. We say that a circle is an ellipse. However, recall our "is a" rule from the inheritance lesson. A is a B if A has all the properties that B has. Plugging in what we know, a circle is an ellipse, since a circle has all the properties than ellipse has? Hold up, that seems really wrong. Circles don't have all the properties an ellipse has. A circle only has one axis length; an ellipse has two axes lengths. If you really think about it, an ellipse has all the properties that a circle has. Therefore, an ellipse is a circle. So our teachers are wrong? Turns out this isn't right either. A circle has a radius, but an ellipse does not. Ellipses don't have all the properties of a circle either. So I suppose a circle is not an ellipse, and an ellipse is not a circle, at least according to the "is a" relationship in inheritance. Our solution is to use a different paradigm called mixins. I think of this as the "can" relationship. Let me show you what I mean. Here are two classes, person and dog. Beneath both classes, I've listed their methods. We'll start by using inheritance. Our natural parent class is living thing, which defines move and grow by default. Here, the gray text under person and dog means those two classes inherit those methods. However, what if we add a car class? A car is not a living thing, but it can move. Let's create another parent class for classes that can move. This new class is called movable, and both living thing and car inherit from it. Great. But what if we had a plant class? It's a living thing, but it doesn't move. There's no clear hierarchy where this will work. So instead of building a hierarchy using inheritance, let's consider what each class is able to do. Each parent class instead defines a property of the object. Here, we have a parent class for all classes that can grow, called Growable, in pink. We also have a separate class, called Movable, in blue. Each parent class defines a new behavior that the subclasses can do. A plant can grow, a person can move and grow, and so on and so forth. In summary, A can B if a has the ability B; for example, a person is growable since a person can grow. The official name for these ability-based classes are mixins, which greatly improve the flexibility of your code. You can mix and match any number of different abilities for each new class you define. Another alternative to the "is a" relationship is composition, where we nest objects instead of inheriting objects. Composition is summarized by "has a" relationships, like how a car has wheels. I believe composition has more nuance than most online articles cover, but we'll stop here for now because I feel the nuance will be more confusing than helpful. We'll cover the nuance of how composition replaces inheritance in a later class, post or video. Our final concept in this lesson is polymorphism. In short, polymorphism is when several classes all implement the same interface. In other words, a set of classes all satisfy the same expectations. For example, we may implement multiple animal classes, like monkey and dog, that all have grow and move methods. Say you have a piece of code that only relies on the grow and move methods, then the cool thing is you could run that code on any animal class you have. That concludes our concepts for this lesson. In short, we covered the circle-ellipse problem to unveil a flaw in "is a" relationships. We then covered two alternative types of relationships, "can" and "has a" relationships, to improve the flexibility of your code. Finally, we briefly discussed a benefit of object-oriented programming, which is polymorphism. For a copy of these slides and more resources, make sure to check out the course website. This concludes our advanced inheritance lesson, leveling up your object-oriented programming understanding. This was a challenging lesson, so if you didn't quite follow, that's okay. The tips and takeaways from this lesson will make more sense as you begin to use object-oriented programming and begin to run into design flaws yourself. In the next lesson, we'll implement mixins. 15. Practice: Melting Ice Cream: In this lesson, we'll implement melting ice cream. In particular, we'll use mixin concept we learned to create a melting ice cream we can both eat and drink. Navigate to the following URL to get started. Once you see this page, fork the project to get your own editable copy. To do this, click on the project name in the top-left to get a drop-down. Click on the ellipsis to get another drop-down, and finally, click on the Fork button. Then I suggest placing your Skillshare and Repl.it windows side-by-side as shown here. Let's start by visualizing the goal. Right now, you only have an ice cream class. Our goal is to add both lemonade and melting ice cream. We know that melting ice cream should subclass ice cream. In the spirit of mixins, we'll add a drinkable class, which adds the ability for a class to be drunk. Here, both lemonade and melting ice cream can be drunk. Highlighted in red are the classes we'll implement. Here's one thing to note, however. Note that ice cream and drinkable both have the add method. We'll need to take special care when using multiple inheritance from melting ice cream. Melting ice cream needs to subclass both drinkable and ice cream, but give precedence to ice cream. You'll now create these classes one by one. On the right-hand side, you'll see the code that we got from previous lessons. We can find the ice cream class by scrolling down. We can also find the ice cream truck class, and finally, the deluxe ice cream truck class. At the very bottom after that class, let's now add our mixins. We're going to create our drinkable class. In the constructor, set the number of cups to zero. We will measure how much of the drink is left in cups. Here's our constructor with self as an argument, we'll define cups equal to zero. Next, create an add method that adds a certain number of cups to the drink. You have def, the add method with this self argument, then the number of cups to add. Then we'll increment the number of cups by this amount. Finally, create a drink method that subtracts a certain number of cups from the drink. Here we'll have the drink method, which takes a certain number of cups and deducts that number of cups from the total. Now let's navigate back to the slides. For your reference, here's the diagram for what we're building out. Now create the lemonade class. Again, for your reference, here's our diagram. Underneath your existing code, let's create a new lemonade class. This lemonade class will inherit from drinkable. This lemonade class will subclass drinkable. The lemonade class then inherits automatically all of the add and drink methods. Now for the final piece, you'll implement melting ice cream. Again for your reference, here's our diagram. Create a class, melting ice cream that inherits from both ice cream and drinkable. Here we'll have melting ice cream which inherits from both ice cream and drinkable. Notice that the ordering matters. By writing ice cream first, we enforce that ice cream's add method takes precedence over drinkable's add method. We'll now add an elapse method. This method will update the ice cream's number of scoops of unmelted ice cream and cups of melted ice cream over time. This method takes time elapsed as an argument. Here we'll write elapse(self), the amount of time that has elapsed, and first compute the number of ice cream scoops that have melted. This is either the amount of time that has elapsed or the number of scoops left; whichever is smaller. Here we'll compute melted is equal to the minimum of either a time elapsed or the number of scoops left. Then subtract the number of melted scoops from the total number of scoops. Here we'll have scoops minus equals melted. Also, add the number of melted scoops to the cups of melted ice cream. Here we'll have cups plus equals to melted. That completes our melted ice cream class. The add, drink, and eat methods are all inherited from the two parent classes. Now you'll use your brand new melting ice cream class. Click on the green arrow at the top to run your code, then instantiate your melting ice cream. Add some scoops, and then elapsed time to see how much has melted. Here we'll write ice_cream.elapse(2). Let's see how many scoops are left. We expect two scoops of ice cream to melt so only one scoop of ice cream should be left, and indeed we see one. Now let's see how many cups of melted ice cream are left. Ice_cream.cups and we expect two, just like our results say. Let's now drink some melted ice cream. Ice_cream.drink, and let's drink one cup. Let's now check how much melted ice cream is left. Ice_cream.cups, as expected, we only have one cup left. That's it. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. This concludes our melted ice cream practice. Next up is additional bonus practice for mixin. 16. (Bonus) Practice: Timed Lights: In this lesson, we'll practice more of the mixin inheritance concept using light switches. In particular, we'll implement timed lights, which turn off after a certain period of time. Navigate to the following URL to get started. Once you see this page, fork the project to get your own edible copy. To do this, click on the project name in the top left to get a drop-down. Click on the "Ellipsis" to get another drop-down, and finally, click on the "Fork" button. Then I suggest placing your Skillshare and Repl.it windows side-by-side as shown here. First, we'll visualize our plan. We have a light already. We will now create a TimerLight, which subclasses both a timer and a light. Note that we didn't create a generic device class that contains all these abilities. We instead it created separate timer and light classes, where each represents an ability like a mixin. Highlighted in red, are the new classes we'll need to create. Let's start with a timer class. Here's our diagram of what we're creating. After forking your code, your right-hand side should match mine. If I scroll down, you'll see the light class that we created in previous lessons, as well as the old light. At the bottom of your file, we're now going to create a timer. Here we have class timer. In our constructor, we're going to set the time left to zero. Define your constructor, and inside the constructor, we'll define the time left to be zero. Then define a set method which allows you to start the timer. We'll define set, which takes in an argument with how much time left. Here, we'll set a time left to the time requested. We then define a method ring to ring when time is up. We'll define ring, which takes no additional arguments and will print, "Timer is up." Finally, we'll define an elapse method, which elapses the timer for some amount of time. Here we'll have defined elapse, which takes in self as its first argument and then the time elapsed. First, check if there's any time left in the timer. If self.left is greater than zero, then subtract the time left. Here we have the time left is equal to the time left minus the time elapsed. However, we need to make sure that this quantity is not negative. In other words, if the time elapse is greater than the time left, it might be negative. Let's write here, max with zero, and this ensures that the time left is always non-negative. Finally, if there's no more time left in the timer, then we should ring. If self.left is equal to zero, then we should call our ring method. Now, we should write our TimerLight class. Back inside of your code, we're going to create a TimerLight class. Here we'll have TimerLight, which inherits from both the light and the timer. The first thing we'll do, here's our diagram of what we're creating. Create your TimerLight class, and subclass both light and timer. Here we'll have TimerLight, with subclasses both light and timer. Our first step will be to override the set method. Define set, which takes in the time left. The first lesson here is to respect abstraction barriers. We'll call the parent classes, set method. Now, if there's any time left, turn on the light. If self.left is greater than zero, we should automatically turn on the light. We should also override the ring method. Here we'll define ring, which takes in no additional arguments, call your parent ring method, so we can ring the timer, and then turn off the light, since the timer only rings when time is up. This concludes our TimerLight. Finally, we'll use our TimerLight class. Click on the green button at the top to run your code, then instantiate your timer. Here we'll start the clock with five seconds. Timer is set, five. Then let's elapse three seconds. No ringing should occur and none does. Then we need to elapse three more seconds. This time, it should ring. Furthermore, the time left should be zero. Let's check timer.left is indeed zero and not negative. Next, let's check our TimerLight. I'm going to click on this X button to clear my output. Now, instantiate your timer class. Timer_light equals to TimerLight. Do the exact same thing. Start the clock with five seconds, timer_light dot set with five. The time light should now be on. Let's check, timer_light dot is_on, and indeed the light is on. We should then elapse three seconds. Timer_light dot elapse(3). The light should still be on, so let's double-check. Timer_light dot is_on, is true. Let's elapse another three seconds. We expect a ring and the light off turn off. Timer_light dot elapse, three seconds, that timer is up. That's our ring, and let's check if the light is on or not. Timer_light dot is_on. There we go. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. That concludes our timer light practice. Next up, is another mysterious bug related to inheritance, which you may encounter. Let's see what that mysterious error is and how to deal with it. 17. (Bonus) Mystery: Fragile Base Case: In this lesson, we'll explore phenomenon called Fragile Base Class. This stranger causes and how to work around this. Just like before, navigate to repl.it/languages/python3. Then I suggest placing your Skillshare and repl.it windows side-by-side as shown here. First, let me introduce the Error. Let's define a class A. Inside of this class, we'll define a method hi, which prints hi, and print hi. Will then define a method hello, which also prints hi. Will then define a class B, which subclasses A. We then override the method hi to instead call hello. Now this itself will not create problems. Let's go ahead and see how to use these. We're going to hit "Run" at the very top. Then instantiate class B. We'll call b.hi and no problems. Let's now create the problem in class A. Say we noticed the redundancy issue. Method hi and hello do the exact same thing, so we decide to have the method hello simply call a method hi. Let's update the method hello to simply call hi. Let's now try this again. We're going to hit the green arrow at the top to run your file, then we'll type in b is equal to B to instantiate, then we'll call b.hi and wow. We got this recursion error, maximum recursion depth exceeded. What happened? Well, class B inherits the method hello, which calls hi. Class B also overrides the method hi to call hello, so hi calls hello and hello calls hi. These two methods, hi and hello, keep calling each other infinitely and the error at the bottom here, maximum recursion depth exceeded, means the code ran a bajillion times until the computer couldn't handle it anymore. The fix is simple in this case, simply don't redefine hello to call hi. However, in a more complex system, this becomes hard to check for and enforce. Unfortunately in Python, there aren't any good built-in solutions for this. But in other languages, one solution is to mark methods as final and impossible to override. In this case, we could have marked the method hi as final. For a copy of these slides and the finished code for this lesson, make sure to check out the course website. This concludes the Fragile Base Class mystery and their inheritance plus section. In fact, we finished all new content for the course. In the next lesson, we'll summarize your takeaways, layouts some next steps, and describe bonus content. In the next lesson, we'll summarize your takeaways and layout some next steps. Congratulations on making it this far. This was not an easy course and you've done well. 18. Conclusion: Congratulations on building your very first simulation with object oriented programming. We covered a large number of concepts. Concepts like classes, instances, to general paradigms like abstraction. That's a lot of material. But don't worry, you can always relook up the syntax if you forget it. Most important takeaway, what you can't easily look up is the object oriented programming paradigm. How to think of entities in the world around you as a series of objects interacting with one another. How to use abstraction for readability, inheritance for maintainability, and mixings or composition for flexibility. If you forget these specific terms, that's okay. However, if you find yourself writing inflexible, unmaintainable, or unreadable code, then it may be worth it to actually revisit some of these lessons to regain the intuition and practice these ideas. I suggest adding a brand new feature to your simulation, maybe add deserts, other deserts, do ice cream truck, then link to and share your project in the projects and resources tab. I can't wait to take a look. Also, now that you've just learned OOP, you probably have a different and very unique way of explaining your takeaways from this course. If you do have that in mind, don't hesitate to post in the discussion part of the Skillshare course. If this has piqued your interest and you're looking to learn a little bit more, there are a few next steps. Check out the additional resources at alvinwan.com/oop101. Mastering OOP is unique and that you don't need the right answers. Instead, just practice designing very complex systems in OOP. Your aha moment will come when two conditions are met. First, you realize that your code is hard to read or hard to maintain. Second, you revisit this course, apply those tips and realize how it makes your code easy to scale, easy to extend, or easy to read. Sure, it's still soft discovery, but instead of trying 100 different ways, I've simply passed onto you the right answer that geniuses before me have come up with. To learn more coding beyond OOP, you can try My Computer Vision 101 class to dabble in computer vision, MySQL 101 class to start design databases, or my Data Science 101 class to start playing with data. I've also designed this course to be self-contained and relatively short. There's more content coming to help you ace coding interviews and grow into an advanced Python developer. If that sounds interesting, go to my Skillshare profile and click follow to get notified when the next class launches. Congratulations once more in making it at very end of the course, and until next time.