×
By the end of this chapter, you should be able to:
In Python 3, everything is an object! We can examine what kind of object using the type()
function. We've seen examples of this with built-in data types like booleans, strings, and integers. In this chapter, we'll create our own data types, in a way, using classes. Any instances of that class that we create will have a type equal to that class.
Object oriented programming is a method of programming that attempts to model some process or thing in the world as a class or object. Conceptually, you can think of a class or object as something that has data and can perform operations on that data. With object oriented programming, the goal is to encapsulate your code into logical groupings using classes so that you can reason about your code at a higher level. Before we get ahead of ourselves, though, let's define some terminology and see an example:
Class - A blueprint for objects. Classes can contain methods and properties. We commonly use classes to reduce code duplication.
Instance - objects that are made from a class by calling the class. Instances share a similar structure because they all come from the same blueprint (i.e. class).
Say we want to model a game of poker in our program. We could write the program using a list to represent the deck of cards, and then other lists to represent what each player has in their hand. Then we'd have to write a lot of functions to do things like deal, draw cards, see who wins, etc.
When you end up writing large functions and lots of code in one file, there is usually a better way to organize your code. Instead of trying to do everything at once, we could separate concerns.
When thinking about a game of poker, some larger processes and objects stand out that you will want to capture in your code:
Each one of these components could be a class in your program. Let's pick one of the potential classes and figure out the data that it will hold and the functions that it should be able to perform:
Deck of cards
Now that we can conceptualize how a problem can be broken down into classes, let's talk about why programming this way can be useful.
Encapsulation is the idea that data and processes on that data are owned by a class. Other functions or classes outside of that class should not be able to directly change the data.
In our deck class, we have 52 cards. The player class should not be able to choose any card he or she wants from the deck or change the order of a deck manually. Instead a player can only be dealt a hand. The contents of the deck is said to be encapsulated into the deck class because the deck owns the list of cards and it will not allow other classes to access it directly.
Abstraction is the result of a good object oriented design. Rather than thinking about the details of how a class works internally, you can think about it at a higher level. You can see all of the functions that are made available by the class and understand what the class does without having to see all of the code or worry about implementation details.
Continuing with our example, if you had a deck of cards class and you saw that you could call the .shuffle()
function or the .deal()
function, you would have a good understanding of what the class does without having to understand how the functions are working internally.
Other hallmarks of object oriented programming include inheritance and polymorphism. We'll discuss these later.
class Vehicle: def __init__(self,make,model,year): self.make = make self.model = model self.year = year
Every class needs an __init__
method. Every time you create an instance from a class in Python, that instance will get run through the __init__
method. self
inside of this method refers to the instance.
To create an instance from a class, we instantiate the class using () and pass in the values to initialize the instance (these are defined in __init__
).
v = Vehicle('toyota', 'corolla', 2012)
To add methods on instances, we simply define them in the class. When defining instance methods, the first argument should always be called self
, and refers to the current instance. If you need to pass other arguments, do so after passing in self
. Note that when you call these instance methods, you never pass in self
. Here are a couple of examples:
class Person(): def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name def full_name(self): return f"My name is {self.first_name} {self.last_name}" def likes(self, thing): return f"{self.first_name} likes {thing}!" p = Person('Tim', 'Garcia') p.full_name() # My name is Tim Garcia p.likes("computers") # Tim likes computers!
Notice that we must add self
as the first parameter to each of our instance methods.
We've seen how to add methods on instances. But what if we want to add a method on the class itself? To do this, we use a decorator! We'll talk more about decorators later. For now, it's enough to know that there are two decorators we can use to create class methods:
@classmethod
One way use to use the @classmethod
decorator:
class Person(): @classmethod def say_hello(cls): return "HI!" Person.say_hello() # "HI!"
Similar to instance methods, the first argument in a class method has special meaning. For an instance method, the first argument refers to the instance, and is called self
. For a class method, the first argument refers to the class, and is typically written as cls
.
@staticmethod
Another decorator we can use is the @staticmethod
decorator:
class Person(): @staticmethod def say_hello(): return "HI!" Person.say_hello() # "HI!"
So what are the differences between these two? See if you can spot it and head over here after!
When you're ready, move on to Inheritance and MRO