Python Part 1 - Object Oriented Python

Dave White

What is OOP?

Extends the idea of typing.
Creating objects

Concepts:
4 Pillars of OOP

Class vs Object

Class vs Object

Class, type , sometimes prototype, are loosely interchangeable terms
Class is usually associated with higher order, a blueprint.
Object is an instance or implementation of a class.

C Example

int A
int B = 8

python
int is a type, A and B are instances of int.

Python example

in python…

A=int(8)
B=int(8)

python
int is a class, A and B are of int.

Details

In C, the variable int A has a name, value, and address.
In python, int A has much more going on under the hood.

dir(A)

python

Things listed by dir are data that are encapsulated with A.

Encapsulation

A.real
A.imag
A.conjugate()

For example, A has a "real" and "imag" attributes
A has a conjugate method

Attributes and methods are the types data that are encapsulated with A.
Encapsulated data is accessible through ".", e.g. A.real

Attributes

Attributes/properties are like variables tied to objects.

int(8).real # ending no parenthesis
int(8).imag # ending no parenthesis
# OR
getattr(a,"imag")

hasattr(a,"real") # True
getattr(a,"infinite") # False

help(a)    # get help on object
help(int)  # same as getting help on class

python

Methods

Methods are class specific functions encapsulated with objects
Two ways of looking at them:

As an attribute, A has the ability to compute its own conjugate:

A.conjugate # No parenthesis

python
The return value is the method itself

As an action, A computes its own conjugate:

A.conjugate() # with parentheses

python
The return value is what the action resulted in

Methods are encapsulated within objects/classes.
They are inseparable; they do not exists outside of the class

conjugate(int(8)); # this does not work
callable(a.conjugate) # check if method ... YES
callable(a.int) # NOT A METHOD

python

Constructor method

For the int class, "int()" is a special method called a constructor
Constructors create a new instance of a class.

Self documenting

Encapsulation allows self documentation
Its easy to find what int can do and can't do, what data its associated with it and what is not.

conjugate(int(8)); # this does not work

python

Polymorphism

A=int(8)
B=int(8)

A and B are two separate instances of the same class, but not the same.
Different names, different, address, same value
Polymorphism the same class can be in different states.
Here objects differ by their name attributes and memory addresses.

Inheritance

Outside of python, integer could be thought of as a subclass of numbers.
Integers are numbers, but constrained or extended in particular ways.
Like not having decimal values, or being able to enumerate distinct objects.
Additionally, integers inherit properties from the parent class numbers.
Such as the ability to be added and subtracted to other numbers.

Integers are a primitive type in python, so are not subclasses of anything, but the principle holds.
We could in theory create subclasses from integers, like complex integers or imaginary integers.

Classes are also polymorphic by subclassing/subtyping.

Object permanence

my_controller=controller()
my_ui=ui()
my_db=db()
my_controller.change_to_state_1()

my_ui.controller=my_controller
my_db.controller=my_controller

my_controller.change_to_state_2()

