Classes, Objects, Package Structure

Software Development for Engineering Research


Kyle Niemeyer. 13 Jan 2022

ME 599, Corvallis, OR

Classes: organize data, methods, and functions

Objects: manifestations of classes

We can use object-oriented programming to cleanly organize behaviors and data in our code.

Object Orientation


It provides a framework for classifying distinct concepts into comprehensible sizes. These smaller conceptual units facilitate cleaner, more scalable modeling.

Object Orientation


  • Classes and objects combine functions with data to make both easier to manage.
  • A class defines the behaviors of a new kind of thing, while an object is a particular thing.
  • Classes have constructors that describe how to create a new object of a particular kind.
  • An interface describes what an object can do; an implementation defines how.
  • One class can inherit from another and override just those things that it wants to change.

Object Orientation


  • Encapsulation is the property of owning data
  • Inheritance establishes a relationship hierarchy between models
  • Polymorphism allows for models to customize their own behavior even when they are based on other models

Objects


Everything in Python is an object.

All objects in Python have attributes and methods.

Object example: integers



                            a = 1
                            help(a) # gives info about the object
                            dir(a) # gives list of data and behaviors associated with class
                        

What do double underscores around __abs__ mean?

__ = "dunder". From PEP8: "magic objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented."

Generally don't call dunder methods directly; instead, use built-in functions.

... what about functions?


                            import math
                            dir(math.sin)
                        

Even functions are objects!

Head explode gif

What about classes?


Classes define logical collections of attributes and methods describing a kind of object, and how to create objects of that kind.

Choose classes to encapsulate internal data and functions for different types of objects.

Example: particle physics



                            class Particle(object):
                                """A particle is a constituent unit of the universe."""
                                # class body definition here
                        

What else do classes include?

  • Class variables
  • Constructors
  • Methods

Elementary Particles

The Standard Model of Elementary Particles (source: Wikipedia)

Class variables


Data universally applicable to all objects of the class


                            # contents of particle.py
                            class Particle(object):
                                """A particle is a constituent unit of the universe."""
                                # class body definition here
                                roar = "I am a particle!"
                        

                            # import particle module
                            import particle as p
                            print(p.Particle.roar)
                        

                            # create instance of Particle
                            import particle as p
                            higgs = p.Particle()
                            print(higgs.roar)
                        

Instance variables


Data with different values for each instance of the class

Example: particle position.


                            import particle as p
                            # create empty list to hold observed particle data
                            obs = []
                            # append first particle
                            obs.append(p.Particle())
                            # assign its position
                            obs[0].r = {'x': 100.0, 'y': 38.0, 'z': -42.0}
                            # append second particle and assign its position
                            obs.append(p.Particle())
                            obs[1].r = {'x': 0.01, 'y': 99.0, 'z': 32.0}
                            # print positions of each particle
                            print(obs[0].r)
                            print(obs[1].r)
                        

Using instance variables, we can store all data associated with a particle observation (position, mass, charge, spin, etc.) without much more complexity

(Hopefully the value of this reduced complexity is obvious.)

How to accomplish in class definition? Constructor: __init()__ function.

Constructors


Constructor: __init__() function, executed upon instantiation of object.

Constructor not required; every class inherits default constructor from object.

Tip: good to initialize all instance variables in constructor, to ensure they are initialized when you need them.


                            # particle.py
                            class Particle(object):
                                """A particle is a constituent unit of the universe.

                                Attributes
                                ----------
                                c : charge in units of [e]
                                m : mass in units of [kg]
                                r : position in units of [meters]
                                """

                                roar = "I am a particle!"

                                def __init__(self):
                                    """Initializes the particle with default values for charge c, mass m, and position r.
                                    """
                                    self.c = 0
                                    self.m = 0
                                    self.r = {'x': 0, 'y': 0, 'z': 0}
                        

self argument required since function is method; binds to specific instance of the class.

More efficient: specify data values upon initialization.


                            # particle.py
                            class Particle(object):
                                """A particle is a constituent unit of the universe.

                                Attributes
                                ----------
                                c : charge in units of [e]
                                m : mass in units of [kg]
                                r : position in units of [meters]
                                """

                                roar = "I am a particle!"

                                def __init__(self, charge, mass, position):
                                    """Initializes the particle with supplied values for charge c, mass m, and position r.
                                    """
                                    self.c = charge
                                    self.m = mass
                                    self.r = position
                        

Methods

