I have a Python module that contains a number of classes, each representing a particular physical material with its properties (e.g., density, specific heat). Some of the properties are just float
members of the class, but many depend on some parameter, e.g., the temperature. I implemented this through @staticmethod
s, i.e., all of the classes look like
class Copper(object):
magnetic_permeability = 1.0
@staticmethod
def density(T):
return 1.0 / (-3.033e-9 + 68.85e-12*T - 6.72e-15*T**2 + 8.56e-18*T**3)
@staticmethod
def electric_conductivity(T, p):
return 1.0141 * T**2 * p
@staticmethod
def specific heat(T):
return ...
class Silver(object):
...
class Argon(object):
...
...
The Class
es thus merely act as containers for all the data, and the abundance of @staticmethod
s has me suspecting that there may be a more appropriate design pattern for this use case.
Any hints?
I suspect that a more fitting structure would be to have a
Material
class, which takes either functions or coefficients as arguments, e.g.Each material then supplies its own coefficients for each calculated parameter:
If you need more complex relationships (e.g. different calculations) you could use sub-classes of
Material
and over-load the appropriate calculations.This is really a "season to taste" question. You could do as you've done -- methods on a class OR you could eliminate the class entirely, and just go with module level functions. In general, I prefer whichever is simpler to read/understand & maintain.
Based strictly on the limited example you shared -- I lean towards module level functions. But of course, a more fleshed out example might alter that opinion.
Defining a staticmethod is virtually always a mistake. Python has functions, so you'd always just define a module-level function. (You'd have
copper.py
and inside of it have a plain olddef density(T):
instead of using a staticmethod.)That is to say,
copper.py
would look likeIn this particular case, do you actually have multiple materials? If so, then you probably want them to be instances, not classes or modules. If you e.g., don't want them all to have the same rational cubic form for the thermal density dependence, you can make a subclass and have an instance of that or you can make a class that accepts functions as arguments.
You can also make a metaclass if you want to maintain a declarative style.
By the way
probably doesn't do what you want to. This makes
magnetic_permeability
only accessible in an instance ofcopper
. I don't recommend using classes rather than instances or modules for this, but if you did, you'd need to doto be able to do
Copper.magnetic_permeability
Note that I'm inheriting with object so that we're using Python 2 "new style classes". The changes are subtle, but it's nicer if you just ensure you'll never run into them.
You could name your module
copper
and create all of these as module level functions, thenimport copper; copper.density(0)
.But what if someone does
from copper import density
, and you also have a module calledcobalt
and another calledcarbon
and another calledchlorine
etc., all with their owndensity
functions? Uh oh.Since we're all consenting adults here, you can document this and expect your users to know well enough to import just the module. Or you can take your approach; in this case, I would consider putting all of your elements in one module called
elements
, then the user canfrom elements import Copper
. Static methods would then be appropriate.How about making the variable properties functions that take all required values as arguments?
Then, the fixed properties could be defined through a dictionary.
You would use this in the following way: