5

I am using Python 2.7. (Switching to Python 3 for this particular code is not an option, please don't suggest it.) I am writing unit tests for some code.

Here is the relevant piece of code:

class SingleLineGrooveTable:
  VEFMT = '.3f'

  @classmethod
  def formatve(cls, value, error=None):
    er = 0
    if error is not None:
      er = error
      v = value
    elif len(value) > 1:
      v, er = value
    else:
      v = value
    return format(v, cls.VEFMT), format(er, cls.VEFMT)

and my test is:

import unittest

class TestSingleLineGrooveTable(unittest.TestCase):
  
  def test_formatve_no_error(self):
    e_v = '3.142'
    e_er = '0.000'
    r_v, r_er = SingleLineGrooveTable.formatve([3.1423])
    self.assertEqual(e_v, r_v)
    self.assertEqual(e_er, r_er)

(Yes, I know it's funny I'm getting an error on the test with "no_error" in the name...)

When I run the test, it throws ValueError: Unknown format code 'f' for object of type 'str' on the return statement for the function. But I can't figure out where it's getting a str from. Possibly relevant, this code and the code I have that uses it were copied pretty much wholesale from someone else's code (who I can no longer contact), so maybe I'm calling it in the wrong way, but still, that's a list, not a string!

What is going on here? How do I fix this?

7
  • 2
    I'm going to suggest switching to Python 3 anyway, even if you don't want to hear it. Still using 2.7 in 2024 is weird. That said: you're going to want to turn this into a minimal reproducible example because right now you're showing a def formatve that takes two arguments (and a third optional one) but then your code seems to call it with a single argument, so this should be throwing TypeError: formatve() missing 1 required positional argument: 'value'. Also, there are no f-strings in your code, so are you sure about the f-string tag on your post? Commented Jun 13 at 20:13
  • v = value should be v = value[0]. The error stems from trying to format a list as a float.
    – chepner
    Commented Jun 13 at 20:40
  • The issue of why you get a particular error message for an obviously unintended call of [3.1423].__format__('.3f') is less important than making the intended call of 3.1423.__format__('.3f').
    – chepner
    Commented Jun 13 at 21:06
  • @Mike'Pomax'Kamermans Unfortunately, I'm coding scripts that interact with an application that only has a python package for python 2.7, so switching really isn't an option. I'd love to, considering due to my name's accent I literally had to edit the encoding in 2.7's source code to get it to even run, but it's not an option :( Even if the application had a Python 3 version, we have too much code for switching to be practical.
    – Réka
    Commented Jun 14 at 18:06
  • @chepner I thought that it shouldn't be a list at first, but not making it a list makes the elif len(value) throw an error... Although, looking at the previous coder's code that uses this, I'm actually not sure he ever called it without an error value, and he didn't unit test it. So maybe that was a bug that never got caught, I assumed it was my issue because that code's been in use for years.
    – Réka
    Commented Jun 14 at 18:08

3 Answers 3

2

On Python 2, object.__format__ effectively delegates to format(str(self), format_spec). You can see the implementation here.

Since list inherits object.__format__, your first format call is effectively calling format(str([3.1423]), '.3f'). That's why you get the error message you do.

This would still produce an error on Python 3. It'd just be a different error.

3
  • Well, that's odd. Like I said, I copied this from another coder who used to work at my job, and when I tried using 3.1423 as an argument not as a list, it complained that it couldn't use len() on it. Although, looking at his code, I'm not sure if he ever used this without entering an error value as well, and he didn't have unit tests for it...
    – Réka
    Commented Jun 14 at 18:04
  • But thank you, this makes perfect sense, and allowed me to figure out how to fix my code!
    – Réka
    Commented Jun 14 at 18:19
  • Basically, if you provide an explicit error, you can pass a bare float. Otherwise, the first argument must be a list that whose first element is a float, with the optional second element an error.
    – chepner
    Commented Jun 14 at 19:55
1

Try this code

class SingleLineGrooveTable:
    VEFMT = '.3f'

    @classmethod
    def formatve(cls, value, error=None):
        er = 0
        if error is not None:
            er = error
            v = value
        elif len(value) > 1:
            v, er = value
        else:
            v = value[0]  # Extract the float from the list
        return format(v, cls.VEFMT), format(er, cls.VEFMT)

import unittest

class TestSingleLineGrooveTable(unittest.TestCase):
  
    def test_formatve_no_error(self):
        e_v = '3.142'
        e_er = '0.000'
        r_v, r_er = SingleLineGrooveTable.formatve([3.1423])
        self.assertEqual(e_v, r_v)
        self.assertEqual(e_er, r_er)

if __name__ == '__main__':
    unittest.main()
1
  • The input should be able to be either a list or a float - this is essentially what I ended up doing to fix it, except I added "elif type(value) == float or type(value) == int: v = value", made the else you have an "elif len(value) == 1", and moved "v, er = value" to the else.
    – Réka
    Commented Jun 24 at 16:57
-1

The issue is that format() doesn’t recognize "f" as a specifier for the str: "[3.1423]"; Try it online.

Since you are formatting float, consider using string formatting operation [python.org] for float.

result = "%.3f" % 3.1423

So what changes should we make to your code?

VEFMT = "%.3f"
#•••Rest of code•••
else:
    v = value[0]
return cls.VEFMT % v, cls.VEFMT % er

Try it online!

3
  • This is not how format works. If it was, then format(1.2, ".3f") wouldn't work, but it does. Commented Jun 13 at 21:44
  • @user2357112 I missed a the. Commented Jun 13 at 21:46
  • This is still not how format works. If this were how it worked, then format('1.2', '.3f') would behave the same as format(1.2, '.3f'), but the first raises an exception and the second runs fine. Commented Jun 13 at 21:53

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