3. Class Anatomy: Attributes and Methods#

Let’s create our own class and object with attributes and methods

3.1. Basic class#

To start a new class definition, all we need is class statement, followed by the name of the class, followed by a colon.

class Customer:
    #  code inside `class` is indented
    pass    # use pass to create an "empty" class

Even though the above class Customer is empty, we can already create objects of the class by telling the name of the class, followed by parentheses.

customer1 = Customer()
customer2 = Customer()

customer 1 and customer 2 are two different objects of the empty class Customer.

3.2. Adding Methods to a Class#

How do we define methods? By creating functions inside the class.

Important

There is one exception! The special self argument needs to be created as the first argument for every method, possibly followed by other arguments.

Let’s create a method called identify for the Customer class that takes self and name as parameters, and finally print a message, when called.

class Customer:

    def identify(self,name):
        print(" Hi, I am Customer " + name)

Now, let’s create a new customer object, and call the method identify by using object-dot-method syntax and pass the desired name, to finally get the output.

customer1 = Customer()
customer1.identify("Fernando C. Pacheco")

Hi, I am Customer Fernando C. Pacheco

3.3. What is self?#

Classes are templates. Objects of a class don’t yet exist when a class is being defined, but we often need a way to refer to the data of a particular object within class definition.

The purpose of self is to serve as a stand-in for the future object.

Python takes care of self when methods are called from an object:

customer1.identify("Fernando")

The above will be interpreted as:

Customer.identify(customer1, "Fernando")

3.4. Add Attributes to Class#

Customer name should be an attribute of a customer object, instead of a parameter passed to a method.

class Customer:
    # Set the `name` attribute of an object to `new_name`
    def set_name(self,new_name):
        # Create an attribute by assigning a value
        self.name = new_name      # this will create `.name` when set_name is called
    
    def identify(self):
        print(" Hi, I am Customer " + self.name)


customer1 = Customer()
customer1.set_name("Fernando")      # <--- `.name` is created and set to "Fernando"

print(customer1.name)               # <--- `.name` can now be used =)
  • Let’s compare the codes:

    Old Version:
    class Customer:
        
        
        
        
        def identify(self,name):
            print(" Hi, I am Customer " + name)
    
    New Version:
    class Customer:
        
        def set_name(self,new_name):
            self.name = new_name 
        
        # Use `.name` from the object it*self* 
        def identify(self):
            print(" Hi, I am Customer " + self.name)
    

    We removed the name parameter from the identify method, and replaced it with self-dot-name in the printout, which uses self, to pull the name attribute from the object that called the method.

    customer1 = Customer()
    customer1.set_name("Fernando") 
    customer1.identify()
    

3.5. The __init__ constructor#

  • We have been defining and calling methods one after another. This could become unsustainable if our classes contain a lot of data.

  • A better way to deal with that would be to add data to the object when creating it. Python allows us to add a special method called the constructor –> __init__() that is automatically called every time an object is created.

    class MyClass:
        def __init__(self,attr1,attr2,attr3):
            self.attr1 = attr1
            self.attr2 = attr2
            self.attr3 = attr3
    
    # All attributes are created at the same time
    obj = MyClass(value1, value2, value3)
    
  • Defining all attributes in the constructor ensures that all of them are created when the object is created, so you don’t have to worry about trying to access an attribute that doesn’t yet exist.

  • All this results in more organized, readable, and maintainable code.

3.6. Best practices#

  1. Initialize attributes in __init__()

  2. Naming:

    1. Classes: CamelCase

      1. If your class name has several words, they should be written without delimiters.

      2. Each word should start with a capital letter.

    2. Functions & Attributes : lower_snake_case

      1. Words should be separated by underscores and start with lowercase letters.

  3. Keep self self

  4. Use docstings

    1. Classes allow you to create docstrings which are displayed when help() is called on the object.

      class Myclass:
          """This class does nothing but has docstring =) """
          passs
      

3.6.1. Example - Class constructor#

class Employee:
    """This is a docstring version for the `Employee` class."""
  
    # Create __init__() method
    def __init__(self, name, salary=0):
        # Create the name and salary attributes
        self.name = name
        self.salary = salary
    
    def give_raise(self, amount):
        self.salary += amount

emp = Employee("Fernando C. Pacheco")
print(emp.name)
print("Current salary: " + str(emp.salary))
emp.give_raise(1500)
print("Current salary: " + str(emp.salary))
emp.give_raise(3000)
print("Current salary: " + str(emp.salary) + " LETSSSS GOOOOOOOO GIVE ME MORE ")

3.6.2. Example - Class constructor 2#

import datetime 

class Person:

    def __init__(self, name, surname, birthdate, address, telephone, email):
        self.name = name
        self.surname = surname
        self.birthdate = birthdate

        self.address = address
        self.telephone = telephone
        self.email = email

    def age(self):
        today = datetime.date.today()
        age = today.year - self.birthdate.year

        return age

person = Person(
    "Filipe",
    "Toledo",
    datetime.date(1995, 4, 16), # year, month, day
    "No. 77 Bells Beach, Australia",
    "777 777 7777",
    "filipe.toledo@bells-champion.com"
)

print(person.name)
print(person.email)
print(person.age())