Object-oriented Programming in Python

Many chemical engineers and researchers I am familiar with are comfortable using Python (or other languages) from a scripting perspective. The define variables, use published packages, perform calculation and plot results with varying degrees of confidence and ability. This skill opens up a world of possibilities inaccessible using other tools (like Excel), but they often still feel like outsiders in the programming world and fail to understand and take advantage of some of the more useful and powerfull tools available to them in a language like Python.

In particular, I meet a lot of people that are comfortable making their own functions, but have not yet figured out exactly what "classes" and "objects" are and how to make effective use of them. This article is meant to help facilitate that understanding, particularly for chemical engineering students.

Why you should use Classes

I generally need to understand why I need something and where it is useful before I can learn or really figure out a new topic, so I will start out with some examples of when you might be looking to use a class without realizing it.

A classic example is when you have multiple copies of something that are very similar, but not quite the same. This is especially true when there is more than just data involved with each item. You may have found yourself copying sections of code including variables and functions and then changing the names, values and implementations slightly to make the new copy relate to a new idea in your code that is very similar in structure to a previous idea. Your code might look something like this:

# Calculation of the heat of reaction

You should feel at least somewhat uncomfortable with the implementation above. Here is how the same code may be improved using a class:

# Calculation of the heat of reaction

As you can see, the second example makes use of a class to generalize the code relating to a chemical. That way there is no need to keep redefining things at each point. This is conceptually a little more challenging at first, but note some of the improvements:

  1. There is only one place that you need to define the equation. For a very simple problem like this, the effort of redefining an equation is relatively trivial, but when you are working with large equations of state, this can really be a deal-breaker.
  2. When you find an error in an equation, you only need to fix it in one place. This applies to updates in functionality as well. Say modifying a thermodynamics problem from using the ideal gas equation to the full vapor-liquied equilibrium (VLE) equation.
  3. It is very easy to reuse. You might find that it takes a little longed to write out this way at first, but let's say your problem changes a little and you need to perform this operation for 5 more reactions. It will be much faster and less error-prone to expand work with the version that uses a class rather than copy and paste. Now how about if you had twenty reactions in place and you find you need to modify the equation? Using a class become the difference between a 1 minute fix and an half-hour of disorienting updates accross dozens of lines (very error-prone).
  4. It is much easier to reason about and understand. When you show this to a classmate, need help from a TA or come back to this a few weeks later, one of these two implementations is much easier and faster to understand than the other.
  5. It looks like you really know what you are doing. This obviously does not affect how your code functions in the least bit, but it does make a big difference on how you look at yourself and how others look at you. This is often all that it takes to be looked up to as one of the "coding people".

Exactly when a class is useful and when it may be better to stick with a list of dicts or some other structure can be a complex question. Often there is not necessarily a single right answer. Careful thought and experience should help guide the decision.

What are Classes

  • blueprints for making objects

When should I use a Class

  • Matter of debate

Example 1: Encapsulation of data and logic

One place I commonly use classes is in encapsulating data and logic in a program in a reuseable manner. This keeps the global namespace uncluttered and allows the code to be easily portable without excessive overhead.

A particular example I have run into is in implementing an equation of state for ammonia as shown below. In this particular example, there is a lot of embedded data and multiple steps which qould be very messy not to encapsulate. It is certainly true that the following example could be done using a function rather than a class, but using a class still remains a valid option.

import numpy as np

class AmmoniaEOS(object):
    MW = 17.03
    R = 488.20981
    Tc = 406.8
    Pc = 11.627
    rhoc = 237.64
    T0 = 200
    Ta = 500
    tauc = 1.2333498

    A = [
        [-6.453022304053e-3,  -1.371992677050e-2,  -8.100620315713e-3,  -4.880096421085e-3,  -1.202877562682e-2, 6.806345929616e-3],
        [8.080094369688e-6,   1.435692000561e-5,   -4.505297669943e-5,  -1.661889985705e-4,  3.790895022982e-5,  -4.073020833373e-5],
        [1.032994880724e-9,   5.584395580933e-8,   4.920166508177e-7,   1.737835999473e-6,   -3.087491526377e-8, 7.148353041627e-8],
        [-8.948264632008e-12, -1.697777441391e-10, -1.236532371672e-9,  -7.812161168317e-9,  1.779548269140e-12, -3.897461095850e-11],
        [-6.692285882015e-14, -1.75394377532e-15,  2.085533713355e-13,  2.134894661440e-11,  0,                  0],
        [2.473417459954e-16,  2.999839155475e-16,  4.509080578790e-15,  -3.798084988179e-14, 0,                  0],
        [-3.065578854310e-19, 2.411655109855e-20,  -9.323356799989e-18, 4.272409853059e-17,  0,                  0],
        [1.617910033375e-22,  -5.074780704643e-22, 8.139470397409e-21,  -2.745871062656e-20, 0,                  0],
        [-2.782168879368e-26, 2.988129173133e-25,  -2.772597352058e-24, 7.668928677925e-24,  0,                  0]
    ]

    def __Q(self, T, rho):
        q = 0
        for i in range(len(self.A)):
            for j in range(len(self.A[0])):
                q += self.A[i][j] * rho**i * (self.Ta/T - self.tauc) ** j
        return q

    def __dQ(self, T, rho):
        dq = 0
        for i in range(len(self.A)):
            for j in range(len(self.A[0])):
                dq += self.A[i][j] * i * rho**(i-1) * (self.Ta/T - self.tauc) ** j
        return dq

    def P(self, T, rho):
        return rho * self.R * T * (1 + rho * self.__Q(T, rho) + rho**2 * self.__dQ(T, rho))

More Articles: