2
\$\begingroup\$

My Python code is converting XML attributes in an ElementTree to appropriate values. How do I best convert a value in this dict to a float() if it exists, or else get None?

Best I have now is:

attr = etree_elem.attrib
self.radius = float(attr['radius']) if 'radius' in attr else None
...
if self.radius:
  # do stuff using radius

I dislike this because it's not DRY (repeats both 'radius' and attr). I had code like this:

self.radius = float(attr.get('radius', -1))
...
if self.radius >= 0:
  # do stuff using radius

But (a) that only works when I know that a particular attribute value is otherwise invalid, and (b) it's not as "clean" to me as using None to properly signify that no radius was passed.

\$\endgroup\$
2
\$\begingroup\$

It's a bit difficult to get None without writing an explicit conditional. The next best option may be to go with float('nan') instead of -1 to make it harder to accidentally use it as a value.

self.radius = float(etree_elem.attrib.get('radius', 'nan'))

You can use math.isfinite() to test the existence of a value. The self.radius >= 0 test would also continue to work.

\$\endgroup\$
2
\$\begingroup\$

Neither are that great. And they are actually doing different things. Take:

attr = {'radius': None}
self.radius = float(attr['radius']) if 'radius' in attr else None
# TypeError: float() argument must be a string or a number, not 'NoneType'

I assume you'd want to handle that the same way, otherwise you'd not have tried the other approach. And so you could change it to:

float(attr['radius']) if attr.get('radius') is not None else None

Again this is in no way DRY, and so I'd instead make a new function say float_nonnull. Where it's defined as:

def float_nonnone(data):
    if data is None:
        return None
    return float(data)

Which is good until you need the same with str, int, dict, etc. But would also be WET if you pass the conversion function as an argument. Unless you use functools.partial. Which can result in:

from functools import partial

def convert_nonnull(convert, data):
    if data is None:
        return None
    return convert(data)

float_nonnull = partial(convert_nonnull, float)

print(float_nonnull(None))          # None
print(float_nonnull(1))             # 1.0
print(float_nonnull(2.2))           # 2.2
print(convert_nonnull(float, None)) # None
print(convert_nonnull(float, 1))    # 1.0

Note: it still errors on invalid input. Like 1a.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the answer, and pointing out edge cases. Personally, a separate function just to massage data is sort of the opposite of elegance that I was hoping for. \$\endgroup\$ – Phrogz Oct 24 '16 at 16:53
  • \$\begingroup\$ BTW, why not if data is not None:\nreturn float(data) and allow the implicit None of the function handle the other case? \$\endgroup\$ – Phrogz Oct 26 '16 at 21:41
  • 1
    \$\begingroup\$ @Phrogz You can do that, but I don't like an implicit return None. And to be a hobgoblin, it goes against PEP8 too. \$\endgroup\$ – Peilonrayz Oct 26 '16 at 22:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.