output formatting for user-defined types

R

Russ

I'd like to get output formatting for my own classes that mimics the
built-in output formatting. For example,

4.54

In other words, if I substitute a class instance for "x" above, I'd
like to make the format string apply to an element or elements of the
instance. Is that possible? If so, how? Thanks.
 
P

Peter Hansen

Russ said:
I'd like to get output formatting for my own classes that mimics the
built-in output formatting. For example,




4.54

In other words, if I substitute a class instance for "x" above, I'd
like to make the format string apply to an element or elements of the
instance. Is that possible? If so, how? Thanks.

I believe implementing the special method __float__ on your class
should be sufficient.

class X:
def __float__(self):
return 3.14
3.140000

-Peter
 
R

Russ

Thanks, but that is not acceptable for my application. Any other ideas?

I thought I might be able to overload __rmod__, but apparently python
applies the % operator before __rmod__ is even invoked if the left-hand
argument is a string.
 
P

Peter Hansen

Russ said:
Thanks, but that is not acceptable for my application. Any other ideas?

Yeah, how about we read your mind or make wild guesses about why it's
not acceptable, and about what your requirements really are.

Really, your response seems a little bizarre to me, given that __float__
is the defined way in which float() gets a value from an instance, and
float() is what the % operator calls when it encounters a '%f' in the
format string.

-Peter
 
R

Russ

Yeah, how about we read your mind or make wild guesses about why it's
not acceptable, and about what your requirements really are.
Really, your response seems a little bizarre to me, given that __float__
is the defined way in which float() gets a value from an instance, and
float() is what the % operator calls when it encounters a '%f' in the
format string.

My class represents physical scalars with units. If I allow arbitrary
conversion to float, then the user can pass any units to trig
functions. But trig functions expect radians, so passing any other unit
(e.g., degrees or meters) is an error. To prevent such errors, I allow
conversion to float only when the units are actually radians (or
dimensionless). That is just one of several reasons for preventing
arbitrary conversion to float, but I won't go into the others here.

By the way, I realize that several other classes have been developed
for representing physical scalars, but I think mine is unique in that
it allows the user to easily disable the units for more efficient
"production runs" after testing is complete. This can increase
efficiency by two orders of magnitude.

I announced my application a week or two ago on comp.lang.announce.
Anyone who is interested can download the code and user guide at
http://RussP.us/scalar.htm

Check it out. I bet you'll like it.
 
S

Steven D'Aprano

My class represents physical scalars with units. If I allow arbitrary
conversion to float, then the user can pass any units to trig
functions. But trig functions expect radians, so passing any other unit
(e.g., degrees or meters) is an error.

I'm sorry, your system of units doesn't allow trig functions to operate on
degrees? I presume you don't allow grads either. What about steradians or
other arbitrary measures of angle or solid angle?

Sounds like an awfully limited system of units to me.

To prevent such errors, I allow
conversion to float only when the units are actually radians (or
dimensionless). That is just one of several reasons for preventing
arbitrary conversion to float, but I won't go into the others here.

Well, here are three reasons for allowing it:

(1) It will make your job as programmer easier.

(2) It will be useful for people to take one of your units, convert it to
a float (or extract the scalar part), pass that to some other module's
function, then convert the answer back to the appropriate unit.

(3) You're not my mother. If I want to shoot myself in the foot by
extracting the scalar part of one of your precious units and then doing
inappropriate things to it, that's absolutely none of your business.


I suggest another approach: play nice with the rest of Python by allowing
people to convert your units into strings and floats. Once they have
explicitly done so, it isn't your problem if they want to add 35 metres to
18 kilograms and convert the answer into minutes.
 
R

Russ

I'm sorry, your system of units doesn't allow trig functions to operate on
degrees? I presume you don't allow grads either. What about steradians or
other arbitrary measures of angle or solid angle?

I should have stated that more clearly. You can enter the value in
degrees, but it will automatically get converted to radians for
internal use. When you pass it to a trig function, it will
automatically be in radians. For example:
0.5