Methods: functions tied to a class definition; may operate on data contained by object.


                            # particle.py
                            class Particle(object):
                                """A particle is a constituent unit of the universe.

                                Attributes
                                ----------
                                c : charge in units of [e]
                                m : mass in units of [kg]
                                r : position in units of [meters]
                                """

                                roar = "I am a particle!"

                                def __init__(self, charge, mass, position):
                                    """Initializes the particle with supplied values for charge c, mass m, and position r.
                                    """
                                    self.c = charge
                                    self.m = mass
                                    self.r = position

                                def hear_me(self):
                                    """Print information about particle.
                                    """
                                    myroar = self.roar + (
                                        " My charge is:     " + str(self.c) +
                                        " My mass is:       " + str(self.m) +
                                        " My x position is: " + str(self.r['x']) +
                                        " My y position is: " + str(self.r['y']) +
                                        " My z position is: " + str(self.r['z']))
                                    print(myroar)
                        

Example: proton


                            from scipy import constants
                            import particle as p

                            m_p = constants.m_p
                            r_p = {'x': 1, 'y': 1, 'z': 53}
                            a_p = p.Particle(1, m_p, r_p)
                            a_p.hear_me()
                        

Methods can alter instance variables. Example: Quark class with instance variable flavor.


                            def flip(self):
                                """Flip quark's flavor to complementary flavor.
                                """
                                if self.flavor == "up":
                                    self.flavor = "down"
                                elif self.flavor == "down":
                                    self.flavor = "up"
                                elif self.flavor == "top":
                                    self.flavor = "bottom"
                                elif self.flavor == "bottom":
                                    self.flavor = "top"
                                elif self.flavor == "strange":
                                    self.flavor = "charm"
                                elif self.flavor == "charm":
                                    self.flavor = "strange"
                                else:
                                    raise AttributeError("The quark cannot be flipped, because the flavor is invalid.")
                        

                            from quark import Quark

                            t = Quark()
                            t.flavor = "top"
                            t.flip()
                            print(t.flavor)
                        

Particle capture relationship between uncertainty in momentum and uncertainty in position:


                            from scipy import constants

                            class Particle(object):
                                """A particle is a constituent unit of the universe.

                                # ... other parts of class definition ...

                                def delta_x_min(self, delta_p_x):
                                    """Returns minimum possible value of Δx
                                    """
                                    hbar = constants.hbar
                                    delx_min = hbar / (2.0 * delta_p_x)
                                    return delx_min
                        

Static Methods

Example: Quark class can include function that lists all possible values of flavor; possible values are static irrespective of specific instance.


                            def possible_flavors():
                                return ["up", "down", "top", "bottom", "strange", "charm"]
                        

Use @staticmethod decorator to define a method not bound to object.


                            class Particle(object):
                                """A particle is a constituent unit of the universe.
                                """

                                # ... other parts of class definition ...

                                @staticmethod
                                def possible_flavors():
                                    return ["up", "down", "top", "bottom", "strange", "charm"]
                        

Duck Typing

“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”

Meaning: Python does not explicitly check for object types like other languages. It only checks for behavior when a method is called or attribute accessed.

If different object types both "quack like a duck", then it treats them like a duck. An object does not need to be of a certain type in order for its methods to be invoked; must merely possess those method.

Example: all particles with a valid c attribute for charge can be used identically.


                            def total_charge(particles):
                                """Calculate the total charge of a collection of particles.
                                """
                                tot = 0
                                for p in particles:
                                    tot += p.c
                                return tot
                        

                            p = Proton()
                            e1 = Electron()
                            e2 = Electrion()
                            particles = [p, e1, e2]
                            total_charge(particles)
                        

                            # overrule duck typing when inconvenient
                            def total_charge(particles):
                                """Calculate the total charge of a collection of particles.
                                """
                                tot = 0
                                for p in particles:
                                    if isinstance(p, Particle):
                                        tot += p.c
                                return tot
                        

Polymorphism

When class inherits attributes of a parent class. General rule: what works for parent class should work for subclass (plus specialized behavior).


                            # elementary.py
                            class ElementaryParticle(Particle):
                                """No distinct constituent particles, have spin.
                                """

                                def __init__(self, spin):
                                    self.s = spin
                                    self.is_fermion = bool(spin % 1.0)
                                    self.is_boson = not self.is_fermion
                        

                            # composite.py
                            class CompositeParticle(Particle):
                                """Particles like protons and neutrons, composed of elementary particles
                                but don't share attributes.
                                """

                                def __init__(self, parts):
                                    self.constituents = parts
                        

Subclasses

ElementaryParticle and CompositeParticle are Particle objects, and thus have (inherit) all of the functions and data of the original class.

Can override that data and those behaviors if desired.


                            # elementary.py
                            class ElementaryParticle(Particle):
                                """No distinct constituent particles, have spin.
                                """
                                roar = "I am an Elementary Particle!"

                                def __init__(self, spin):
                                    self.s = spin
                                    self.is_fermion = bool(spin % 1.0)
                                    self.is_boson = not self.is_fermion
                        

                            from elementary import ElementaryParticle
                            p = ElementaryParticle(1.5)
                            p.s
                            p.hear_me()
                        

Superclasses

Any class, including a subclass, can be a superclass or parent class; the subclass inherits from its parent.

ElementaryParticle can also be a superclass:


                            class Quark(ElementaryParticle):
                                """No distinct constituent particles, have spin.
                                """

                                def __init__(self, color, charge, color_charge, spin, flavor):
                                    self.color = color
                                    self.charge = charge
                                    self.color_charge = color_charge
                                    self.spin = spin
                                    self.flavor = flavor
                        

Inheritance of class contructors

Best/most Pythonic way of handling inherited + additional constructor arguments: be explicit.


                            # particle.py
                            class Particle(object):
                                """A particle is a constituent unit of the universe.
                                """

                                roar = "I am a particle!"

                                def __init__(self, charge, mass, position):
                                    """Initializes the particle with supplied values for charge c, mass m, and position r.
                                    """
                                    self.c = charge
                                    self.m = mass
                                    self.r = position

                                def hear_me(self):
                                    """Print information about particle.
                                    """
                                    myroar = self.roar + (
                                        " My charge is:     " + str(self.c) +
                                        " My mass is:       " + str(self.m) +
                                        " My x position is: " + str(self.r['x']) +
                                        " My y position is: " + str(self.r['y']) +
                                        " My z position is: " + str(self.r['z']))
                                    print(myroar)
                        

                            # elementary.py
                            class ElementaryParticle(Particle):
                                """No distinct constituent particles, have spin.
                                """
                                roar = "I am an Elementary Particle!"

                                def __init__(self, charge, mass, position, spin):
                                    super().__init__(charge, mass, position)
                                    self.s = spin
                                    self.is_fermion = bool(spin % 1.0)
                                    self.is_boson = not self.is_fermion
                        

Metaprogramming

When definition of a class or function is specified (in part or in full) by code outside the definition itself.

Example: add an is_particle class attribute to Particle class:


                            def add_is_particle(cls):
                                cls.is_particle = True
                                return cls

                            @add_is_particle
                            class Particle(object):
                                """A particle is a constituent unit of the universe.

                                # ... other parts of class definition ...
                        

