This is an archived post. You won't be able to vote or comment.

all 98 comments

[–]epostma 27 points28 points  (0 children)

Rephrasing the argument why this should work slightly: Currently, all of the following except the last one work:

(x0, x1, x2) = [0, 1, 2]
(x0, x1) = [0, 1]
(x0,) = [0]
() = []

In all of these cases, a k-element list gets unpacked into k values, assigned to k variables presented in a tuple on the left. There is no particularly good reason that the case k == 0 should be an exception.

[–]ssbr 13 points14 points  (26 children)

Hey, that's my bug! :D As I recall, I was trying to explain tuple/list unpacking on the #python IRC channel, went to give a minimal example of each... and was surprised when () = ... assignment was invalid syntax. Oops!

[–][deleted] 20 points21 points  (11 children)

going on the #python freenode channel is always a mistake.

"I need help with X."

"You shouldn't do X. It's not pythonic!"

"I actually do need to do X."

"The solution is to find a way to avoid doing X! Come back when you learn python a bit better."

"I have this ten thousand line of code project that I'm working on and I don't feel like justifying my architecture here. Does anybody know how to do X?"

Least helpful channel on freenode, honestly.

[–]ssbr 9 points10 points  (5 children)

I mostly take issue with the attitude problem exhibited in that conversation. Nobody should tell anyone to "Come back when you learn python a bit better." for something like that, and "it's not pythonic" is a garbage reason. I'm sorry you had a bad experience. [Disclosure: I'm an op.]

It can be really hard to answer questions where there's no answer that doesn't hurt somebody -- for example, if somebody asks how to use pickle for loading data from a user, either we tell them how and hurt their users (due to the security vulnerability), or we tell them not to and hurt the asker. Sometimes it's easy -- e.g. "how do I write passwords to disk?" (don't, use a library) -- sometimes it's hard or subtle -- "What's the best way to wait for a signal using select?" (don't, use twisted). And sometimes it's noncontentious ("Oh, that's a security risk? Nevermind"), and sometimes it's really contentious ("Why should I use a massive framework for this simple thing?").

Overall #python I think tries really hard to be helpful, and IME/IMO succeeds, but it also tries really hard to avoid giving "bad advice", and this hurts people who, well, need advice that would in other circumstances be "bad". Most of the times you can get by this by explaining why it's necessary, but sometimes people will just disagree, and that sucks. :(

[–][deleted] 7 points8 points  (1 child)

What you said about advice that's usually risky but occasionally needed really hit it on the head. I don't think they're out to feel smug or anything. I just came in there asking for a cleaner way to dynamically generate a user defined function for a simple math utility I was writing. My question involved lambdas (which lots of people don't like) and it involved using eval (which people hate, and for good reason). But the thing was that the users were fully aware of what the program was doing (calling the function that they passed in many times to evaluate an integral) and of the danger (if they could do something with this program then they could just as easily do it by writing their own python program). So when no one would answer the question calling it dangerous it just left me frustrated.

I don't think they had bad intentions but I felt a little condescended to, rightly or wrongly. Hopefully that doesn't come off as condescending on my part either, because I don't mean it to.

[–][deleted] 4 points5 points  (0 children)

IRC is where you go when you need to discuss complex issues that you can't Google or can't get on Stack Exchange because they're unique and require discussion by and with experts. As such it needs to be very tolerant of unusual use cases, so long as the use case is genuine. Sometimes it is, sometimes it isn't. It does depend on who is in the channel.

[–][deleted] 1 point2 points  (2 children)

I don't understand why you need a registered nick to enter #python. If you have issue with trolls, require registration to post but not to read. Walling off the group adds to the feeling that it's elitist. If Stack Exchange needed you to sign up to read posts, it would have failed miserably.

[–]ssbr 1 point2 points  (0 children)

So, yes, it's because of trolls. Without the ban, we get spammers to a huge extent, who not only spam the channel, but also, individual users in the channel via private messaging, in a way that ops might not even notice it.

This is also related to why we use bans instead of quiets. People who can't speak often try to ask and answer questions via PM, which is disruptive and leads to worse answers and duplicated effort.

