Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Chapter 6: OOP Fundamentals - Organizing Scientific Code

Foundations for Scientific Programming

Learning objectives

By the end of this chapter, you will be able to:

Prerequisites

Before starting this chapter, verify you can:

Quick diagnostic:

Original: 200
Result: 200

If you said both print “200”, you’re ready! Python uses call-by-sharing: the function receives a reference to the same object, so mutating a mutable object is visible to the caller; rebinding the local parameter is not.

Why this chapter matters

Functions organize calculations; classes can organize calculations that share one object’s state. For example, a simulation particle has a mass, position, and velocity. A class can keep its state, position update, and energy calculation together, with a place to check valid updates.

You will construct objects, use self in methods, and define properties that compute values or validate changes. You will also compare a class with a function-based design. Use a class when shared state and its rules make the interface clearer; a stateless conversion can remain a function. These patterns will help you read scientific objects such as NumPy arrays.

A Particle class blueprint lists mass, position, velocity, methods, and a design goal of positive mass. An instance holds one particle's state and can update its position. The design goal needs an explicit validation check.

Figure 1:A class can keep related state and behavior together, and can enforce rules when their checks are implemented.

What to notice: positive mass is a design goal here. The introductory Particle example below does not check it. A class protects only the paths it validates; even a checked rule cannot establish that the model or measurements are physically adequate.

For a first reading, follow the transformation from a particle dictionary to a small object, then focus on self, method contracts, and properties. Return later to the protocol and design-choice Deep Dives when a class needs to work with Python syntax or you are deciding whether a class is warranted.

Try this chapter


6.1 From Functions to Objects: The Conceptual Leap

Let’s start with a problem you’ve already solved with functions, then transform it into objects to see the difference. In Python, everything is actually an object - even functions and modules! But some objects are more complex than others, and creating your own classes lets you model your specific problem domain.

Energy: 62.5 ergs

Now let’s see the same problem with OOP:

Energy: 62.5 ergs

Both approaches solve the problem, but notice the differences:


6.2 Classes and Objects: Building Blocks

Functions and modules can organize reusable calculations and validate inputs. A class is useful when several operations share state and should enforce the same rules for constructing or updating it.

Organization: modules group related functions; classes group the state and behavior of instances.

Consistency: validation must be written explicitly in either design. A class can centralize it at construction and through controlled updates.

Reuse: functions already support reuse. Choose a class when a shared state contract makes the interface clearer, not merely because calculations are related.

A class is a blueprint for creating objects. An object (or instance) is a specific realization of that blueprint. Think of a class as the concept “thermometer” and objects as specific thermometers in your lab.

Temperature: 293.15 +/- 0.1 K
Pressure: 1.01e+06 +/- 500 dyne/cm^2
Pressure relative error: 0.049%

Understanding self

The self parameter is how each object keeps track of its own data. When you call temp.relative_error(), Python automatically passes temp as the first argument. Here’s what happens behind the scenes:

Counter 1: 2
Counter 2: 1

This seemingly simple concept of bundling data with behavior revolutionized programming. Let me tell you how it started...

Instance vs Class Attributes

Instance attributes belong to specific objects. Class attributes are shared by all instances. This bundling of data and methods is called encapsulation - a core principle of OOP:

Total simulations: 2
Sim1 particles: 1000
Sim2 particles: 5000
Speed of light: 3.00e+10 cm/s
Box size: 1.00e-04 cm

6.3 Methods: Functions Attached to Objects

Methods are functions that belong to a class. They can access and modify the object’s state through self. Let’s build up from simple to complex.

Iron density: 7.87 g/cm^3
Sinks in water: True

Now let’s advance to more complex mathematical methods:

v1 magnitude: 5.0 cm
Dot product: 3 cm^2
Angle: 0.93 radians
After normalization: (0.60, 0.80)
New magnitude: 1.00

Method Types: Instance, Class, and Static

Average: 20.0
Factory average: 3.0
Data valid: True
Temperature: 298.15 K

6.4 Properties: Smart Attributes

A three-by-four rectangle initially has area twelve. After its width changes to six, a stored area of twelve is stale while a newly computed area is twenty-four.

If a derived value is stored separately, an update can leave it inconsistent. Which values should the rectangle store?

Open full-size figure

Properties can enforce declared invariants through a controlled public interface. A circle’s radius must be positive. A temperature can’t go below absolute zero. A probability must be between 0 and 1. Without a controlled setter, any code could set circle.radius = -5 and corrupt your simulation. With one, the class can reject values it explicitly recognizes as invalid:

What properties give you:

  1. Validation on assignment — Reject invalid values immediately, not when they cause cryptic errors downstream

  2. Computed attributes — Derive area from radius automatically, guaranteeing consistency

  3. Encapsulation — Hide internal storage (_radius) while exposing a clean interface (radius)

  4. Invariant enforcement through the public interface — Reject invalid assignments where the class controls updates; this is not a proof that every scientific assumption or internal mutation is valid

