This lesson covers the following topics:
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of variables (also reffered to as attributes, fields, or properties), and code, in the form of functions (often known as behaviors, methods, procedures, or subroutines).
A feature of objects is that an object's own functions can access and often modify the data variables of itself (objects have a notion of this or self). In OOP, computer programs are designed by making them out of objects that interact with one another. OOP languages are diverse, but the most popular ones are class-based, meaning that objects are instances of classes, which also determine their types.
In object-oriented programming, a class is template for creating objects, providing initial values for attributes (member state or variables) and implementations of behavior (member functions or methods). In many languages, the class name is used as the name for the class (the template itself), the name for the default constructor of the class (a function that creates objects), and as the type of objects generated by instantiating the class.
When an object is created by a constructor of the class, the resulting object is called an instance of the class, and the member variables specific to the object are called instance variables, to contrast with the class variables shared across the class.
# This is a very simple example of a class called Point that represents points in the Cartesian plane.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class Point(): # The name of a class is capitalized, using camel-case.
def __init__(self, x, y): # This is the "constructor" function. In Python it is called __init__().
"""Constructs a point object."""
self.x = x # This attribute, self.x, can be used anywhere within the class.
self.y = y # This attribute, self.y, can be used anywhere within the class.
def display(self): # This behavior prints out a formatted representation of the Point object.
"""Prints out a formatted representation of the Point object."""
print(f"({self.x},{self.y})")
# Now we can create as many objects (or "instances") of the class as we want.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
p1 = Point(0, 0)
p2 = Point(42, 23)
p3 = Point(55, 89)
# We can interact with the objects by calling their behaviors.
p1.display()
p2.display()
p3.display()
In object-oriented programming, encapsulation refers to the bundling of data (or attributes) with the functions (or behaviors) that operate on that data, or the restricting of direct access to some of an object's components. Encapsulation is used to hide the values or state of an object's attributes inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods. Publicly accessible functions (or behaviors) are generally provided in the class to access or modify the state more abstractly. In practice, these are called "getters" and "setters", which are provided to access the values indirectly, in order to prevent violating encapsulation.
In the example above, the attributes highlighted in red are private and the behaviors highlighted in green are public.
# Here's an example of private/public attributes and behaviors within a class in Python.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class Citizen():
def __init__(self, name, ssn, pwd):
"""Constructs an object (or instance) of the Citizen class."""
self.__name = name # private attribute
self.__ssn = ssn # private attribute
self.__password = pwd # private attribute
def getName(self): # public behavior
"""Returns the name of the Citizen object."""
return self.__name
def getSSN(self, pwd): # public behavior
"""Returns the ssn of the Citizen object, if the correct password is provided."""
if self.__checkPassword(pwd):
return self.__ssn
else:
return "The password you entered is incorrect."
def __checkPassword(self, pwd): # private behavior
"""Returns True if the password is correct, False if otherwise."""
return self.__password == pwd
# Now let's create some instances (objects) of Citizen and interact with the them.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bob = Citizen("Bob", "123-45-6789", "password123")
sue = Citizen("Sue", "987-65-4321", "65478126571")
print(bob.getName())
print(bob.getSSN("password123"))
print(sue.getName())
print(sue.getSSN("password123"))
Function (or method) overriding is the property of a class to change the implementation of a behavior (or function) provided by one of its base classes. Overriding is a very important part of OOP since it makes inheritance utilize its full power. By using method overriding a class may "copy" another class, avoiding duplicated code, and at the same time enhance or customize part of it.
In Python function overriding occurs by simply defining in the child class a function with the same name of a function in the parent class. Function overriding is technically a part of the inheritance mechanism, which we will cover more extensively when we talk about class inheritance.
One of the most common functions that we override in a Python class is the __str__() function. This is the function that is implicitly called when you print an object. By default, this function returns a string representation of the memory location that the object is saved at. Since that information isn't typically very useful, we override it to return something more useful.
# By default a class automatically knows how to print itself by inheriting a __str__() function from its base class.
# This behavior, by default, prints out the memory location of the instance (or object) of the class.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class Demo():
pass
d = Demo()
print("Printing d:", d)
# However, we can override this behavior by simply redefining the __str__() function within the class.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class Demo():
def __str__(self):
"""Returns a string representation of a Demo object."""
return "Now I think I'll print myself like this! :)"
d = Demo()
print("Printing d:", d)
A class is not always a template for creating objects. It can also host its own variables and functions that may have absolutely nothing to do with objects that are created from the class. However, due to the relationships that are inherently formed while using an object-oriented approach, class and object variables and functions are typically closely related.
To put it simply, "class" variables/functions are called "static", only one copy is made while executing the program, and this copy belongs to the class. On the other hand, "object" variables/functions are called "non-static" and each instance (or object) of the class gets its own copy of these variables/functions while executing the program.
# This is an implementation of the example illustrated above, which demonstrates
# the use of static and non-static variables/functions.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class Point():
# Static (Class) Variables/Functions
#---------------------------------------------------------------------------
numPoints = 0
def distance(p1, p2):
d = ((p1.__x - p2.__x)**2 + (p1.__y - p2.__y)**2)**(1/2)
print(f"This distance between {p1} and {p2} is {d}.")
# Non-Static (Object) Variables/Functions
#----------------------------------------------------------------------------
def __init__(self, x, y):
"""Constructs a Point object."""
self.__x = x
self.__y = y
Point.numPoints += 1
def __str__(self):
"""Returns a string representation of a Point object."""
return f"({self.__x},{self.__y})"
# Now lets go ahead and create some Point objects and interact with the class.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
p1 = Point(0, 0)
p2 = Point(42, 23)
p3 = Point(55, 89)
print("Number of Point objects created: ", Point.numPoints)
Point.distance(p2, p3)