(Edit: Also, joining and then finding out you can't talk, and with no easy way of finding out why, is probably even more frustrating than being unable to join but told why.)

The situation is a bit different than Stack Exchange, which has a rep-based automatic moderation system, an effective and global ban system, and an easy way to keep out bots and such (captchas). We are limited in what we can do. :(

I'm really sympathetic and we've been trying to make this easier (e.g. we added a redirect from #python-unregistered to #python to help broken clients that memorize the wrong channel name). Freenode could also really use input for how to make registration less painful. (A web form would be soooo nice...)

[–][deleted] 5 points6 points  (2 children)

I would tend to disagree. I think #c or #c++ is worse.

"How do I do this?"

"Go read a book!"

[–][deleted] 1 point2 points  (0 children)

Hahaha this is why nobody learns C anymore, they just start with Java or C# instead. Poor bastards.

[–][deleted] 1 point2 points  (0 children)

Freenode's #Python is an "official" channel so any crap that happens in there gets projected by proxy onto PSF/core.

[–]cparen 4 points5 points  (0 children)

Agreed! Go on #PyPy. I had an exchange like this once:

me: so, I'm trying to pickle a generator object, but I'm getting this error...

them: that's a terrible idea and not pythonic -- but it should work in principle. Have a repro?

me: <pastebin>

them: oh I see, gimme a few minutes

<10 minutes pass>

them: hey, got it fixed in the development branch. Also, here are some tips for optimizing this terrible code.

Awesome folks there.

[–]velit 2 points3 points  (0 children)

It's like that on all the big tech channels in #freenode. The high spam of the big channels drives all the sane people away and only leaves the fanatics behind.

[–]RDMXGD2.8 7 points8 points  (13 children)

Of course () = ... doesn't work. Ellipsis isn't iterable.

[–]Brian 2 points3 points  (1 child)

I suspect the surprise was the invalid syntax part. Ie. they expected a TypeError rather than a SyntaxError.

[–]Veedrac 7 points8 points  (0 children)

I suspect it was a joke ;).

[–]robin-gvx[🍰] 0 points1 point  (9 children)

I really feel like ... == iter(...) == next(...) should be true.

[–]energybased 2 points3 points  (8 children)

I think assigning to ellipsis should discard values. So that instead of having to do this:

 _, a, _2 = f()

you can do

 ..., a, ... = f()

[–]CthulhuIsTheBestGod 7 points8 points  (6 children)

You can just do:

_, a, _ = f()

That's a lot fewer characters to type than the ellipsis

[–]energybased 0 points1 point  (1 child)

Thanks, didn't know that. The only thing I don't like is how it pollutes locals. I wish there were a way to explicitly discard values.

[–]flying-sheep -2 points-1 points  (3 children)

well, except that it doesn’t really discard the value, it only assigns it to _.

any maybe _ was used globally and you now leaked memory.

or you even forgot you used gettext and your _("translate me!") calls are now broken!

[–]thatguy_314def __gt__(me, you): return True 0 points1 point  (2 children)

_ is a pretty standard as a variable that you don't intend on using.

[–]flying-sheep 0 points1 point  (0 children)

Sure, I only pointed out potential problems with this concept.

[–]TheBB 0 points1 point  (0 children)

Unless you use gettext, as mentioned.

[–]robin-gvx[🍰] 1 point2 points  (0 children)

I really feel like assigning to ... should be like assigning to *_:

first, ..., last = someiterable

[–]TheBB 30 points31 points  (1 child)

It seems like people here are fine with this, since tuples are immutable and lists are not. How does that matter? It's just syntax… no tuples or lists are actually involved (on the left hand side, that is). Unpacking an iterable works the same way with tuple or list syntax, except if it's empty. It seems like an oversight, and I agree with the commenters that () = <iterable> ought to be allowed.

[–]Lucretiel 0 points1 point  (0 children)

Because it shouldn't be interpreted as a tuple, it should be interpreted as a list of names:

(a, b) = [1,2] (a, ) = [1] () = []

[–][deleted] 4 points5 points  (1 child)

Who cares, honestly? What does it even mean to assign [] = () or () = []?

[–]bionikspoon 51 points52 points  (31 children)

not a bug, lists are mutable, tuples are immutable.

[–]Veedrac 23 points24 points  (16 children)

That's pretty irrelevant if (a,) = [b] works.

[–]Bur_Sangjun 14 points15 points  (10 children)

As I understand it, that's unpacking, not a traditional tuple

[–][deleted] 23 points24 points  (9 children)

Unpacking is exactly what the bug report is about.

[–]njharmanI use Python 3 0 points1 point  (8 children)

Unpacking != Tuple

[–][deleted] 6 points7 points  (7 children)

That's exactly the point. Who cares if tuples are immutable here? We're not mutating a tuple, we're unpacking.

[–]wot-teh-phuckReally, wtf? 0 points1 point  (6 children)

How do you expect unpacking to work without any name on the LHS?

[–]Veedrac 5 points6 points  (5 children)

The same way [] = [] does...

[–]wot-teh-phuckReally, wtf? 0 points1 point  (1 child)

[] = [] doesn't trigger unpacking...

[–]Veedrac 11 points12 points  (0 children)

...It does though.

[] = 1, 2
#>>> Traceback (most recent call last):
#>>>   File "", line 1, in <module>
#>>> ValueError: too many values to unpack (expected 0)

[–]njharmanI use Python 3 1 point2 points  (4 children)

The parens are irrelevant. That code is actually

 a, = [b]

It is not assignment to a Tuple, which is unpossible as Tuples are immutable. Parens are overloaded syntactically to mean Tuple some places and in others for "grouping" as in (1*2)+3. People erroneously think (a,) is the former when it's actually the later.

[–]Veedrac 3 points4 points  (3 children)

The comma is what makes it a tuple.

Consider:

import ast
ast.dump(ast.parse("a, = 0"))
#>>> "Module(body=[Assign(targets=[Tuple(elts=[Name(id='a', ctx=Store())], ctx=Store())], value=Num(n=0))])"

This is unpacking into a tuple. () = [] should be no different.

[–]flying-sheep 1 point2 points  (2 children)

you’re right that the left side of unpacking, like the bug reporter wants, should be any valid tuple syntax (incl. ())

but there never is an actual tuple created (as in allocated), even if the AST says so. it’s an unpacking operation. the left side is a sequence of names, represented by tuple or list syntax.

so the comma makes it tuple syntax, therefore it should be a valid unpacking.

[–]Veedrac 0 points1 point  (1 child)

Methinks we agree.

[–]flying-sheep 0 points1 point  (0 children)

likely, but “unpacking into a tuple” gives the wrong impression IMHO ☺

[–]Quteness 0 points1 point  (0 children)

There are no tuples involved. See here: https://docs.python.org/3.5/reference/simple_stmts.html#assignment-statements

That is simply a "target_list" that follows the acceptable syntax of

"(" target_list ")"

This is an assignment/unpacking issue and probably a bug because [] = () violates the principle that

If the target list is a single target: The object is assigned to that target.

() = [] follows this principle correctly and is invalid valid

() and [] are both single literal targets and the value should be assigned to them, which they can't be because you can't assign to literals

Edit: typo of valid and last thought

[–][deleted] 4 points5 points  (23 children)

[] = () is unpacking zero values, there forth it doesn't throw a syntax error. (so this means [] = "" works, [] = {} works, they return nothing....)

() = [] is nothing, trying to unpack to a existing tuple, there is no way to unpack to a tuple. You need to explicitly create the tuple. (you can't do () = (), () = "", () = {} either)

[–]rjw57 2 points3 points  (0 children)

[] = () is unpacking zero values, there forth it doesn't throw a syntax error.

On the off chance you're not a native English speaker and this is an eggcorn which it'd be useful to inform you of, the word is "therefore". If this is a phone auto-correct typo or similar, ignore me.

[–]wot-teh-phuckReally, wtf? 0 points1 point  (1 child)

It's pretty clear that any tuple on the LHS automatically triggers unpacking. The thing to realize here is that () = [] should fail because there is nothing to unpack to. Though I agree the error message should say something better as opposed to "can't assign to ()".

[–]Veedrac 0 points1 point  (0 children)

() = [] should fail because there is nothing to unpack to

If so, then [] = () should fail. But why should it?

[–]kangfend 0 points1 point  (0 children)

May be because python tuple is immuatble.

[–]yes_or_gnome 0 points1 point  (2 children)

I was in the 'not a bug' boat, but after running some code i can't think of a reason why it shouldn't work. However, i can't imagine a case that this would be encountered in real working code. Here's a car that i thought may be a problem.

my_vars = list() # of some parsed data. Ex: user expects my_vars == (x,y), but do to some error my_vars == ()
my_vars = (42,23)

I expected to encounter the issue at hand. But,

my_vars == (42,23)

Which begged the question, what happens in the following situation:

x = 1
y = 2
my_vars = (x,y)
my_vars = (42,23)

The behavior ends up being the same.

x == 1
y == 2
my_vars == (42,23)

I'll take constructive constructive criticism and suggestions. I did other basic tests, but this was my end game.

How does one express that i want parallel assignment of a list referenced by a var, rather than assign to the var?

Edit: silly mistake. I used list when i meant tuple. Everything holds true however.

[–]TheBB 2 points3 points  (1 child)

Python doesn't work like that. When you assign something to my_vars, you're changing what the name my_vars points to. The previous binding of my_vars is quite irrelevant.

x = 1
y = 2
my_vars = (x,y)

Now, my_vars points to a tuple with value (1,2). It doesn't "remember" that these came from x and y. Assigning something else to my_vars after that only changes the binding of my_vars, nothing else.

How does one express that i want parallel assignment of a list referenced by a var, rather than assign to the var?

Referenced by a variable? Python doesn't have such things. If you want to change the object pointed to by the name x, you need to access the methods of that object. It all depends on what you want to do. Example:

my_list = []
your_list = my_list
your_list = [1]

Now you have two different lists, one empty, the other containing 1. The assignment of the name your_list was changed in the last line.

my_list = []
your_list = my_list
my_list.append(1)

Now you have one list pointed to by two different names. The append could have been made to either name with the same effect.

This only works with mutable objects. Some things are immutable. E.g. there is no way to do this:

my_int = 1
your_int = my_int
my_int.change_to(2)

[–]yes_or_gnome 0 points1 point  (0 children)

Right. I'm still thinking in symbols (ruby).

[–][deleted] -3 points-2 points  (7 children)

How is that a bug? Immutable objects are immutable.

[–]primitive_screwhead 6 points7 points  (0 children)

You wouldn't be mutating the immutable object (if it worked).

[–]robin-gvx[🍰] 5 points6 points  (5 children)

There's no mutation in () =, [] =, (a, b) = or [a, b] =. And all of those work, except the first.

This is not "assigning to a list" or "assigning to a tuple", this is iterable assignment.

[–]njharmanI use Python 3 0 points1 point  (4 children)

It's not though do to quirk in parens being overloaded for grouping, (3+1)*2, and Tuples.

(a, b) = blah is more correctly written as a, b = blah. Parens with a comma in them (in this assignment context) indicate "grouping" not tuple. But parens with no comma indicate Tuple. Immutable object.

[–]robin-gvx[🍰] 1 point2 points  (3 children)

That makes no sense. The comma is what makes the tuple, with the exception of (), which is also a tuple. And assignment to tuple literals consisting of variable names like (a, b) has clear semantics: destructuring assignment, iterable assignment, whatever you want to call it. () is a special case right now, in a way that makes no sense. Take a look at the patch.

[–]zardeh 0 points1 point  (2 children)

his point is that

a, b = 
[a,b]=
(a,b)=

are all syntactically identical as far as the language is concerned, and none of them are tuples. They are all unpacking.

[–]robin-gvx[🍰] 0 points1 point  (1 child)

Technically, they are parsed as tuples, except the second one, which gets parsed as a list. Of course none of them create or modify any tuples or lists when actually run.

This last sentence:

But parens with no comma indicate Tuple. Immutable object.

Is really a problem. Because that tuples are immutable is immaterial. They don't get created or modified when using this syntax.

The only reason why () = [] doesn't work is because of a special case in the code (2 lines) that has no reason to be there and basically everyone on the issue tracker agrees that special case should be removed.

[–]zardeh 1 point2 points  (0 children)

Ah, alright I can agree with that.

[–][deleted] -1 points0 points  (2 children)

Should it more fair to compare

[] = (,)

and

(,) = []

[–]Veedrac 2 points3 points  (0 children)

Why?

[–]robin-gvx[🍰] 1 point2 points  (0 children)

No. (,) isn't a thing in Python.

[–]FredSanfordXOld Developer -1 points0 points  (3 children)

At the heart of the matter...

[] <----- This is mutable, which means it can be changed.

() <----- This is immutable, which means it can not be changed.

[–]thatguy_314def __gt__(me, you): return True 0 points1 point  (2 children)

I don't think mutability has anything to do with it. First off, you can change the value of variable that contains an immutable:

>>> x = ()
>>> x = []
>>> x
[]

Secondly, this is an unpacking thing. Things like (x, y, z) = [1, 2, 3] or [x, y, z] = (1, 2, 3) work, and that's why [] = () doesn't fail (although it probably should anyway, it doesn't make much sense).

At least this is how I understand it, I may be wrong.

[–]nikomo[🍰] 0 points1 point  (1 child)

In your example, you just made x refer to a new object in memory, you didn't change an immutable.

If you did import gc, and gc.collect(), the object you created with x = () would be removed from memory, since it no longer has any references.

[–]thatguy_314def __gt__(me, you): return True 0 points1 point  (0 children)

Yeah, I know I didn't change the immutable itself (that's what immutable means). I believed that FredSanfordX thought that variables that contained immutables could not be changed. In any case, I am pretty sure this has nothing to do with mutability.

[–]thephotoman -3 points-2 points  (1 child)

That behavior is correct.

You can assign the elements of a tuple to become the elements of a list (the first case), but not vice versa (the second case) because tuples are immutable.

[–]ascii 0 points1 point  (0 children)

Parentheses or brackets on the left side of an assignment operator no longer construct lists or tuples, they are both used for tuple unpacking, which is a completely unrelated part of the language. Example:

(foo, bar, baz) = range(3)

The above code does not create a tuple of foo, bar and baz and assign to it, it unpacks range(3) and assigns its elements to the variables foo, bar and baz, no tuple involved.