Python’s programming approach is centered around objects, a concept that encapsulates data and functionality. In Python, everything you use —be it variables, modules, or figures— is an object. This approach is part of what we call object-oriented programming (OOP), which is useful because it organizes code into small, reusable pieces, making it easier to understand and maintain. Think of objects as mini-programs that have their own properties (like characteristics or data) and methods (functions or actions they can perform).
In Python, objects often have both properties and methods. A property is like an attribute of an object, while a method is a function that belongs to the object and usually performs some action or computation related to the object. Methods are called with parentheses and may take arguments.
Syntax
Syntax for accessing object properties and methods:
object.propertyobject.method()
Syntax for defining our own objects using classes:
class ClassName:def__init__(self, attributes):# Constructor method for initializing instance attributesdef method_name(self, parameters):# Method of the class
ClassName is the name of the class. __init__ is the constructor method. method_name is a method of the class.
Let’s look at these concepts using a simple example and then we will create our own object.
Properties and methods example
I often find that using the NumPy module is a simple and clear way to illustrate the difference between properties/attributes and methods/functions.
Jargon note
The terms property and attribute are used interchangeably to represent characteristics of the object.
Similarly, the term method is an another word to denote a function inside of an object. Methods (or functions) represent actions that can be performed on the object and are only available within a specific object.
import numpy as np# Define an array (this is an object)A = np.array([1, 2, 3, 4, 5, 6])# Properties of the Numpy array objectprint('Properties')print(A.shape) # Dimensions of the arrayprint(A.size) # Total number of elementsprint(A.dtype) # Data typeprint(A.ndim) # NUmber of array dimensions# Methods/Functions of the Numpy array objectprint('') # Add a blank lineprint('Methods')print(A.mean()) # Method to compute average of all valuesprint(A.sum()) # Method to compute sum of all valuesprint(A.cumsum()) # Method to compute running sum of all valuesprint(A.reshape(3,2)) # Reshape to a 3 by 2 matrix
Consider a scenario where we operate a soil analysis laboratory receiving samples from various clients, such as farmers, gardeners, and golf course superintendents. Each soil sample possesses unique attributes that we need to record and analyze. These attributes might include the client’s full name, the date the sample was received, a unique identifier, the location of sample collection, the analyses requested, and the results of these analyses. In this context, the primary unit of interest is the individual sample, and all the additional information represents its metadata.
To efficiently manage this data, we can create a Python class specifically for our soil samples. This class will allow us to create a structured record for each new sample we receive, demonstrating Python’s flexibility in creating custom objects tailored to our specific needs. We will use Python’s uuid module to generate a unique identifier for each sample and the datetime module to timestamp when each sample object is created.
Note
Classes are like blueprints for objects since they define what properties and methods an object will have. For more information check Python’s official documentation about objects and classes
import uuidimport datetimeimport pprintclass SoilSample:""" Class that defines attributes and methods for new soil samples"""def__init__(self, customer_name, location, analyses_requested):"""Attributes of the soil sample generated upon sample entry. Inputs ---------- customer_name : string Customer's full name location : tuple Geographic location (lat,lon) of sample collection analyses_requested : list Requested analyses """self.sample_id =str(uuid.uuid4()) # Unique identifier for each sampleself.timestamp = datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S") # Timestamp of sample entryself.customer_name = customer_name # Customer's full nameself.location = location # Geographic location of sample collectionself.analyses_requested = analyses_requested # List of requested analysesself.results = {} # Dictionary to store results of analysesdef add_results(self, analysis_type, result):"""Function that adds the name and results of a specific soil analysis."""self.results[analysis_type] = result # Add analysis resultsdef summary(self):"""Function that prints summary information for the sample.""" info = (f"Sample ID: {self.sample_id}",f"Timestamp: {self.timestamp}",f"Customer: {self.customer_name}",f"Location: {self.location}",f"Requested Analyses: {self.analyses_requested}",f"Results: {self.results}")return pprint.pprint(info)
What is __init__()?
The __init__() function at the beginning of the class is a special method that gets called (another term for this action is invoked) automatically when we create a new instance of the class. Think of __init__ as the setup of the object, where the initial state of a new object is defined by assigning values to its properties.
What is self?
When defining a class, the word self is used to refer to the instance of the class itself. It’s a way for the class to reference its own attributes and methods and is usually defined with the words self or this (but it can be anything else you want). Typically it is a short word that is meaningful and easy to type. We will use self to match the official Python documentation.
Imagine each class as a blueprint for building a house. Each house built from the blueprint is an instance (an occurrence) of the class. In this context, self is like saying this particular house, rather than the general blueprint.
# Access our own documentationSoilSample?
Init signature: SoilSample(customer_name, location, analyses_requested)Docstring: <no docstring>
Init docstring:
Attributes of the soil sample generated upon sample entry.
Inputs
----------
customer_name : string
Customer's full name
location : tuple
Geographic location (lat,lon) of sample collection
analyses_requested : list
Requested analyses
Type: type
Subclasses:
# Example usage. Create or instantiate a new object, in this case a new sample.new_sample = SoilSample("Andres Patrignani", (39.210089, -96.590213), ["pH", "Nitrogen"])
What does instantiation mean?
Instantiation is the term used in Python for the process of creating a new object from a blueprint, the class we just defined.
# Access properties generated when we created the new sampleprint(new_sample.customer_name)print(new_sample.timestamp)
Andres Patrignani
04-Jan-2024 16:52:03
# Use the add_results() method to add some information to our sample objectnew_sample.add_results("pH", 6.5)new_sample.add_results("Nitrogen", 20)
# Use the summary() method to print the information available for our samplenew_sample.summary()
A cool feature of classes in Python is their ability to inherit properties and methods from an already pre-defined class. This is called inheritance, and it allows programmers to build upon and extend the functionality of existing classes, creating new, more specialized versions without reinventing the wheel. After mastering the basics of classes and objects, exploring class inheritance is a must to take your coding skills to the next level.
Practice
To take this exercise to the next level try the following improvements:
Add one new attribute and one new methods to the class
Use the input() function to request the required data for each sample from users
Use a for loop to pre-populate the results with None for each of the requested soil analyses. This will ensure that only those analyses are entered into the sample.
Create a way to store multiple sample entries. You can simply append each new sample to a variable defined at the beginning of your script, append the new entries to a text or .json file, use the pickle module, or use a databases like sqlite3, MySQL, or TinyDB module