Cheat Sheet on Python Classes and OOP
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:
data
methods
(any function inside a class is a "method", or "class method")initialization
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:
- The method is implicitly used for an object for which it is called.
- 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___
- _init__ method (a.k.a. "constructor")
- _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
remembers "state"
i.e. knows where it is during iteraabtion- 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. - _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)
- _next__ special syntax
Same way you can do away with
i_nums.__next__()
and usenext(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
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.
\