Me:
Dan Sommers:
I think I'd add a change_temperature_to method that accepts the target
temperature and some sort of timing information, depending on how the
rest of the program and/or thread is structured.
Hmmm. I wonder if this is mostly a question of how the
process decomposition occurs. I think in objects ("thingys
with state") whereas you think in tasks ("thingys that do stuff")?
I say that somewhat surprisingly in that I'm not a deep OO
person - functions are fine with me and I don't like the
Java/Ruby "everything is in a class" world view.
It might turn into two
or three methods, depending on whether I can afford to block while
waiting, or what kind of intermediate feedback and/or error handling I
want (or one method with some carefully chosen default arguments).
In the case I'm hypothesizing (haven't worked with a temperature
controller in about 15 years) it's hard to put a layer above
everything because there could be so many different protocols
(chemical protocols, not I/O ones) with different rates. Eg,
it could be an exponential decay, or step-wise approximation
thereof.
The note about errors is interesting. For example, if the
cable to the controller was disconnected then someone, especially
someone used to memory lookups for attributes, might not expect
temp_controller.current
to possibly raise an I/O error exception. On the other hand,
at the C level that could be memory mapped I/O, so that
int x = temp_controller.current;
could also have all sorts of side effects. That case
is dangerous in C because of its poor exception mechanism.
I think in Python it's much less of a problem.
I don't know how that device driver works, but it might look something
like this:
def change_temperature_to( self, target, seconds_between_checks ):
print 'target temperature:', target
tell_the_device_to_change( )
while 1:
current = read_the_temperature_from_the_device( )
print 'current temperature:', current
if abs( current - target ) < 0.1:
break
time.sleep( seconds_between_checks )
Your "tell_the_device_to_change" is my "self.target" and your
"read_the_temperature_from_the_device" is "self.current_temperature".
In some sense it comes down to style.
BTW, had I done this for real I would have two layers, one
which is communications oriented ("send 'get current temperature'
message to device") and my object model which uses the messaging
interface underneath.
I think part of my thinking
comes from my old Pascal days, when it made me cringe to think that
"x:=b;" might actually execute a subroutine rather than just copy some
memory around.
To give a more recent example for me, which I covered here some
years back, I wrote an OO interface to an "OO-ish" C library
for doing chemistry. In the data model, atoms have an atomic
symbol, a charge and a list of bonds (and many other things).
Bonds have a bond type and the two atoms at the ends of the bonds
(and many other things).
I liked being able to say:
print atom.symbol, "with charge", atom.charge, "has", \
len(atom.bonds), "bonds"
for i, bond in enumerate(bonds):
print "bond", i, "has type", bond.bondtype
To me this is a very natural way of querying the data and
traversing the data structure.
Now underneath the covers it looks like this:
atom.charge fails so use __getattr__(atom, "charge")
__getattr__ uses a dispatch table to get the underlying C function
which is "dt_getcharge"
return dt_getcharge(self.handle)
where "handle" is the handle used by the C library.
I figured though that this example might be more esoteric
than my PID controller example, though in retrospect it
looks like it might be a better justification.
Andrew
(e-mail address removed)