Cheat Sheet on Python Classes and OOP

Home

1 Object Oriented Programming OOP

Object Oriented Programming has formally defined properties:

  • encapsulation
  • data abstraction
  • polymorphism
  • inheritance

1.1 encapsulation (think private variables)

Encapsulation in OOP conceals the internal state and the implementation of an object from other objects. It can be used for restricting what can be accessed on an object.

In Python, encapsulation with hiding data from others is not so explicitly defined and can be interpreted rather as a convention. It does not have an option to strictly define data being private or protected. However, in Python, you would use the notation of prefixing the name with an underscore (or double underscore) to mark something as nonpublic data. When using the double underscore, name mangling occurs; i.e in a runtime, that variable name is concatenated with the class name.

If you have an __auditLog() method in a device class, the name of the method becomes Device__auditLog(). This is helpful to prevent accidents, where subclasses override methods, and break the internal method calls on a parent class. Still, nothing prevents you from accessing the variable or method, even though, by convention, it is considered private.

class Device:
   def __auditLog(self):
      # log user actions
      pass

   def action(self):
      # perform desired action and log
      (self.__auditLog()     # private methods can typically called only
                             # within the same class

1.2 data abstraction

modules, classes or functions directly.

1.3 polymorphism

Goes hand in hand with class hierarchy. When a parent class defines a method that needs to be implemented by the child class, the method is considered polymorphic, because the implementation would have its own way of presenting a solution than what the higher-level class proposed.

Polymorphism is present in any setup of an object that can have multiple forms. when a variable or method accepts more than one type of variable, it is polymorphic.

1.4 inheritance

Already discussed in python classes.

2 Classes (for Object Oriented Programming)

Classes are all about bundling data and functionality on that data. Can also think of classes as "a template for creataing objects with related data, and functions that do interesting things with that data" - Socratica

See docs.python.org for detail on classes and methods. But summarizing:

Creating a class means creating a new TYPE of object, not an instance of a type of object. x = 3 simply creates a new instance of an integer class object, and assigning it to "x". But creating a class is creating a new TYPE.

The new TYPE of object allows new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.

2.1 Features of Python Classes

Provide all the standard features of object oriented programming.

2.2 Coding Classes

Capitalize all classes. For example, "class User:" not "class user:".

pass

If you define a class you MUST have at least one statement in the class. If you want the class to do nothing, you can include the pass statement which passes to the next statement without doing anythin. – why???

From Socratica:

class User:
    pass

user1 = User()

Here user1 is an instance of the User class. You can call user1 an object. To attach data to this object we can add some fields like this:

user1.first_name = "John"
user1.last_name = "Cleese"

firstname and lastname are variables. Because firstname and lastname are attached to an object, we call them "fields".

These fields store data specific to user1. As per PEP 8 style guide, you should NOT capitalize fields, and you should use underscores to separate multiword fields.

objects of a class do NOT neccessarily all have the same fields. For example, look at this:

user1.age = 80
user2.first_name = "Eric"
user2.last_name = "Idle"
user2.songs = 115

If you try to ask for user2.age you will get an error: "AttributeError: 'User' object has no attribute 'age'

Even though user1.age will get you the answer 80.

So, you can use dir(user2) to see what fields it has.

3 Classes are more than just dictionaries.

Classes can have:

  1. data
  2. methods (any function inside a class is a "method", or "class method")
  3. initialization
  4. help text

Python Classes provide all the standard features of object oriented programming.

Python uses classes because classes allow us to logically group our data and functions in a way that is easy to reuse, and easy to build upon if needed. Think of it as simply bundling 1) data and 2) functionality on that data. Thus accomplishing the "data abstraction" property of OOP

Can also think of classes as "a template for creataing objects with related 1) data, and 2) functions that do interesting things with that data" - Socratica

3.0.1 Inhertiance

New classes can be defined based on existing classes. When they do they inherit the properties, data, and functionality of the existing class.'

In python you do NOT have to pre-declare instances of objects. They get declared/created the first time you use a class to create a class instance.

Outside of the class, you refer to methods inside the class as class.method i.e. use the "dot" notation.

