---
# System prepended metadata

title: Object Oriented Programming

---

# Object Oriented Programming

Python supports different kinds of data, each is an object, every object has:
- a type
- an internal data representation (primitive or composite)
- a set of procedures for interaction with the object
- an object is an instance of a type
    - `1234` is an instance of an `int`
    - `"hello"` is an instance of a string

## Object Oriented Programming (OOP)

**Everything in python is an object**

- **create new** objects
- **manipulate** objects
- **destroy** objects
    - explicitly using `del` or just *forget about them* (binding to a new variable)

*Python system will reclaim destroyed or inaccessible
objects – called “garbage collection”*

## Objects

Objects are **data abstraction** that captures
- **internal representation**
    - through data attributes
- **interface** for interacting with object
    - through methods (aka procedures/functions)
    - defines behaviors but hides implementation

### Example: `[1,2,3,4]` has type list

Lists are represented internally through linked list of cells.

There are many ways to manipulate a list (mutating):
- `L[i]`, `L[i:j]`, `+`
- `len()`, `min()`, `max()`, `del(L[i])`
- `L.append()`, `L.extend()`, `L.count()`, `L.index()`, `L.insert()`, `L.pop()`, `L.remove()`, `L.reverse()`, `L.sort()`

Internal representation should be **private**, correct behavior may be compromised if you manipulate
internal representation directly.

## Advantages of OOP

-  **bundle data into packages** together with procedures
that work on them through well-defined interfaces
- **divide-and-conquer** development:
    - implement and test behavior of each class separately
    - increased modularity reduces complexity

- **reusing code is easier with classes:**
    - many Python modules define new classes
    - each class has a **separate environment (no collision on function names)**
    - inheritance allows subclasses to redefine or extend a selected subset of a superclass’ behavior

## Creating and Using Own Types With Classes

### Creating vs Using Classes

- **creating**
    - defining the class name
    - defining class attributes
    - *someone wrote code to implement a list class*

- **using**
    - creating new instances of objects
    - doing operations on the instances
    - `L=[1,2]` *and* `len(L)`

## Defining Own Types

`class` keyword is used to define a new type:

```python
class Coordinate(object):
    #define attributes here
```
`Coordinate()` is the type of object u want to define where in the parentheses will be ur class parent.

Similar to `def`, indent code to indicate which statements are part of the class definition.

`Coordinate is a a python object, all attributes are **inherited**:
- `Coordinate` is a subclass of `object`
-`object`is a superclass of `Coordinate`

## Attributes

**Data** and **procedures/methods** that belong to the class.

- **data attributes**
    - think of data as other objects that **make up the class**
    - *a coordinate is made up of two numbers*

- **methods** (procedural attributes)
    - think of methods as functions that only work with this class
    - **how to interact with the object**
    - *can define a distance between two coordinate objects but there is no meaning to a distance between two list objects*

## Defining How To Create An Instance of A Class

Before creating an instance of a class, **u have to define how to create an instance of it.**

`__init__()` is specifically used to initialize data attributes:

```python
class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
```

- **double underscore** `__` is a special method to create an instance
- `self` is a p**arameter that refers to an instance of a class**, this is going to be a placeholder for any sort of instance when it creates an object
- `x,y` is what **data initializes** a `Coordinate` object
- `self.x` and `self.y` are two **data attributes** for every `Coordinate` object.

## Actually Creating An Instance of A Class

After defining a way to create instances, we will begin to actually create them:

```python
c = Coordinate(3,4)
origin = Coordinate(0,0)
print(c.x)
print(origin.x)
```

- `Coordinate(3,4)` **creates a new object** of type `Coordinate` and **passes 3 and 4** to the `__init__`
- the dot in `print(c.x)` is used to **access an attribute of instance c**
- don't have to provide a separate argument for `self`, as python automatically assigns it by default. Meaning that `self` by default is assigned to `c`

## Methods

A method in python is seen as a procedural attribute, it's like a **function that works only with this class.**

- python always passes the object as first argument
    - **convention is to use** `self` **as the name of the first argument of all methods**
- the `.` operator is used to **access any attribute**
    - a data attribute of an object
    - a method of an object

## Defining A Method For The Coordinate Class

Methods behave like a function other than `self` and dot notation (take params, do operations, return):

```python
def distance(self, other):
# code here
```

This is the format to **define a method.** Inside the parentheses of a method it **refers to an existing instance called** `self` **and creates another parameter `other`,** combining the previous instance definition will look like this:

```python
class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def distance(self, other):
        x_diff_sq = (self.x-other.x)**2
        y_diff_sq = (self.y-other.y)**2
        return (x_diff_sq + y_diff_sq)**0.5
