可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
You could name your module copper
and create all of these as module level functions, then import copper; copper.density(0)
.
But what if someone does from copper import density
, and you also have a module called cobalt
and another called carbon
and another called chlorine
etc., all with their own density
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 can from elements import Copper
. Static methods would then be appropriate.
回答2:
I suspect that a more fitting structure would be to have a Material
class, which takes either functions or coefficients as arguments, e.g.
class Material(object):
def __init__(self, mag_perm, density_coeffs, ...):
self.mag_perm = mag_perm
self._density_coeffs = density_coeffs
...
def density(self, T):
x0, x1, x2, x3 = self._density_coeffs
return 1.0 / (x0 + (x1 * T) + (x2 * (T ** 2)) + (x3 * (T ** 3)))
Each material then supplies its own coefficients for each calculated parameter:
copper = Material(1.0, (-3.033e-9, 68.85e-12, 6.72e-15, 8.56e-18), ...)
copper.density(300)
If you need more complex relationships (e.g. different calculations) you could use sub-classes of Material
and over-load the appropriate calculations.
回答3:
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 old def density(T):
instead of using a staticmethod.)
That is to say, copper.py
would look like
magnetic_permeability = 1.0
def density(T):
return 1.0 / (-3.033e-9 + 68.85e-12*T - 6.72e-15*T**2 + 8.56e-18*T**3)
def electric_conductivity(T, p):
return 1.0141 * T**2 * p
def specific heat(T):
return ...
In 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.
class Material(object):
def __init__(self, density, electric conductivity):
self.density = density
self.electric_conductivity = electric_conductivity
copper = Material(
density=lambda T: 1.0 / (-3.033e-9 + 68.85e-12*T -
6.72e-15*T**2 + 8.56e-18*T**3),
electric_conductivity=lambda T, p: 1.0141 * T**2 * p
)
You can also make a metaclass if you want to maintain a declarative style.
By the way
class Copper():
def __init__(self):
self.magnetic_permeability = 1.0
...
probably doesn't do what you want to. This makes magnetic_permeability
only accessible in an instance of copper
. I don't recommend using classes rather than instances or modules for this, but if you did, you'd need to do
class Copper(object):
magnetic_permeability = 1.0
...
to 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.
回答4:
How about making the variable properties functions that take all required values as arguments?
def density(T):
<some function of T>
def electrical_conductivity(T, p):
<some function of T and p>
def some_other_property(T, magnetic_permeability):
<some function of T and magnetic permeability>
Then, the fixed properties could be defined through a dictionary.
copper_fixed_properties = {'magnetic_permeability': 1, ...}
You would use this in the following way:
copper_some_other_property = some_other_property(T, copper.magnetic_permeability)
回答5:
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.