You could force it to maintain degrees internally if you wanted to, but
then you couldn't pass it to a trig function. That little feature
prevents perhaps the most common unit error, passing degrees when
radians are expected.

I once heard about a simulation that was run for six months before the
researchers realized that their results were corrupted by such an
error. Yes, that's an extreme case, and maybe they were nitwits (with
Ph.Ds), but these errors are very commonplace and often costly in terms
of corrupted results or debugging time. And who knows how many times
such errors subtly corrupted results but were never even detected?

If you want to print it out in degrees, you must explicitly specify
degrees, then it will get converted to degrees for output. For example:
30.00 deg

That may seem inconvenient, but it actually helps when the units are
turned off for efficiency, because then the units are still known and
can be printed out explicity.
(3) You're not my mother. If I want to shoot myself in the foot by
extracting the scalar part of one of your precious units and then doing
inappropriate things to it, that's absolutely none of your business.

I am developing a way to guard against common errors in scientific and
engineering software. If you want to use it, then don't. I work in air
traffic control, so I am very concerned about such errors. [No, we
don't use Python for actual operational ATC, but I use it for
prototyping and data analysis. Nevertheless, I believe in avoiding
errors anyway.]
I suggest another approach: play nice with the rest of Python by allowing
people to convert your units into strings and floats. Once they have
explicitly done so, it isn't your problem if they want to add 35 metres to
18 kilograms and convert the answer into minutes.

Converting to a float is a trivial matter of dividing by the units of
the scalar. For example:
4

Note, however, that this requires the user to explicity ask for the
conversion. What is unwise is to allow the conversion to happen
automatically without the user's awareness. That's where bugs creep in.
 
S

Steven D'Aprano

Converting to a float is a trivial matter of dividing by the units of
the scalar. For example:

4

Note, however, that this requires the user to explicity ask for the
conversion.

How is this any more explicit and any less safe than:

dist = 4 * ft
print float(dist)

?

What is unwise is to allow the conversion to happen
automatically without the user's awareness.

Who is talking about having conversions happen automatically without the
user's awareness? I certainly am not.

But in any case, I suspect you do automatically convert units. What do you
do in this case:

x = 45*ft
y = 16*m
z = x+y

Do you raise an error?

That's where bugs creep in.

No, that is where ONE class out of an infinite set of possible bugs creep
in.

Go back to your original problem. You wanted to do something like this:

dist = 4*ft
some_string = '%f' % dist

You're converting a distance in feet to a string representation. This is
not only harmless, but vital. What are you worried about? That once you
provide a __float__ method for your classes, people will immediately
convert all their unit objects into raw floats and do all their
calculations on the raw floats? But as you point out, they can still do
that. It just takes them a tiny bit more work: a division instead of a
conversion.

All you are doing is making a rod for your own back, to no advantage.
 
R

Russ

dist = 4 * ft
How is this any more explicit and any less safe than:
dist = 4 * ft
print float(dist)

Because the former specifies the actual units and the latter does not.
If the base units were not feet, then the latter would not return the
value in feet (which happens to be 4 here). It would return the value
in whatever the base unit for length happened to be. So the former
works even if the base unit is changed, but the latter does not.

Secondly, allowing unconditional conversion to float allows any unit to
be passed to trig functions. But any unit other than (dimensionless)
radians passed to a trig function is an error. I explaned that already,
but apparently you missed it.
But in any case, I suspect you do automatically convert units. What do you
do in this case:
x = 45*ft
y = 16*m
z = x+y
Do you raise an error?

Nope. Works just fine. You obviously didn't look at the user guide.
What happens is that any length unit is automatically converted to the
chosen base unit for length, so everything is consistent.
All you are doing is making a rod for your own back, to no advantage.

Wrong again.
 
R

Russ

Let me just revise earlier my reply slightly.
But in any case, I suspect you do automatically convert units. What do you
do in this case:

Yes, I do automatically convert units, but I only do correct
conversions. Conversion from any unit other than radian to a
dimensionless float is incorrect, so I don't do it automatically.
x = 45*ft
y = 16*m
z = x+y
Do you raise an error?