```

Instance definitions are always followed by a method, in this case it's `distance`. In `distance` parameter there will be `self` and `other`:
- `self` is used to refer to any instance
- `other` is used as another parameter to method

`x_diff_sq` and `y_diff_sq` are variables to calculate the difference of x and y attributes squared.

Return operator is also used under method block as it behaves similarly to functions.

## Using Methods

Conventional way to use the class:

 ```python
 c = Coordinate(3,4)
zero = Coordinate(0,0)
print(c.distance(zero))
```

`c` is the object to call the method `distance`. In details, `c` is going to look up the class `Coordinate`, under this class it's going to look up method `distance`. `self` is not included because `c` is assigned to it by default.

Inside the parenthesis it gives parameter `zero`.

This is also equivalent to (easier to understand although more tedious to write):

```python
c = Coordinate(3,4)
zero = Coordinate(0,0)
print(Coordinate.distance(c, zero))
```
`print(Coordinate.distance(c, zero))` is the same as `print(c.distance(zero))`.

`Coordinate` here is being shown as the class, `distance` being the method and inside the parentheses it includes all of the variables **including** `self`. `zero` here can be seen as `other`.

## Print Representation Of An Object

```
>>> c = Coordinate(3,4)
>>> print(c)
<__main__.Coordinate object at 0x7fa918510488>
```

- uninformative print representation by default
- define a `__str__` method for a class
- Python calls the `__str__ `method when used with print on your class object

### **Goal:** 

Define a print method that shows actual coordinates:

```
>>> print(c)
<3,4>
```

## Defining Your Own Print Methods

```python
class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def distance(self, other):
        x_diff_sq = (self.x-other.x)**2
        y_diff_sq = (self.y-other.y)**2
        return (x_diff_sq + y_diff_sq)**0.5
    def __str__(self):
        return "<"+str(self.x)+","+str(self.y)+">"
        #notice that it's concatenated with the + operator
```

`__str__()` is the name of the special method to define a print. **Remember, ur printing a string, so it has to be in** `"<>"` **.** All of the strings that are in the print method should be concatenated with the `+` operator.

## Wrapping Your Head Around Types and Classes

To clear up confusion for types and classes:
- ask for the type of an object instance

```
>>> c = Coordinate(3,4)
>>> print(c)
<3,4> #return of __str__ method
>>> print(type(c)) #prints the type of class c is
<class __main__.Coordinate> #c is in class Coordinate
```

Notice the difference between `print(c)` and `print(type(c))`. `print(type())` is used to print the type of class the object's in.

- this makes sense since

```
>>> print(Coordinate)
<class __main__.Coordinate> #Coordinate is a class
>>> print(type(Coordinate))
<type 'type'> #Coordinate class is a type of object
```

If we print `Coordinate` itself, it shows that it's a class. However, printing the type of `Coordinate` will just be seen as printing an object **(a** `Coordinate` **class is just an object)**, thus it shows `type`.

- use `isinstance()` to check if an object is a `Coordinate`

```
>>> print(isinstance(c, Coordinate))
True
```

`isinstance()` is used to check whether a particular object is an instance of a class.

## Special Operators

- `+`, `-`, `==`, `<`, `>`, `len()`, `print`, and many others

### [Basic Customization Operators](https://docs.python.org/3/reference/datamodel.html#basic-customization)

Similar to `print`, u can override these to work with ur class. Define them with double underscores before/after: 

| Method Operators | Object Operators |
| - | - |
| `__add__(self, other)` | `self + other`
| `__sub__(self, other)` | `self - other`
| `__eq__(self, other)` | `self == other`
| `__lt__(self, other)` | `self < other`
| `__len__(self)` | `len(self)`
| `__str__(self)` | `print self`

This must be used when interacting with two objects of the same class.

### Example: Fractions

**Goal: Create a new type to represent a number as a fraction:**

- internal representation is two integers
    - numerator
    - denominator

- interface (methods) how to interact with `Fraction` objects
    - add, subtract
    - print representation, convert to a float
    - invert the fraction

*Author will post code and explanation later, try it out first!*

## The Power of OOP

- bundle together objects that share
    - **common attributes** 
    - **procedures** that operate on those attributes
- use **abstraction** to make a distinction between **how to implement an object vs how to use the object**
- build **layers of object abstractions** that inherit behaviors from other classes of objects
- **create our own classes of objects** on top of python’s basic classes