variables declared inside a class are not private. But by convention only people use _varname for variables intended to be private (i.e. used only within the class or function.

See docs.python.org for detail on classes and methods.

3.1 Classes as a new TYPE of object

Creating a class means creating a new TYPE of object, not an instance of a type of object.

x = 3 simply creates a new instance of an integer class object, and assigning it to "x". But creating a class is creating a new TYPE.

The new TYPE of object allows new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.

3.1.1 Init method (a.k.a. "constructor")

def __init__( self,arg1, ..., argn)

An initialization method, which other programming languages call "constructors", is called every time you make a new instance of a class.

Said in other words, the init method is always called the first time you create an object of this class.

The first argument is always called "self". It is a reference to the new object being created.

The additional arguments are optional. If they are added, then you must store these values in fields, using the self. syntax as below:

def __init__(self, argument2, argument3)
     self.field2 = argument2
     self.field3 = argument3

A more detailed example of the __init__ method for a class:

import datetime

class User:
  ''' this docstring will be displayed if one issues help(User).
  The class has two methods, init and age. '''
    def __init__(self, full_name, birthday):
        self.name = full_name
        self.birthday = birthday
      # can store the value in birthday in a field called birthday.
      # so be careful when coding.  The second 'birthday' in that line is
      # the value provided when you create a new instance of the object
      # the first 'birthday' is the name of the field where we will store
      # this provided value.

      # Expanding the init method, we can extract first and last names
        name_pieces = full_name.split(" ")
      # this will store [2] split on spaces into an array.
        self.first_name = name_pieces[0]
        self.last_name = name_pieces[-1]  # just look at the last string
      # self.last_name = name_pieces[1]   # would give you the second string
      # which might be an initial, and not the last name

      # Note that we can create variables in the method, but they exist
      # only inside the method and are killed when the method exits.
      # i.e. first_name = name_pieces[0] would NOT assign anythying to the
      # object

    def age(self):
       ''' Return the age of the user in years. '''
       yyyy = init(self.birthday[0:4])
       mm = init(self.birthday[4:6])
       dd = init(self.birthday[6:8])
       dob = datetime.date(yyyy, mm, dd)
       age_in_days = (today - dob).days
       age_in_years = age_in_days / 365
       return int(age_in_years)

Now we can create an object, let's call it 'user':

user = User("Michael Palin", 19430505)
print(user.age())  # will print 80 if you run  this in Nov, 2019
help(User) to see all sorts of additional info to the doc[2] you added.

3.1.2 Example representing actors as a class

class Pythonite:
  # class attributes, all instances of this object have this attribute
  troupe-name = 'Monty Python'

  # the dunder init method is always run when this class is
  # called, i.e. an instance is instantiated.

  def __init__(self, first, last, role, lines):
       # these are the instance attributes, and they only belong to one
       # instance of the object.  (each  object has its own attributes)
       self.first = first
       self.last = last
       self.name = first + " " + last
       self.name = f"{first} {last} "
       self.role = role
       self.email = first.lower()[0] + last.lower() + '@montypython.uk'
       self.lines = lines

# act1 and act2 are two instances of the Pythonite class
act1 = Pythonite('John', 'Cleese', 'Sir Lancalot', 656)
act2 = Pythonite('Michael', 'Palin', 'Sir Galahad', 412)
act3 = Pythonite('Eric', 'Idle', 'Sir Robin', 489)

print(f'{act1.first}, {act1.last}, was part of the group {Pythonite.troupe-name}')

Both act1.troupe-name, and Pythonite.troupe-name will correctly show "Monty Python". But Pythonite.role will fail, as role is an instance attribute, and Pythonite class does not have a class attribute called "role"

Notice that if I try to print(act1, act2) I will only get some line telling me that my objects act1 and act2 are at some unique memory location. If I wanted to actually print John's and Michael's data, I would do this:

print(act1.first, act1.last)
print(act2.first, act2.last, ... act2.lines)

Notice as well that I am able to create additional fields within the class that are in addition to the passed arguments, such as self.email

3.1.3 Simplest example employees as a class

Each employee will have certain standard "template" like fields, such as name, age, sex, hire date,

And, each employee will have certain functions that they can do, such as calculate eligable vacation days based on hire date…..

class Worker:
   pass

employee1 = Worker()
employee2 = Worker()

These will be two separate objects with separate locations in memory. You can see that by printing them i.e. print(employee1, employee2)

4 Class Variables and Instance Variables

4.1 Attributes

Data of fields for a class attributes. The combined self, and extra argument (attributes) passed to and from class is called the class signature

4.2 Instance Variables a.k.a. Fields or Instance Attriutes

These all mean the same thing.

Instance attributes are variables assigned to a particular instance of the class, for example employee1.firstname

They contain data that is unique to each instance. i.e. employee #

act1.first = 'John'
act1.last = 'Cleese'
act1.email = 'jcleese@python.uk'
act1.role = 'Sir Lancalot'
act1.lines = 656

act2.first = 'Michael'
act2.last = 'Palin'
act2.email = 'mpalin@python.uk'
act2.role = 'Sir Galahad'
act1.lines = 412

That is a manual approach. The better way is to use classes properly by creating a 'method' a.k.a. function to do this for us automatically. See 'Methods' below:

4.3 Class Variables ("static" variables) a.k.a Class Attributes

These are variables that are related to the class itself and not just a particular instance of the class (in which case they are instance variables… see above). Class variables have the same value across all class instances. i.e. static variables For instance email domain could be a static variable across ALL instances of an employee class for a company.

For example:

print(Pythonite.troupe-name) is the same regardless of actor (for Monty Python)

4.4 Methods (compare with functions below)

Methods are really just functions, but functions that are associated with a class. The two major differences between functions and methods:

  1. The method is implicitly used for an object for which it is called.
  2. The method is accessible to data that is contained within the class.

from tutorialspoint.com

When we create methods within a class, they always receive the class instance as the first argument automatically. This is true on regular methods and on dunder methods.

This first argument is always called "self".

This first argument is always called "self". It is a reference to the new object being created. You could call it anything, but convention is to call it self. After 'self' you can add any other arguments you want the method to accept additional arguments

The additional arguments are optional. If they are added, then you must store these values in fields, using the self. syntax as below:

Class Pythonite:
   def __init__(self, first, last, role, lines):
        # these are the instance attributes, and they only belong to one
        # instance of the object.  (each  object has its own attributes)
        self.first = first
        self.last = last
        self.name = f"{first} {last}"
        self.role = role
        self.email = first.lower()[0] + last.lower() + '@montypython.uk'
        self.lines = lines
        # first[0] is the first letter of the string held in first
        # first.lower() is the string converted to lower case
        # first.lower()[0] is the first letter of the string, lower case

   def method66(self, argument2, argument3)
       self.field2 = argument2
       self.field3 = argument3

   def announce(self):
       print(f"{self.name} has arrived!!!")

   # if no partner is supplied when method is called, announce them as 'guest'
   def announce_partner(self, partner="guest"):
       print(f"{self.name} and {partner} have arrived!!!")

act1 = Pythonite('John', 'Cleese', 'Sir Lancalot', 656)
act2 = Pythonite('Michael', 'Palin', 'Sir Galahad', 412)
act3 = Pythonite('Eric', 'Idle', 'Sir Robin', 489)

spouse = "Janet"
act1.announce_partner(spouse)
act3.announce_partner()
act2.announce()

# will print
# John Cleese  and Janet have arrived!!!
# Eric Idle  and guest have arrived!!!
# Michael Palin has arrived!!!

In addition, if you want those fields to have default values do this:

def method77(self, argument2=3.1415, argument3=2.71828)
    self.field2 = argument2
    self.field3 = argument3

In which case you can call act1.method66() or act1.method66(3.14, 2.72)

Special care must be taken if the argument is a muttable object in python. That is, best to direclty set them inside the method. For example, an array is a mutable object, so for arrays as arguments do this:

def method66(self, argument2=None, argument3=None)
    if argument2 is None:
       argument2 = []
    if argument3 is None:
       argument3 = []
    self.field2 = argument2
    self.field3 = argument3

4.4.1 Basic Python method

class Class_name 
    def method_name () : 
        ...... 
        # method body 
        ......    

4.5 User-Defined Method

class ABC : 
    def method_abc (self): 
        print("I am in method_abc of ABC class. ") 

class_ref = ABC() # object of ABC class 
class_ref.method_abc()

output: I am in methodabc of ABC class.

4.6 In-built Method

import math 

ceil_val = math.ceil(15.25) 
print( "Ceiling value of 15.25 is : ", ceil_val)  

4.7 Dunder methods (a.k.a. magic methods, a.k.a. constructors)

__init__
__self__
__str__
__lt__
__gt__
__eq__
__add__
__dict__

In python, using leading and trailing double underscores is the naming conventions for indicating a variable is meant to be private.

Dunder or magic methods in Python are special methods that are defined with double underscores (or 'dunders'), such as init, iter, str, dict, weakref, _lt___

  1. _init__ method (a.k.a. "constructor")
  2. _itter__ method

    If the class has this method, it becomes iterable, meaning that you can iterate through all instances of the class. Check by running dir(myclass) to see if the class contains the __iter__ method.

    Iterators need to know two things

    1. remembers "state" i.e. knows where it is during iteraabtion
    2. can get to the "next" instance with the __next__ method

    If the class you created does NOT have the __next__ dunder method, it won't be iterable. You need BOTH __iter__ and __next__ dunder methods and then you are good.

  3. _iter__ special syntax

    If an object or class is iterable, it has the __iter__ function, so you can call it using this syntax: i_nums = nums.__iter__() But this is so common that python add the iter syntax like so: i_nums = iter(nums)

  4. _next__ special syntax

    Same way you can do away with i_nums.__next__() and use next(i_nums)

4.8 Class inheritance

A class supports inheritance, which is the ability for deriving new classes based on existing classes.. The new class is called the child class while the inherited class is called the parent class

4.9 Further detail on iterators and iterables

if you have a list, say nums = [1, 2, 3] and run dir(nums) you will see the __iter__ but you won't see the __next__ because lists by themselves are iterable, but are NOT iterors. That is why if you try to run the next function on nums you will get an error saying: "TypeError: 'list' object is not an iterator" i.e. print(next(nums))

Now if you take that list and create a new object using iter, like so: i_nums = iter(nums) tAnd then you run dir(i_nums) you will see BOTH the __iter__ and the __next__ methods, so inums is an iterator. You can then use print(next(i_nums))

To avoid getting a ExceptionError: StopIteration when you run out of instances and still try to run next() on it, you use this common while contruct:

while True:
   try:
      item = next(i_nums)
      print(item)
   except StopIteration:
      break

4.9.1 Thre is no going backwards in an iterable.

There is ONLY the next option, no "previous" function. So to go backwards, you simply create a NEW iterable from the old one and begin again from the start


  1. _dict__ method
  2. _dict__ method
  3. _dict__ method
  4. _dict__ method

5 Coding Classes (Syntax)

class ClassName(object):
   class_variable = value   #+ value shared across all class instances

   def __init__(instance_variable_value):
      self.instance_varialbe = instance_variable_value
      #+ this value is specific to this instance.  

   #+ accessing instance variables
   class_instance = ClassName()
   class_instance.instance_variable

   #+ accessing class variable
   ClassName.class_variable
   class_instnce.class_variable

5.1 Capitalize all classes.

For example, "class User:" not "class user:".

5.2 Must have 1 or more statements

pass

If you define a class you MUST have at least one statement in the class. If you want the class to do nothing, you can include the pass statement which passes to the next statement without doing anythin. – why???

5.3 Example of pass

From Socratica:

class User:
    pass

user1 = User()

5.4 Objects ( an instance of a class)

In the above example, user1 is an instance of the User class. You can call user1 an object. To attach data to this object we can add some fields like this

user1.first_name = "John"
user1.last_name = "Cleese"

firstname and lastname are variables Because firstname and lastname are attached to an object, we call them "fields". These are also called attributes, or instance variables.

5.5 Objects may have different fields.

objects of a class do NOT neccessarily all have the same fields. For example, look at this:

user1.age = 80
user2.first_name = "Eric"
user2.last_name = "Idle"
user2.songs = 115

If you try to ask for user2.age you will get an error: "AttributeError: 'User' object has no attribute 'age'

Even though user1.age will get you the answer 80.

5.6 dir(some-class-instance) to see what fields it has.

So, you can use dir(user1) or dir(user2) to see what fields it each object has, and what methods are available to that class.

dir() without arguments returns a list of names in the current local scope.

dir(class)

dir(string)

dir(anyobject)

A nicer alterative to dir() is see() but you have to import xx first Another alternative is inspect()import inspect first.

5.7 Fields should NOT be capitalized.

These fields store data specific to user1. As per PEP 8 style guide, you should NOT capitalize fields, and you should use underscores to separate multiword fields.

6 Classes doc string

Docstrings are/and should be used in many places in python. For Classes the PEP 247 standard suggests a style that should be followed. For the detailed PEP 247 see: https://peps.python.org/pep-0257/

The docstring for a class should summarize its behavior and list the public
methods and instance variables. If the class is intended to be subclassed, and
has an additional interface for subclasses, this interface should be listed
separately (in the docstring). The class constructor should be documented in the
docstring for its __init__ method. Individual methods should be documented by
their own docstring.

So here is an example:


6.0.1 Full Coding Example:

import datetime

class User:
  ''' this docstring will be displayed if one issues help(User).
  The class has two methods, init and age. '''
    def __init__(self, full_name, birthday):
        self.name = full_name
        self.birthday = birthday
      #+ can store the value stored in the argument 'birthday' in a
      #+  field called birthday.
      #+ so be careful when coding.  The second 'birthday' in that line is
      #+ the value provided when you create a new instance of the object
      #+ the first 'birthday' is the name of the field where we will store
      #+ this provided value.

      #+ Expanding the init method, we can extract first and last names
        name_pieces = full_name.split(" ")
      #+ this will store strings split on spaces into an array.
        self.first_name = name_pieces[0]
        self.last_name = name_pieces[-1]  #+ just look at the last string
      #+ self.last_name = name_pieces[1]   #+ would give you the second string
      #+ which might be an initial, and not the last name

      #+ Note that we can create variables in the method, but they exist
      #+ only inside the method and are killed when the method exits.
      #+ i.e. first_name = name_pieces[0] would NOT assign anythying to the
      #+ object.  The correct syntax is self.firstname = name_pieces[0]

    def age(self):
       ''' Return the age of the user in years. '''
       yyyy = init(self.birthday[0:4])
       mm = init(self.birthday[4:6])
       dd = init(self.birthday[6:8])
       dob = datetime.date(yyyy, mm, dd)
       age_in_days = (today - dob).days
       age_in_years = age_in_days / 365
       return int(age_in_years)


Now we can create an object, let's call it 'user':

user = User("Michael Palin", 19430505)
print(user.age())  #+ will print 80 if you run  this in Nov, 2019
help(User) to see all sorts of additional info to the docstrings you added.

7 Functions (compare with methods)

Function is block of code that is also called by its name. (independent) The function can have different parameters or may not have any at all. If any data (parameters) are passed, they are passed explicitly.

A function may or may not return any data. Function does not deal with Class and its instance concept.

7.1 Basic Function Structure in Python :

def functionname ( arg1, arg2, …) : …… #+ function body ……

7.2 User Defined Function

def Subtract (a, b): 
    return (a-b) 

7.3 Built-in Function

s = sum([5, 15, 2]) 
print( s ) #+ prints 22 

mx = max(15, 6) 
print( mx ) #+ prints 15 

8 Inheritance

8.1 Method Resolution Order (MRO)

   >>> class A(object):
...     x = 1
...
>>> class B(A):
...     pass
...
>>> class C(A):
...     pass
...
>>> print A.x, B.x, C.x
1 1 1

Makes sense.

>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1

Yup, again as expected.

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3

What the $%#!&?? We only changed A.x. Why did C.x change too?

In Python, class variables are internally handled as dictionaries and follow what is often referred to as Method Resolution Order (MRO).

Since the attribute x is not found in class C, it will be looked up in its base classes (only A in the above example, although Python supports multiple inheritances).

In other words, C doesn’t have its own x property, independent of A. Thus, references to C.x are in fact references to A.x. This causes a Python problem unless it’s handled properly.

9 Unified Modelling Language

Codebases are typically writting in one language, but other developers might fluent in another language. So, pseudo-code like abstration, UML can be used

UML is high level. It let's you sketch you project in high level graphics.

\

9.1 Home