A Rectangle instance stores width three and height four. Reading its area property multiplies the current dimensions and returns twelve without storing a second area value.

The rectangle stores validated dimensions and derives area when requested. The following circle applies the same idea to radius and area.

Open full-size figure

Radius: 5 cm
Area: 78.54 cm^2
Circumference: 31.42 cm
New area: 314.16 cm^2
Error: Radius must be a finite positive number, got -5

Properties for Unit Safety

Water at 300.0 K
  = 26.9  degC
  = 80.3  degF
Boiling: 373.1 K

Astronomy-Specific Properties Example

Observing M31:
  Airmass: 1.15
  Moon illumination: 45.9%
  Moon illumination category: Grey

Deep Dive: Python Protocols and Special Methods

Special methods (also called “magic methods” or “dunder methods”) let your objects work with Python’s built-in functions and operators. The phrase “duck typing” is shorthand for a related idea: an operation can depend on the capability an object supplies, rather than on one nominal class alone. Code may still make explicit type checks when a scientific interface needs them.

f1 = 1/2
f1 + f2 = 5/6
f1 as float: 0.5
f1 == Fraction(2,4): True

Essential Special Methods

Length: 5
First: 10
Contains 30: True
Is non-empty: True
10 25 30 40 50 

Deep Dive: Choosing Objects or Functions

Now that you understand HOW to create classes with all their powerful features - properties for validation, special methods for integration, inheritance for code reuse (Chapter 10) - you need wisdom about WHEN to use them. Not every problem needs objects. Creating unnecessary classes can make code harder to understand, not easier. The art of programming lies in choosing the right tool for the right job.

Here’s how to decide. Note: In Chapter 10, we’ll explore the “is-a” relationship (inheritance) versus “has-a” relationship (composition) in detail. For now, focus on single classes.

Use Objects When:

  1. Managing State Over Time

After 1: mean=1.0, sample var=n/a
After 2: mean=1.5, sample var=0.5
After 3: mean=2.0, sample var=1.0
After 4: mean=2.5, sample var=1.7
After 5: mean=3.0, sample var=2.5
  1. Modeling Real Entities

Andromeda: v=-300 km/s
Lookback estimate: 0.14 Gyr

Use Functions When:

  1. Simple Transformations

Temperature: 298.15 K
Period: 1.0 years
  1. Stateless Operations

Mean: 3.0
Population std: 1.41

Reference: Debugging Small Scientific Objects

Understanding how to inspect and debug objects is crucial. Python provides powerful introspection tools to examine objects at runtime:

Type: <class '__main__.Instrument'>
Class name: Instrument
Is Instrument?: True

Has 'calibrate'?: True
Wavelength: 500

Public attributes: ['calibrate', 'name', 'wavelength_nm']

Instance __dict__: {'name': 'HARPS', 'wavelength_nm': 500, '_calibrated': False}
dir() has 31 items (includes inherited)
__dict__ has 3 items (instance only)

Type Hints with Classes (Optional)

Python supports type hints to document expected types, making code clearer:

Observer: J. Smith
Entries: 2
Duration: 2.5 hours

Main Takeaways

Definitions

attribute - A variable that belongs to an object. Instance attributes are unique to each object; class attributes are shared by all instances

class - A blueprint or template for creating objects. Defines what attributes and methods objects will have

class attribute - Data shared by all instances of a class, defined directly in the class body

constructor - The __init__ method that initializes new objects when they’re created

duck typing - A style in which an operation relies on supported behavior or a protocol rather than only a declared class; code may still use explicit type checks when needed

encapsulation - The bundling of data and methods that operate on that data within a single unit (class)

instance - A specific object created from a class. Each instance has its own set of instance attributes

instance attribute - Data unique to each object, defined with self.attribute

method - A function defined inside a class that operates on instances of that class

object - A specific instance of a class containing data (attributes) and behavior (methods)

object-oriented programming - A programming paradigm that organizes code around objects (data) and methods (behavior)

property - A special attribute that executes code when accessed or set, created with the @property decorator

self - The first parameter of instance methods, referring to the specific object being operated on

setter - A property method that validates and sets attribute values, defined with @attribute.setter

special method - Methods with double underscores (like __init__, __str__) that define object behavior for built-in operations

static method - A method that doesn’t receive self or cls, defined with @staticmethod

Reference: Class Syntax, Protocols, and Debugging

Class Definition Syntax

ElementSyntaxExample
Define classclass Name:class Particle:
Constructordef __init__(self):def __init__(self, mass):
Instance attributeself.attr = valueself.mass = 1.67e-24
Class attributeattr = valueSPEED_OF_LIGHT = 3e10
Instance methoddef method(self):def velocity(self):
Property getter@property plus a getter@property + def energy(self):
Property setter@attr.setter plus a setter@energy.setter + def energy(self, value):
Class method@classmethod@classmethod + def from_values(cls, values):
Static method@staticmethod@staticmethod + def validate(data):

Essential Special Methods