Practical Example


Take a look at my code PyTeCK, which uses classes to hide lots of details of performing simulations: https://github.com/kyleniemeyer/PyTeCK

(Python) packaging


package: a collection of modules in the same directory


Package directory must contain __init__.py for Python to "see" it

compphys package structure:



                            compphys/
                            |-- __init__.py
                            |-- constants.py
                            |-- physics.py
                            |-- more/
                            |   |-- __init__.py
                            |   |-- morephysics.py
                            |   |-- evenmorephysics.py
                            |-- assets/
                            |   |-- data.txt
                            |   |-- orphan.py
                            |-- tests/
                            |   |-- test_physics.py
                            |   |-- test_morephysics.py
                            ...
                        

compphys contents:


  • __init__.py: tells Python this is a package; does not need any contents. Executed first before any other modules imported.
  • Three modules: __init__.py, constants.py, and physics.py
  • more is a submodule
  • raw is just a subdirectory; not a submodule since it doesn't have __init__.py (orphan.py is unreachable!)

Importing submodules


Use attribute access operator: .


                            import compphys.constants
                            import compphys.more.morephysics

                            two_pi = 2 * compphys.constants.pi
                        

These are absolute imports

Explicit relative imports


From physics.py:


                            from . import constants
                            from .constants import pi, h
                            from .more import morephysics
                        

From evenmorephysics.py:


                            from . import morephysics
                            from .. import constants
                            from ..constants import pi, h
                        

Important: while easy to create new functions and modules, don't reinvent the wheel!


Rely on the Python standard library, NumPy, SciPy, etc. as much as you need.

Useful modules in Python standard library:


os Operating system abstractions: file path operations, file removal
sys System-specific; gets into Python interpreter
math Common mathematical functions and constants
re Regular expressions library
subprocess Spawns child processes and shells; good for running other command-line tools
argparse Creates easy and beautiful command-line utilities
collections Advanced collection types and tools
random Pseudo-random number generators
csv Tools for reading/writing comma-separated value files

Useful third-party packages for computing:


numpy Essential library for creating and manipulating numerical data
scipy High-level scientific computing library
matplotlib Plotting with Python
sympy Symbolic mathametics computation library
pandas Python data structures and analysis library
pint Package for parsing, defining, and working with units
ipython Interactive computing shell for Python
wxPython cross-platform GUI toolkit for Python
scikit-learn Python-based machine learning library
numba Write high-performance functions in Python