Methods and encapsulation
About this video
A method is a function defined inside a class, called on an object with dot syntax, and Python automatically passes that object as the first parameter, self. Using a BankAccount class with get_balance, deposit and withdraw, the video explains encapsulation: an object manages its own data so outside code uses well-defined methods that validate changes.
You'll be able to
- define a method inside a class with self as its first parameter.
- call a method on an object and explain what Python passes in automatically.
- explain encapsulation and why methods protect an object's attributes.
- add validation logic inside a mutator method.
Key words
- method
- A function that belongs to an object, defined inside a class.
- self
- The first parameter of a method, which holds the object the method was called on.
- encapsulation
- An object managing its own data, so outside code only changes it through well-defined methods.
- behavior
- What an object can do, like deposit or withdraw money.
Transcript
- [Instructor] We can use objects to store data, but their true power comes from bundling data and behavior together. By behavior, I mean that an object can do things. It can act on the data it stores. To define an object's behavior, we just need methods. A method is a function that belongs to an object, meaning it's a function that's defined on a specific data type.
Typically, we use methods to access or update an object's attributes. But instead of passing the object as an argument to the method, we call the method on the object. Just like we use dot syntax to access an object's attributes, we also use dot syntax to call an object's methods.
Let's consider a BankAccount type. A BankAccount might have the attributes owner and balance. What actions can we take on a BankAccount? Well, we can get the current balance, deposit money, or withdraw money. We can achieve all of those behaviors by directly working with each account's balance, but it's easy for a programmer to make a mistake here.
They might forget to round the balance to two decimal places or withdraw more money than there is. To solve for this, we think through those requirements upfront and then wrap that common logic inside a function. We call this concept encapsulation, where an object manages its own data.
Outside code doesn't modify an object's attributes directly. It only interacts with the object through those well-defined methods, thus protecting the object's attributes from invalid changes. That's the theory of methods. Now, let's take a look at the actual syntax.
We have a class definition for a BankAccount type with an owner and a balance attribute. We wanna guarantee that whenever we access an account's balance, it's always rounded correctly. So, we can write a function that takes in a BankAccount object. Let's call that parameter self, and returns its rounded balance.
Now, here's the method magic. Notice that we indented this function definition inside of our BankAccount class definition that attaches this function to the BankAccount type, thus making it a method. When we call a method on an object, Python automatically passes in that object as the first argument.
Just like with our __init__ method, the first parameter is always the object we're acting on, and by convention, we always name that parameter self. This, once again, is just a convenient syntax shortcut. Python knows that checking is a BankAccount type, so when we call get balance on checking, Python knows to look for that get_balance() definition inside the BankAccount class.
We can tell Python that information explicitly. We can say, "Go to the BankAccount class and find the get_balance() definition." And then we manually pass in that checking object. These expressions are equivalent. They do the exact same thing. They get the balance of the checking object.
This first one is just a bit easier and it doesn't require that we know where the BankAccount class lives, so programmers prefer this syntax of calling the method on the object. Methods can access and return data, but they can also mutate it. I can write a method deposit() that, as always, first takes in the object and then an amount.
Inside the method, I'll just add that amount to the object's balance attribute. And then I'll add another method, withdraw(), that subtracts an amount from an object's balance. When we call those methods on the checking object, it automatically gets passed in as that first argument, so we only need to pass in a value for amount.
The real value of these methods is that we can add more logic around these updates, so let's make sure no one deposits a negative amount and no one withdraws an amount greater than their current balance. If other programmers play by the rules and only access balance through these methods, we can guarantee that this validation always runs before balance gets updated.
That's the power of encapsulation. We define all of the rules for a type inside the class definition, what data it stores in the attributes, and how that data can be accessed and updated in the methods. Then we can use those methods to safely interact with objects of that type, thinking in terms of the behaviors deposit and withdraw instead of the details of how the code and attributes work.
Video © Khan Academy, used under CC BY-NC-SA. Watch on Khan Academy. Puzzicle is not affiliated with Khan Academy.