'Python Tuples: remove the first three elements cleanly

My issues:

  • How is (9,) different from (9)? (9,)==(9) yields False
  • Why is (9,) showing up in the snippet below?
  • How do I fix the code below so I get (9)?
chop_first_three = lambda t: t[3:]
chop_first_three((1, 2, 3, 4, 5))  # (4, 5) correct
chop_first_three(('abc', 7, 8, 9)) # (9,)   ??? why not (9)?

Answer

The simple answer is, parentheses are overloaded for grouping and for tuples. To differentiate these forms in python, a trailing comma explicitly indicates a tuple.

Several answers and comments below point this out.

I've left my original questions above so the answers below make sense, but obviously, some of my original questions were incorrect.



Solution 1:[1]

In python if you wrap an object in parenthesis you will just get the element back.

a = (9)
print(type(a)) # <class 'int'>

(9,) instead is a tuple containing only one element (the number 9)

a = (9,)
print(type(a)) # <class 'tuple'>

When you slice a tuple you will always get a tuple back (even if it only contains one element) Therefore the (9,)

Solution 2:[2]

Parentheses with no commas in them are for grouping operations, so (9) is just a strange way to say 9.

It seems strange here, but it's important for reasonable things like (4 + 5) * 6:

  • Without the parentheses, PEMDAS would evaluate 5 * 6 first, and you'd get the wrong answer (34 instead of 54).
  • With the parentheses, if you didn't need a comma to be a tuple, that would evaluate to a one-tuple containing 9, which sequence multiplication would expand to (9, 9, 9, 9, 9, 9), again, not what you wanted.

So specifically for the case of one-tuples, the trailing comma says "I'm making a tuple, not just grouping operations". You can include a trailing comma in any tuple, but it's mandatory for one-tuples.

In short, the result you're seeing is correct. If you really hate the trailing comma, it's on you to manually format your tuples so it doesn't contain them when it comes time to print/stringify them. For example, given a tuple that might have only one element, you can do:

maybe_one_tuple = ...
comma_sep = ', '.join(map(str, maybe_one_tuple))
with_parens = f'({comma_sep})'
print(with_parens)

Or a cheesy trick to use the stringified form of list to reduce the custom work you need to do yourself:

list_str_form = str(list(maybe_one_tuple))
with_parens = f'({list_str_form[1:-1]})'

which just converts to list, stringifies the list (which is the same string form as tuple except with [] instead of (), and with no trailing comma needed for length one lists), manually strip the guaranteed to exist [ and ] and replaces them with ( and ).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Matteo Zanoni
Solution 2