Objects

Object Oriented Programming sounds like a new scary thing, and it does have some complications to it, but in general, it is more of what we've been doing all along with another layer over it. A few vocabulary words:

  • property This is a variable that exists within the scope of the object.

  • method This is a function that exists within the scope of a method.

Okay, keep those terms in mind. An Object is a named entity that collects properties and methods in a meaningful and abstract way. Let's think about an example in the non-programming sense. Think of a car. It has properties (number of doors, color, number of seats, number of wheels, etc.). Each of these properties is general to all cars (all cars have doors, color, seats, wheels, etc.) but also a specific value for your car. Perhaps it is a two-door coupe. Maybe the color is red. Maybe the car has room for 5 and 4 wheels. My car might have different specific values for each of those properties, but it has those properties. In fact, there is a certain set of properties that a car must have for it to be a car. Likewise, the car has stuff that it does or actions that it performs. These actions are the methods. So, my car accelerates, it stops, it opens its doors, and it honks its horn. These actions are the methods of the car. Once again, each specific instance of a car might return different values for the methods, car horns all have different sounds based on the properties of the car, but they all can beep their horn.

In Object Oriented Programming, we look at our programming tasks and we consider if there are collections of properties and methods that we could combine. Let's take our car example and turn it into Python:

class Car:
   '''
   This docstring describes the object. Not required but useful.
   '''
   year
   price
   capacity
   mpg

   def get_year(self):
      return self.year

   def get_price(self):
      return self.price

   def set_price(self, price):
      self.price = price 

   def get_capacity(self):
      return self.capacity

The first line is the syntax for defining a class. Then within the class block we have our list of class properties. These are accessible by any method within the class and directly from outside the class, but it's better to provide methods for getting their values. Finally, we have some methods that access the properties. So, once I have created a particular instance of a car, I can use the get_year method to get the particular year of this instance. More on that in a minute. Let's build out our object a little more:

class Car:
   '''
   This docstring describes the object. Not required but useful.
   '''
   year: int
   price: int
   capacity: int
   mpg: int
   horn: str

   def __init__(self, year, price, capacity, mpg, horn):
      self.year = year
      self.price = price
      self.capacity = capacity
      self.mpg = mpg

      if horn == "":
         self.horn = "beep"
      else:
         self.horn = horn

   def get_year(self):
      return self.year

   def get_price(self):
      return self.price

   def set_price(self, price):
      self.price = price 

   def get_capacity(self):
      return self.capacity

   def make_noise(self):
      print(self.horn)

Notice that the class has the following properties: color, horn, price, capacity, and miles per gallon. Also called properties, they are all variables that will eventually hold values. Since they are variables, any instance of the class could have different values assigned to the attributes. How does this work? We begin by instantiating the class:

The Constructor and Instantiation

The first thing to notice is that I added a special method called init. That method is the constructor. Each class, by default, has a method called a constructor. This is the function that is automatically called when we create a new instance of the class (see line 41 above). The constructor initializes that instance of the class in memory, or it creates a space to store a Rectangle of n-width and n-length, plus all of the methods. If you don't include a constructor, it simply creates space for that object in memory without any parameters. The constructor is a good place to create initial or default values for the properties. In other words, the constructor gives the object state by creating an instance of it in memory. If I created 100 new Rectangle objects, each one would have its own, unique state even though they're built on the Rectangle class template.

You'll notice the constructor method in python is init(), which initializes the object. The key is the moment you create an instance of the class, this constructor is run.

my_car = Car(2018, 35000, 6, 37, "honk honk")

This will create an object that is based on the Car class. This means that the object will have all of the properties and methods defined by the car class. You'll notice that I pass a number of parameters to the Car class as I create (or "instantiate") the object. These parameters are given to the constructor. In Python the constructor method is called init(). A good way to remember init is by thinking it stands for initialize. Whenever you create a new instance of a class (or make an object based on the class), the init() method is called automatically on whatever you pass in as you create the instance. So using the example of my_car above, the init method does this (Note: you don't actually code this. It happens automatically. I'm giving you this to illustrate what's happening):