I said in the previous reply that this works fine. Actually, I don't
think that is true with the current version, but that is only because I
did not include a meter/feet conversion in the two sample configuration
files that are provided. It is trivial matter of two lines.
 
S

Steven D'Aprano

Because the former specifies the actual units and the latter does not.
If the base units were not feet, then the latter would not return the
value in feet (which happens to be 4 here). It would return the value
in whatever the base unit for length happened to be. So the former
works even if the base unit is changed, but the latter does not.

So what you are saying is, if I enter a unit in feet, you automatically
change that unit to some base unit (say, metres if you use SI) behind my
back. So, assuming SI units as the base, if I say:

print 2*ft + 1*ft

you're going to give me an answer of 0.9144 metres, instead of the
expected 3ft. Surely if I'm entering my values in a particular unit, I
would like to see the answers in that same unit, unless I explicitly ask
for it to be converted?

Secondly, allowing unconditional conversion to float allows any unit to
be passed to trig functions. But any unit other than (dimensionless)
radians passed to a trig function is an error. I explaned that already,
but apparently you missed it.

I didn't miss that at all.

sin(45*ft)

is still an error.

sin(45)

shouldn't be, even if that 45 came from float(45*ft). What do you care
where the value comes from? I'm sure you allow sin(float('45')) without
complaining that trig functions don't operate on strings.

Nope. Works just fine. You obviously didn't look at the user guide.
What happens is that any length unit is automatically converted to the
chosen base unit for length, so everything is consistent.

So, despite your going on about the evils of automatic conversions behind
the user's back, you do go ahead and do automatic conversions behind the
user's back.

Slight inconsistency, don't you think?

Wrong again.

Hey, it isn't *me* battling to find a way to easily print my instance
objects.
 
R

Russ

So what you are saying is, if I enter a unit in feet, you automatically
change that unit to some base unit (say, metres if you use SI) behind my
back. So, assuming SI units as the base, if I say:
print 2*ft + 1*ft
you're going to give me an answer of 0.9144 metres, instead of the
expected 3ft. Surely if I'm entering my values in a particular unit, I
would like to see the answers in that same unit, unless I explicitly ask
for it to be converted?

Actually, that was my initial thinking exactly, and it is exactly what
my first version actually did. I learned through experience, however,
that that approach makes adding differrent units of the same type
(e.g., feet and meters) substantially more complicated than it is using
my current approach, so I abandoned it.

The disadvantage is that, as you point out, you can add feet to feet
and get meters. There are two answers to this problem. First, if you
expect to use feet consistently, you can easily make it your base unit.
Secondly, you can always convert the output to feet:

x = 2 * m
print format(x,ft)

That may seem inconvenient, but there are actually a couple of
significant advantages here that makes this form preferable to simply
"print x". One advantage is that if you "disable" units for efficiency,
your output can (and will) still show the units of feet. Otherwise you
lose the unit information completely. The other advantage is that you
can change your base unit and your program will still produce the same
result.
sin(45*ft)
is still an error.

shouldn't be, even if that 45 came from float(45*ft). What do you care
where the value comes from? I'm sure you allow sin(float('45')) without
complaining that trig functions don't operate on strings.

Either you don't understand how the "__float__" function works or you
don't understand basic physics. If the float function converts 45 * ft
to simply 45, then sin(45*ft) will be automatically converted to
sin(45). That is most definitely *not* what you want.
So, despite your going on about the evils of automatic conversions behind
the user's back, you do go ahead and do automatic conversions behind the
user's back.
Slight inconsistency, don't you think?

Let's try this one more time. Automatic conversion is fine as long as
the conversion is dimensionally correct. Automatically converting from
feet to a dimensionless number is *not* dimensionally correct. This
isn't rocket science.
Hey, it isn't *me* battling to find a way to easily print my instance
objects.

Actually, I have already developed an excellent workaround for that
problem.

By the way, I doubt you are impressing anyone here. Are you sure you
want to continue with this?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top