myui and mydb have mycontroller assigned as attributes to "controller"
When mycontroller changes state, this is reflected in the other objects (both attributes would have mycontroller in state2.

This is because non-primitive objects in python are always bound to aliases/references/pointers.
The end users is expected not to interact with the underlying symbol that created internally.

A=mycontroller() is actually

A = (some hidden symbol = ) my_controller

Object permanence symptoms

What happens when we bind B to A?

A = [ 1, 2, 3 ] # list type, with elements 1, 2, 3. Lists are not primitive
B=A
print(A)
print(B)
B[0]=2
print(A)
print(B)

A is updated when B is.

Details

Options of what could happen

Alias or pointer?

del A
print(A)
print(B)

Appears to be a pointer to the end user.

Copy Contents not address

If you need to copy a value, not the pointer

import copy  # copy module
A = [ 1, 2, 3 ]
B=copy.copy(A) #copy funciton in the copy module
B[0]=2

Object (im)mutability

A = int(8)
B=A
print(A)
print(B)
B=2
print(A)
print(B)

Unlike lists, primitive types do not create intermediate variable aliased to then user-assigned variable.
More specifically, immutable types do not have this property.
Objects belonging to immutable classes have attributes that cannot be changed once created.
In the above example, when B is assign to two, a new variable is being declared rather than changing the one that exists.
Immutable types include all primitive types which are also not collection types or things that can be indexed.

Why immutability?

All about memory efficient memory management.
Immutable types used as a foundation for the rest of the code base.
Immutable types limit human error, are faster.

Immutability in practice

Not much you can change strings/ranges by indexing

mylist =  [1, 2, 3]
mytuple = (1, 2, 3)

# Reassignment is valid in lists...
mylist=mylist[0:2] + mylist[0:2]
print(mylist)

# and tuples...
mytuple=mytuple[0:2] + mytuple[0:2]
print(mytuple)

# Changing elements is valid in lists...
mylist[0]=0 # works
print(mylist)

# not tuples
mytuple[0]=1 # does not work

#workaround
mytuple=(0,) + mytuple[1:]
print(mytuple)

python
Tuples and lists are identical other than syntax and mutability.
lists can change their elements in place, tuples cannot.

Builtin Types

Primitive Types (immutable)

Collection Types (mutable)

Collection Types (immutable)

Iterable Types (immutable)

Dictionaries are only mutable in newer versions of python.
That said, its safer to just to view them as immutable.
Together, list is the only builtin type that should be viewed as mutable.
Rule of thumb: treat immutables as values, treat mutables as objects (with reference etc.)

Primitive types

int & float

Float are numbers with decimal precision.
Int is short for integer (no decimal precision).

type(8)
type(-8)
type(8.1)
A=int(8)
B=float(8)
type(A+B)
print(A+B)

Python automatically interprets numbers without decimals as integers
Python automatically interprets numbers with decimals as float
If a int is combined with a float, the int will be cast to a float.

Arithmetic

8+8        # addition
8-8        # addition
8*8        # multiplication
8/8        # division
8**8       # exponent
8.1%8        # modulus (remainder)
8.1//8       # flood diviision (division without remainder)
round(8.1)
abs(-8)

Boolean

b=3
a=-3
b >= abs(a)                  # Is b greater than abs(a)?
b <= abs(a)                  # Is b less than abs (a)?
b == 3                       # Is b equal to 3? Notice to equal signs instead of 1.
b != 3                       # Is b not equal to 3?
b <= 4                       # Is b less than or equal to 4?
b >= 4                       # Is b greater than or equal to 4?
True==1                      # True and 1 are are the same
False==0                     # False and zero are the same
not True==False              # not takes the compliment of a test (flips the sign)
c = b==4                     # Assigning result of logical test
print(c)

Combined logic

not (b <= 4)                 # compliment of Is b less _greater_ than or equal to 4?
b==5 | abs(a)==3             # OR
b==5 & abs(a)==3             # AND
(b==3 & abs(a)==3 ) | c      # Grouping

string

Unlike other languages, strings, not individual characters are primitive.

NoneType

None type is simply an empty value.
Think of it as an extension to the other primitives.

None
x=None
print(x)
print(not x):

Other than 0, False, and None, all other values of all other types will be cast to "True" when applied to logic.
The None type is a great placeholder.

Collection types

Collection types are like containers that contain other types.
All collection types have ways of selecting individual elements by indexing

Numeric Indexing

  1. Zero indexing

    Numeric indexing in python uses zero indexing, meaning that the first element in the collection is labeled 0, the second 1, and so on.
    \(\underset{0}{\text{A}} \underset{1}{\text{B}} \underset{2}{\text{C}}\)

  2. Element Selecting

    \(\underset{0}{\text{A}} \underset{1}{\text{B}} \underset{2}{\text{C}}\)

    mylist[0] # first element
    mylist[-1] # last element
    mylist[1] # 2nd element third
    mylist[3] #  4th element
    mylist[-2] #  third to last element
  3. Slicing

    \(\text{ }_0\text{A}_1 \text{B}_2 \text{C}_3\)

    mylist[0:1] # first element
    mylist[-1] # last element
    mylist[1:2] # 2nd to third
    mylist[:] #  all elements
    mylist[1:] #  2nd to last elements
    mylist[:-1] # 1 to second to last
  4. Slice type

    The colon indexing used in previous examples is implicitly a slice type

    list1=[1,2,3,4,5]
    list2=[6,7,8,9,10]
    list1[1:4]
    list2[1:4]
    
    # same indexing, but with explicitly called slices
    s=slice(1,4)
    list1[s]
    list2[s]

    Explicitly creating a slice variable is useful for slicing multiple things in the same way.

Tuples

The Immutable collection

mytuple = ("apple", "banana", "cherry", "orange")

Use if you have a list that doesn't change.
Faster & protected from changing

Consider set as the base collection type.
Other collection types are tuples with more features

  1. Check immutability

    mytuple=mytuple[1:3] # a reassignment
    mytuple[0]="blueberry" # Doesn't work.
    
    dir(mydict)

Lists

The Mutable Tuple

mylist = ["apple", "banana", "cherry", "orange"]

mylist.append("cherry")
mylist.pop()
mylist.count("cherry")
mylist.extend(["watermelon","strawberry"])
mylist.insert(0,"cherry")
mylist.remove("cherry")
mylist.reverse()
mylist.sort()

dir(mydict)

Sets

The unordered tuple
Note: few use or even know about this builtin type.

myset1 = {"apple", "banana", "cherry", "orange"}
myset2 = {"apple", "banana"}
myset3 = {"apple"}
myset4 = {"apple","blueberry"}
myset5 = {"steak"}

myset4.intersection(myset4)
myset4.union(myset2)
myset4.difference(myset2)
myset4.isdisjoint(myset5)
myset4.isdisjoint(myset4)
myset4.issubset(myset1)
myset4.issuperset(myset3)

Dictionaries

The unordered tuple with mutability and key-pairs

Same curly brackets as sets, but each entry is a key and value

mydict = {
  "apple": "yabloka",
  "banana": "banan",
  "cherry": "veeshnya",
  "orange": "apelseen",
}

mydict["apple"]
mydict[1]
mydict.keys()
mydict.values()
mydict.items()

Iterable Types

Iterable types are like lists of sorted numbers whose values are generated on the fly.
There are two builtin iterable types, slices and ranges.
Slices were covered in "Indexing" above.
Ranges and will be covered again later, when discussing "for loops."