MethodPurposeCalled By
__init__Initialize objectMyClass()
__str__Human-readable stringstr(obj), print(obj)
__repr__Developer stringrepr(obj)
__len__Get lengthlen(obj)
__getitem__Get by indexobj[i]
__setitem__Set by indexobj[i] = val
__contains__Check membershipx in obj
__iter__Make iterablefor x in obj
__add__Additionobj1 + obj2
__eq__Equality testobj1 == obj2
__bool__Truth valueif obj:, bool(obj)
__call__Make callableobj()
__abs__Absolute valueabs(obj)
__float__Convert to floatfloat(obj)

When to Use Classes vs Functions

Use Classes WhenUse Functions When
Managing state over timeSimple transformations
Modeling real entitiesStateless operations
Operations belong to dataOne-way data flow
Several operations share one state contractOne calculation validates its inputs
Complex initializationSimple input →\to output
Multiple related methodsSingle operation

Debugging Object Tools

FunctionPurposeExample
type(obj)Get object’s classtype(particle)
isinstance(obj, cls)Check if object is instanceisinstance(p, Particle)
hasattr(obj, 'attr')Check if attribute existshasattr(p, 'mass')
getattr(obj, 'attr')Get attribute safelygetattr(p, 'mass', 0)
setattr(obj, 'attr', val)Set attributesetattr(p, 'mass', 1.0)
dir(obj)List all accessible attributesdir(particle)
vars(obj) or obj.__dict__Get instance attributes onlyvars(particle)
help(obj)Get documentationhelp(Particle)

Recap and next connection

Finish Part I with the Part I Recap — Trustworthy Scientific Programming. It turns Chapters 1–6 into a diagnostic return route when a computation is hard to trust. Then Part II begins with Chapter 7: NumPy Fundamentals. There, arrays provide a different kind of abstraction: many numerical values, their shape, and operations over them. Carry forward the same habits—name assumptions, protect invariants, and test a known case.

References

  1. Mars Exploration Rover Spirit Recovery (2004)

    • NASA JPL. (2004). Mars Exploration Rover Mission: Spirit Anomaly Report. Jet Propulsion Laboratory.

    • Reeves, G., & Neilson, T. (2005). “The Mars Rover Spirit FLASH Anomaly.” IEEE Aerospace Conference Proceedings.

  2. SIMULA and OOP Origins (1960s)

    • Nygaard, K., & Dahl, O. J. (1978). “The development of the SIMULA languages.” ACM SIGPLAN Notices, 13(8), 245-272.

    • Holmevik, J. R. (1994). “Compiling SIMULA: A Historical Study of Technological Genesis.” IEEE Annals of the History of Computing, 16(4), 25-37.

  3. Therac-25 Radiation Accidents (1985-1987)

    • Leveson, N.G. & Turner, C.S. (1993). “An Investigation of the Therac-25 Accidents.” IEEE Computer, 26(7), 18-41.

  4. Hubble Space Telescope Mirror Error (1990)

    • Allen, L. (1990). The Hubble Space Telescope Optical Systems Failure Report. NASA-TM-103443.

    • Chaisson, E. (1994). The Hubble Wars. New York: HarperCollins. ISBN 0-06-017114-6.

  5. Mars Climate Orbiter Loss (1999)

    • Stephenson, A. G. et al. (1999). Mars Climate Orbiter Mishap Investigation Board Report. NASA.

    • Oberg, J. (1999). “Why the Mars Probe Went Off Course.” IEEE Spectrum, 36(12), 34-39.

  6. NumPy Design Decisions (2005)

    • Oliphant, T. E. (2006). A guide to NumPy (Vol. 1). USA: Trelgol Publishing.

    • Van Der Walt, S., Colbert, S. C., & Varoquaux, G. (2011). “The NumPy array: a structure for efficient numerical computation.” Computing in Science & Engineering, 13(2), 22-30.

  7. Astropy Unification (2011-2013)

    • Robitaille, T., et al. (2013). “Astropy: A community Python package for astronomy.” Astronomy & Astrophysics, 558, A33.

    • Greenfield, P. (2011). “What Python Can Do for Astronomy.” Proceedings of the 20th Annual Python in Science Conference.

  8. Python Language Design

    • Van Rossum, G. (1996). Foreword for “Programming Python” (1st ed.). O’Reilly Media.

    • Van Rossum, G., & Drake, F. L. (2009). Python 3 Reference Manual. CreateSpace.

    • Martelli, A. (2000). “The Python ‘Duck Typing’ Principle.” comp.lang.python newsgroup archives.

  9. Object-Oriented Design Principles

    • Kay, A. (1993). “The early history of Smalltalk.” ACM SIGPLAN Notices, 28(3), 69-95.

    • Kay, A. (2003). Email correspondence to Stefan Ram on the definition of object-oriented programming.

  10. Python OOP Resources

    • Lutz, M. (2013). Learning Python (5th ed.). O’Reilly Media.

    • Ramalho, L. (2015). Fluent Python. O’Reilly Media.

    • Beazley, D., & Jones, B. K. (2013). Python Cookbook (3rd ed.). O’Reilly Media.