4

I was looking at code in my course material and had to write a function which adds the value 99 to either a list or tuple. The final code looks like this:

def f(l):
    print(l)
    l += 99,
    print(l)

f([1,2,3])
f((1,2,3))

This was used to show something different but I'm getting somewhat hung up on the line l += 99,. What this does, is create an iterable that contains the 99 and list as well as tuple support the simple "addition" of such an object to create a new instance/add a new element.

What I don't really get is what exactly is created using the syntax element,? If I do an assignment like x = 99, the type(x) will be tuple but if I try run x = tuple(99) it will fail as the 99 is not iterable. So is there:

  • Some kind of intermediate iterable object created using the syntax element,?
  • Is there a special function defined that would allow the calling of tuple without an iterable and somehow , is mapped to that?

Edit: In case anyone wonders why the accepted answer is the one it is: The explanation for my second question made it. I should've been more clear with my question but that += is what actuallly got me confused and this answer includes information on this.

Seth
  • 1,215
  • 15
  • 35
  • 1
    @ScottHunter `x = 99,`, not `x = 99;` comma vs semi-colon – TemporalWolf Dec 09 '16 at 19:23
  • 1
    This question has actually uncovered something I was not aware of before. Apparently the `list.__add__` method (for addition) will not accept a `tuple` for the `other` argument, but `list.__iadd__` (for +=) does accept it. I wonder why that is? – Rick Dec 09 '16 at 19:32
  • That smells like a bug @RickTeachey – Dimitris Fasarakis Hilliard Dec 09 '16 at 19:39
  • @JimFasarakis-Hilliard seems that way to me too, but I tend to take Treebeard's advice and not be too hasty. – Rick Dec 09 '16 at 19:41
  • 2
    @RickTeachey I'll see if I can find anything on the bug tracker and/or source, this has me pretty surprised too. Edit: [Found it](https://bugs.python.org/issue12318) :-) – Dimitris Fasarakis Hilliard Dec 09 '16 at 19:42
  • 1
    @JimFasarakis-Hilliard "It's been that way for a long while and Guido said he wouldn't do it again (it's in his list of regrets). However, we're not going to break code by changing it (list.__iadd__ working like list.extend)." COWARDS! ;) – Rick Dec 09 '16 at 19:49

3 Answers3

5

If the left-hand argument of = is a simple name, the type of argument currently bound to that name is irrelevant. tuple(99) fails because tuple's argument is not iterable; it has nothing to do with whether or not x already refers to an instance of tuple.

99, creates a tuple with a single argument; parentheses are only necessary to separate it from other uses of commas. For example, foo((99,100)) calls foo with a single tuple argument, while foo(99,100) calls foo with two distinct int arguments.

chepner
  • 497,756
  • 71
  • 530
  • 681
3

The tuple constructor requires an iterable (like it says in your error message) so in order to do x = tuple(99), you need to include it in an iterable like a list:

x = tuple([99])

or

x = tuple((99,))
Will
  • 4,299
  • 5
  • 32
  • 50
3

The syntax element, simply creates an "intermediate" tuple, not some other kind of object (though a tuple is of course iterable).

However, sometimes you need to use parentheses in order to avoid ambiguity. For this reason, you'll often see this:

l += (99,)

...even though the parentheses are not syntactically necessary. I also happen to think that is easier to read. But the parentheses ARE syntactically necessary in other situations, which you have already discovered:

list((99,))
tuple((99,))
set((99,))

You can also do these, since [] makes a list:

list([99])
tuple([99])
set([99])

...but you can't do these, since 99, is not a tuple object in these situations:

list(99,)
tuple(99,)
set(99,)

To answer your second question, no, there is not a way to make the tuple() function receive a non-iterable. In fact this is the purpose of the element, or (element,) syntax - very similar to [] for list and {} for dict and set (since the list, dict, and set functions all also require iterable arguments):

[99] #list 
(99,) #tuple -  note the comma is required
{99} #set

As discussed in the question comments, it surprising that you can increment (+=) a list using a tuple object. Note that you cannot do this:

l = [1]  
l + (2,) # error

This is inconsistent, so it is probably something that should not have been allowed. Instead, you would need to do one of these:

l += [2]
l += list((2,))

However, fixing it would create problems for people (not to mention remove a ripe opportunity for confusion exploitation by evil computer science professors), so they didn't.

Rick
  • 43,029
  • 15
  • 76
  • 119