Classes and Object-Oriented Programming in Python
Procedural and Object-Oriented Programming
Chapter of Starting Out with Python, Sixth Edition, provides a comprehensive overview of Classes and Object-Oriented Programming, with copyrights held by Pearson Education, Inc. in , , and . The material begins by distinguishing between procedural and object-oriented programming paradigms. Procedural programming is defined as the practice of writing programs consisting of functions that perform specific tasks. In this model, procedures typically operate on data items that are kept separate from the procedures themselves. Data items are commonly passed from one procedure to another, and the primary focus of the developer is to create procedures that operate effectively on the program’s data.
Object-oriented programming (OOP), by contrast, is focused on the creation of objects. An object is defined as an entity that contains both data and procedures. The data within an object is referred to as data attributes, while the procedures are known as methods. Methods are responsible for performing operations on the data attributes. A fundamental concept in OOP is encapsulation, which refers to the combining of data and code into a single, cohesive object. This structure is visualized in Figure , illustrating that an object serves as a container for both its data attributes and its methods.
Core Principles of Object-Oriented Programming
One of the primary advantages of object-oriented programming is data hiding. This concept ensures that an object’s data attributes are hidden from any code residing outside the object itself. Access to these attributes is strictly restricted to the object’s own methods. This restriction serves two main purposes: it protects the data from accidental corruption by external code, and it allows external code to interact with the object without needing to understand the object's complex internal structures. This interaction is depicted in Figure , which shows outside code communicating specifically with the object's methods rather than the data directly.
Another significant benefit is object reusability. A well-designed object can be used across different programs without modification. For instance, a image object created for an architectural visualization program could be reused in a game programming context. In everyday terms, an object can be understood through the example of a clock. The state of the clock is defined by its data attributes, such as the current second, minute, and hour. The clock provides public methods, such as \text{set_time} and \text{set_alarm_time}, which allow external code to manipulate it, while also utilizing private methods for its internal mechanical or logical workings.
Classes and Instances
The relationship between a class and an object is foundational to OOP. A class is the code that specifies the data attributes and methods for a particular type of object; it acts as a blueprint, similar to the architectural plans for a house or a cookie cutter. An instance is a specific object created from that class, much like a physical house built from a blueprint or an individual cookie cut from a mold. As shown in Figure , many distinct houses can be built from a single blueprint. Similarly, Figure demonstrates that different entities, such as a housefly and a mosquito, can both be instances of a single class.
Defining Classes and the Initializer Method in Python
A class definition is a set of statements that define the methods and data attributes for the class. In Python, this begins with the keyword followed by the \text{Class_name}, which by convention typically starts with an uppercase letter. Every method definition within a class is similar to a standard function definition but must include the parameter. This parameter is a reference to the specific object that the method is currently working on.
The initializer method, denoted as \text{__init__ (self)}, is a special method that executes automatically whenever a new instance of a class is created. Its primary purpose is to initialize the object's data attributes and assign the parameter to the newly created object. For example, a class might import the module and define an \text{__init__} method where is initially set to the string 'Heads'. The class could also include a method that uses to determine if should be 'Heads' (if the result is ) or 'Tails' (otherwise). A \text{get_sideup(self)} method would then simply return the value currently stored in .
Working with Instances and Dot Notation
To create a new instance of a class, the format \text{variable_name = ClassName()} is used. For example, \text{my_coin = Coin()} creates an instance and triggers the \text{__init__} method. This process is detailed in Figure . Once the instance exists, class methods are called using dot notation in the format \text{My_instance.method()}. Because the parameter automatically references the specific instance, the method impact is limited to that instance. In a code sequence where \text{my_coin = Coin()} is followed by \text{my_coin.toss()} and \text{print(my_coin.get_sideup())}, Line creates the object and assigns its reference to \text{my_coin}, Line calls the toss method to change the state, and Line retrieves and displays the current state.
Hiding Attributes and Accessor/Mutator Methods
To ensure data attributes remain private and protected from outside code, Python developers use two underscores as a prefix for attribute names, such as \text{__sideup}. Classes can be stored in external modules, which are files ending in , allowing them to be imported into any program. These classes can have complex methods, such as in a class where \text{__init__} might accept a starting balance and a method might accept an parameter to update the \text{__balance} attribute.
An object's state reflects the values of its attributes at a specific moment. The \text{__str__} method is a special function used to display this state; it is called automatically whenever an object is passed to the or functions. Furthermore, instance attributes belong to specific instances. If multiple instances of a class are created (such as , , and in Figure ), each maintains its own unique set of attributes as seen in Figure . Accessor methods, or getters, are used to safely retrieve attribute values without changing them, while mutator methods, or setters, are used to store or modify these values.
Designing Classes and the Unified Modeling Language
When passing an object as an argument to a function or method, a reference to the object is passed, allowing the receiving code to access and modify the actual object through its methods. For designing these systems, the Unified Modeling Language (UML) provides a standard graphical format. As seen in Figure , a UML diagram for a class is a box divided into three sections: the top section contains the class name, the middle section lists the data attributes, and the bottom section lists the methods. Figure shows this applied specifically to the class.
Identifying classes during development involves analyzing the problem domain—the written description of the real-world objects or processes the program will simulate. This domain description can include physical objects, roles played by people, business event results, or recordkeeping items. To identify potential classes, a developer identifying all nouns, noun phrases, and pronouns in the description and then refines the list. Refining involves removing synonyms, irrelevant items, objects that aren't classes, and simple values that can be served by standard variables. Finally, the developer identifies a class's responsibilities: what it must "know" (its data attributes) and what it must "do" (its methods), based on the requirements of the problem domain.