OOPs in Python
Introduction
In Python programming, everything is an object. Variables and even functions are objects. A class is a mold that creates an object.
Think of a popsicle tray. First, you manufacture the popsicle tray to create your desired size, shape, and depth; this is the class. Then, you can decide what to pour into your popsicle tray to freeze — maybe you add water and simply make ice, or maybe you add different kinds of fruit and juices to make popsicles. Each popsicle you create is an object, and objects can have different “data” or flavors associated with them.
This article will demonstrate with code how to create your own class and use it in your Python code. The different components of a class can be broken down into the following: constructors, getters and setters, properties, decorators, privacy naming, class methods, attributes, and inheritance.
When to use a class/object versus a module:
- Use classes when you need a number of individual instances with similar behavior but different data
- Use classes when you want support for inheritance; modules do not support inheritance
- Use a module if you only want one of something
- Use the simplest solution; modules are usually simpler than classes
Introduction Example of a Class
Below is an example of a simple class. Within the class, we see three components: the __init__
method which is the initialization method, or constructor, a setter method called toss
, and a getter method called get_sideup
.
class Coin():
def __init__(self): # Constructor
self.sideup = "Heads"
def toss(self): # Method
if(random.randint(0, 1) == 0):
self.sideup == "Heads"
else:
self.sideup == "Tails"
def get_sideup(self): # Method
return self.sideup
How do you use it in your main Python script? In your script, you simply call the object and set it to a new variable. Then you can begin to use its components.
my_coin = Coin() # Creates the object
my_coin.toss()
print("This side is up: ", my_coin.get_sideup())
Let’s break it down.
Class Components
Object Initialization Method
When you see a method with the special name __init__
, you will know that this is the object initialization method. This is called the constructor because it constructs the object in memory. This method is run automatically when you create an object of your class.
class Person():
def __init__(self, name):
self.name = name
Our __init__
method above requires an argument called name. When we create an object using our Person
class, we should pass a name into the call like: Person("Bob")
.
The self argument specifies that it refers to the individual object itself. Remember that the class is a mold and we can use that mold to initialize (and then later modify) multiple objects. For example, we can create two objects with the Person
class:
me = Person("Author")
me.name # --> "Author"
you = Person("Reader")
you.name # --> "Reader"
Getters and Setters
Some object-oriented languages support private object attributes that can’t be accessed directly from the outside. For that reason, you need getter and setter methods to read and write the values of private attributes.
All attributes and methods in Python are public. We don’t need getters and setters. To be “Pythonic”, use properties.
class Duck():
def __init__(self, input_name):
self.hidden_name = input_name # The user won't know to try duck.hidden_name
def get_name(self): # Getter
print("inside the getter")
return self.hidden_name
def set_name(self, input_name): # Setter
print("inside the setter")
self.hidden_name = input_name
name = property(get_name, set_name)
The last line defines the getter and setter methods as properties of the name attribute. Now it will call the getter and setter methods in the following:
pet = Duck("Donald")
pet.name
# --> inside the getter
# --> "Harold"
pet.name = "Daffy"
# --> inside the setter
Decorators
Decorators are another way to define properties (the same thing we did above).
class Duck():
def __init__(self, input_name):
self.hidden_name = input_name # The user won't know to try duck.hidden_name
@property
def name(self): # Getter
print("inside the getter")
return self.hidden_name
@name.setter
def name(self, input_name): # Setter
print("inside the setter")
self.hidden_name = input_name
name = property(get_name, set_name)
Privacy Naming
Begin by using two underscores in the name. This makes it so that the attribute can’t be accessed outside of the class definition once you create your object. This also helps to prevent the accidental overriding of attributes by subclasses.
In our Duck class, instead of using hidden_name
, use __name
.
self.hidden_name = input_name
→ self.__name = input_name
Class Methods
So far, what we’ve been demonstrating have been instance methods. How can we tell? The first parameter of an instance method is self. When you call an instance method, the call will only affect the copy of the object you are working with.
Class methods affect the class as a whole (and therefore all of its copies of objects). Instead of the self parameter, a class method uses the cls parameter. A class method can be defined by using the class decorator @classmethod
@classmethod
def count_objects(cls):
print("The class has", cls.count, "objects")
Static methods are a third type of method that affect neither the class nor its objects. It uses no self or cls parameters. It’s just there for convenience.
@staticmethod
def commercial():
print("This product is brought to you by Medium.")
Attributes
Instance attributes are the outward behaviors we want the object instances to share. A student class may have the attributes:
- methods: student.get_gpa(), student.add_class(), student.get_schedule()
- data: student.first_name, student.last_name, student.class_list
dir(object_instance)
gives you the list of attributes of that object.
object_instance.__dict__
gives you all the instance attributes specific to that instance (and values)
A class attribute is an attribute of the class, rather than an attribute of an instance of a class. This is an attribute that all objects of the class share together. Let’s say we want to track that every student is a human:
class Student():
isHuman = True # --> class attribute
def __init__(self, ...):
...
If you want to know more, here is an overly-thorough guide on Python class attributes.
Inheritance
Inheritance allows you to create a hierarchy of classes where a class acquires all the properties and behaviors of a parent class. Then you can make your own specifications on the child class that is different from the parent.
For example, we have a parent class Animal
with the ability to eat and sleep. Then we create a child class Cat
that also takes on the attributes from Animal
, plus its own special attribute.
class Animal():
def eat(self):
print("Munch munch")
def sleep(self):
print("Zzz...")
class Cat(Animal):
def meow(self):
print("Meow!")
All you need to do is pass the Animal class into Cat. Now the Cat class has eat()
and sleep()
. You can override the eat or sleep methods on the Cat subclass by simply defining the method in Cat. You can override any method using __init__()
.
The child class can add a method that wasn’t in the parent (i.e.; meow()
). The parent will not have this method.
Use super()
when the child class is doing something on its own but still needs something from the parent:
class Person():
def __init__(self, name):
self.name = name
class EmailPerson(Person):
def __init__(self, name, email):
super().__init__(name)
self.email = email
Benefits of inheritance:
- Allows subclasses to reuse code from the parent
- Instead of starting a class from scratch, you can specialize or extend a class
- The parent class can define an interface to allow subclasses to interact with a program
- Allows the programmer to organize related objects
Summary
- A class is a mold (popsicle tray) and an object is created from that class (the popsicle)
- Objects can call instance methods of their class (using self) to receive and change their data
- Privacy naming helps to prevent the accidental overriding of attributes by subclasses
- Classes themselves have methods (using cls) where you can track and manipulate all object instances of that class
- Inheritance allows us to bring similar classes to scale
Comments