The variable my_car now holds a Car() object that has its properties set to the above values by the constructor.

The cool thing about classes: I can create loads of instances without much extra work:

my_other_car = Car(2012, 20000, 3, 23, "Beep")
my_other_other_car = Car(1999, 2000, 3, 19, "Chirp")S

Each of the new objects is constructed the moment the Python interpreter reads the line initializing it. At that moment, the init() method is called to initialize the instance of the object.

dot-notation & visibility & self

dot-notation

You probably have already written Python code that uses dot-notation. For instance, when we want to make a string all lowercase, we simply call str.lower(), which is a method that returns the string with all of its characters lowercase. In this case, you're using dot-notation, which allows you to access a particular objects methods and properties. For instance, in the above example, if we wanted to access the mpg property directly, we'd simply write my_car.mpg. The first part tells us which object, and the part after the dot refers to a property. In the case of strings, every string is not only a series of characters, but it is an object (in Python). One of the methods that is part of every string is the lower() method, which takes the contents of the string and returns the contents in lowercase. To access that method, you simply use dot notation to call the method on the string object.

Whenever you use object oriented programming, using something like dot-notation is vital. For instance, when you create an object and instantiate it, the way you make use of that object (it's methods and properties) is to call them through dot-notation. If you are not using object oriented programming, then dot-notation is less significant, but since in Python, everything is an object, and most things have methods and properties, and most things can be subtypable (more on that later), you use dot-notation a lot.

visibility

This is not inforced in Python. If you have experience with a programming language like Java or C++, you probably have encountered public, private, and protected variables and methods. This denotes their visibility. Let me give you an example:

class Car {
   public:
      Cars(string color, int capacity) {
         this.color = color;
         this.capacity = capacity;
      }

      int get_capacity() {
         return this.capacity;
      }

      string get_color() {
         return this.color;
      }
   private:
      string color;
      int capacity;
};

int main() {
   Car my_car = new Car("blue", 4);
   cout << "The color is: " << my_car.get_color() << endl;
   // This next line will not work because color is private.
   cout << "The color is: " << my_car.color << endl;
}

I suspect this isn't too difficult to understand coming from Python. The various int's and string's are just defining the type of things. The thing to pay attention to is that there are private and public methods and properties. C++ (as well as Java) enforces the visibility of these. So, I cannot directly access the Car's color property because it is a private property. Instead, I have to use the public get_color() method. This is a security feature that is quite useful, and generally speaking it is good programming practice, especially if you are building something complex, to control how properties (data) is accessed. However, Python does not enforce private or public properties and methods. Everything is public. It has become conventional for Python developers to start the names of what they consider (or would like to consider) private with a single underscore _foo. Likewise, if you are building a system with complex inheritence (next page), you can precede a variable or function name with two underscores. This allows you to differentiate between the method in the parent class and child classes. This will make more sense when we talk about inheritance next.

self

The keyword self often leads to a good deal of confusion in Python. If you have experience with another programming language, like javascript or C++ or Java, you may have seen the keyword this. Python uses self instead. The keyword self refers to this particular instance of the class. For instance, let's look at a little class:

class Shape:
   sides: int
   color: str

   def __init__(self, color, sides): 
      self.sides = sides
      self.color = color

   def get_color(self):
      return self.color

   def get_area(self):
      return self.area

Notice that the class has two properties. One is the number of sides. The other is the color. Technically, these properties are in the scope of the class, but not necessarily in the scope of the methods. Also notice that each of the methods has self as a parameter. Basically, what's happening here is you are passing the instance of the object to each of these methods so that all of its methods and properties are accessible within each method. For instance, self.sides = sides means that we are assigning to the sides property of this instance of the object whatever value was passed in with the local sides parameter variable. self.sides is not the same variable as sides even though the latter's value is assigned to the former. Self is the way we donote this particular instance of the object. Conclusion

This has been a really brief introduction to objects. In the future, we will begin discussing the four major principles of object oriented programming:

  • Abstraction

  • Encapsulation

  • Inheritance

  • Polymorphism

These central concepts represent the major value and use of this programming paradigm. However, before we conclude, here are a few examples that you can run: