Feedback for new exercise (Locomotive Engineer) and new concept (Unpacking and Multiple Assignment) for python

You mean . I’ll read it right after writing this.

There is the concept vs the exercise The concept is very short, and the is more detailed.

Just to be super clear: Concept docs on GH and Exercise docs on GH.

So in this case, we need more context/clarity in the for the exercise?

Sigh. I was so careful to not mix them up , but still did :disappointed:

I’ll rectify my earlier replies. Update: done, I think.

I do this on a daily basis. :joy: I can’t keep them straight. It’s one reason why we separate the exercise issues from the concept issues in the repo - there’s just a little too much overlap for me. :smile:

@MatthijsBlom - looping back a bit on the feedback (and thank you very much for it!)

No, they aren’t … exactly operators, except that the PEP on Additional Unpacking Generalizations calls them that. As does the Python docs in various places (I went down a big ole rabbit hole with those. :wink:) Now, to be fair the PEP on Extended Iterable Unpacking does call them “starred expressions” - but I don’t see that in much use elsewhere.

…and part of the reason they need context is that they’ve really really overloaded them.
in this context:

>>> first_number, *other_numbers, last_number = 3,11,63,98,73,2,4,6,8
>>> first_number

>>> other_numbers
[11, 63, 98, 73, 2, 4, 6]

>>> last_number

The tuple on the right-hand side is unpacked into the various names, and then the *other_numbers repacks the numbers in the middle into a list.

But here where we’re deep unpacking, more context is needed:

matrix = [[(2, "red"), (4, "red"), (8, "red")], 
          [(5, "blue"), (9, "blue"), (13, "blue")], 
          [(3, "orange"), (7, "orange"), (11, "orange")]]

#an attempt to transpose by zipping and unpacking
>>> *row_1, *row_2, *row_3 = zip(*matrix)
  File "/var/folders/c7/.../T/ipykernel_78982/", line 1
    *row_1, *row_2, *row_3 = zip(*matrix)
SyntaxError: multiple starred expressions in assignment

# specifying a list display for the unpacked & repacked items. 
# equivalent to [item for item in row_x]
>>> [*row_1], [*row_2], [*row_3] = zip(*matrix)

>>> row_1
Out[420]: [(2, 'red'), (5, 'blue'), (3, 'orange')]

>>> row_2
Out[421]: [(4, 'red'), (9, 'blue'), (7, 'orange')]

>>> row_3
Out[422]: [(8, 'red'), (13, 'blue'), (11, 'orange')]

*name unpacks the tuples from the zip object (and given the rule above should repack as a list as well), but using multiple * in a row is not allowed (too much ambiguity for grouping, maybe?). Adding [] or (*something,) to signify that the unpackings are meant to be repacked as lists or tuples resolves the issue.

Totally agree that we need to go back in and clarify things, and regrouping as you suggest might be the way to go.

What makes all this multiple assignment and packing/unpacking so confusing (at least for me) is that except for the mathematical use, Python sees all of this as the same thing – but certain contexts disallow certain uses. One case I find particularly mind-bending is use in for loops, particularly with enumerate() (a case we didn’t go over in the exercise).

Review of still coming up; I’m just letting myself be distracted by forum posts.


My own inclination is to write first and to then (lightly) strip + elaborate it to get the

Ew! Though I guess on some level I already knew that: I would probably google them like that as well.

In explanatory material like this I feel it is especially important to use good terminology. Plenty of confusion going around already, such as on the difference between expressions and statements. E.g. “The ... if ... else ... statement”. Sadly it seems a bad name is actually the best choice here.

I would want to add a note on how the name is wrong, but this might well create too much noise.

Yes, I’m quite sure that that is the reason.

No need to specify a list; it always is:

(*a,), (*b,), (*c,) = zip(*matrix)
print(a)  # [(2, 'red'), (5, 'blue'), (3, 'orange')]

Still not sure exactly how to feel about this.

There’s not even a type error.

I spotted ½ a mistake in my grouping: the unpacking in expression contexts is syntactic sugar for unpacking into argument lists.

Regarding (un)packing, Python has two dual visions on these operators:

  • Packing occurs to the left of = and in parameter lists, i.e. only in ‘assignment targets’.
  • Unpacking occurs ‘morally to the right of =’: only in argument lists and their syntactic sugars, i.e. ‘to-be-assigned payloads’.

Or did you mean something else?

As in

for i, (*row,) in enumerate(table):

? That is an assignment target: morally i, (*row,) = next(iterator). Unpacking on the right, packing on the left, as always. Or does the confusion lie elsewhere?

3 posts were split to a new topic: Should exercise feedback be given on the forum or on GitHub?

Your friend is great at handling trains, although they aren’t amazing at handling the logistics computers and would like your programming help organizing the train and correcting mistakes in the data.

  • This sentence is rather long; consider breaking it up.
  • The connector word “although” suggests that the two ideas are in some ways at odds with each other; “however” or “but” might be a better choice.
  • The terms “logistics computers” and “programming help” add complexity without value. Simpler terms like “computers” and “help” convey the same amount of information with less complexity.

However, we’d like you to practice using Unpacking and Multiple Assignment to solve each of the tasks below.

I don’t believe “Unpacking” or “Multiple Assignments” are proper nouns and, as such, should not be capitalized.

Your friend has been keeping track of each wagon identifier, but they’re never sure how many wagons they are going to have to process at any given time.

I’ve been taught to avoid contractions in documentation and formal writing. I’m not sure how formal you would consider exercise prose but I generally avoid them in the Exercism writing I’ve done. This applies to other locations in the docs, too.

It would be much easier for the rest of the logistics program to have the data to be returned as a list .

“have … to be” is incorrect grammar. This could be, “It would be easier to have the data returned as a list .” or even, “It would be easier to have the data as a list .”

Implement a function get_list_of_wagons that accepts an arbitrary amount of positive integers which are the IDs of each wagon. It should then return the given IDs as a list.

This article says,

Although number and amount have similar meanings, number is used for things that can be counted, while amount is used for things that cannot be counted.

The function should take a number of values, not an amount.

This exercise discusses identities previously. Prefer sticking to identities over “IDs” or define “IDs”, e.g. "identities (IDs).

In American English, trains have cars, not wagons. I’m not sure if that holds true elsewhere.

Implement a function get_list_of_wagons that accepts an arbitrary amount of positive integers which are the IDs of each wagon. It should then return the given IDs as a list.

“It should then” implies the function first does something (active action) then returns the list. “Accepts” is not an active action.

The train ID system works by assigning the locomotive an ID of 1

It might be worth explaining the term “locomotive”.

But then your friend had to connect two new wagons to the train and forgot to update the system!

“But” and “then” are used to connect ideas; they should not be used to start a sentence.

Now the first two wagons in the list

The list contains wagon IDs, not wagons :slight_smile:

All they can remember is that once the new wagons are moved to the end, the values from the second list should be placed directly after the designated locomotive.

I’m not sure what this means. Does each list have a locomotive? Which one is designated? Designated in what sense? Are the “new” wagons the second list? The word “new” isn’t used previously.

The function should then return the list with the modifications.

The word return is used many times but mostly not monospaced. It would be good to be consistent.

>>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])

This example is rather long. Examples are usually easiest to read when they have enough data to illustrate the problem, but no more.

the routing dict
a routing dict

The docs reference both “the” and “a” routing dict.

The function should then return the routing dict, updated with a key that holds a list of all the stops in order.

Should “list” be “list”?

Implement a function extend_route_information that accepts two dicts .

Should that be dicts?

The first dict contains which cities the train route moves between.

Routes don’t move. The train moved between cities. The route contains cities.

The function should return a consolidated dict with all routing information.

Is this missing “the”? “The function should return a consolidated dict with all the routing information.”

The second dict can contain different properties than the ones shown in the example.

Should that be dict?

Wagons are stored in the depot in grids, with each column in the grid grouped by wagon color.

Are the columns grouped? Or are the wagons grouped into the columns?

“Wagons are stored in the depot in grids, with the wagons grouped by color into columns.”

But for the storage grid to work correctly

Sentences should not start with “but”.

a tuple with (<wagon ID>, <wagon color>).

Tuple notation uses () so the parenthesis should be part of the syntax, i.e. " a tuple with (<wagon ID>, <wagon color>)."

three row lists

Should that be lists?

[(2, "red"),(5, "blue"),(3, "orange")],
[(4, "red"),(9, "blue"),(7, "orange")],
[(8, "red"),(13,"blue"),(11, "orange")]

Should the contents of the list be indented?

1 Like

I left comments on the exercise, since that doesn’t already have a bunch of discussion, so it’s easier to just read it and leave feedback on it. The introduction and about pages have a bunch of discussion already, which makes it much harder to leave feedback without worrying that the feedback was already discussed or is being changed.

Thank you again @MatthijsBlom - your feedback has been really helpful for me in clarifying my thinking.

:scream_cat: I’m not sure how I feel about this either.
And here is a super-fun thing I dug up in the disadvantages section of PEP 448:

>>> *elements, = (1,2,3,4,5)
>>> elements
Out[439]: [1, 2, 3, 4, 5]

#swapping the order yields a tuple
>>> new_elements = *elements,
>>> new_elements
Out[441]: (1, 2, 3, 4, 5)

No…I think that’s what I was trying to express, but I like your phrasing much better. :smile:

No - you’ve nailed it. So the example I was thinking of doesn’t use a * (because in for loops and match expressions, you can use “list like” or “tuple like” syntax):

 input_data = [(1,5,2,7,4), (1,5), (1,), (1,9,3), (1,10,6,3,9,8,4,14,24,7)]
 output_data = [[1,5,2,7,4], [1,5], [1], [1,9,3], [1,10,6,3,9,8,4,14,24,7]]

        for variant, (input_data, output_data) in 
            enumerate(zip(input_data, output_data), start=1):

So its not immediately clear here that unpacking/packing is taking place per usual, since the in is not necessarily recognizable as an assignment to next(iterator).

Ah, I had things mixed up then. I once tried to get the wip exercises visible for maintainers, but got stuck somewhere. Sorry.

I find only *elements, = (1,2,3,4,5) dubious (because I do not know what I should want).

new_elements = *elements, does exactly what I expect: *xs, is syntactic sugar for (*xs,), which is syntactic sugar for tuple(xs).

(Sort of, anyway: it just occured to me that the syntactic sugar story here is more complicated than I thought: (1, *xs, 5) is not trivially rewritten as tuple(...).)

Here, too, it does not matter which one you use: the difference is ignored by Python.

(Indeed, my row becomes a list.)

Feedback on Concept @ 51b91d3:


I worry that the introductory paragraphs might be too abstract for beginners. Terms with (as yet) unclear meaning: unpacking, extracting, unpacked values, the same step, remaining elements / key–value pairs, without a collection, pack, this kind of behavior. I see no fix for this. Fortunately I do not see a need for these paragraphs either; I recommend simply taking them out.

With unpacking, there are some special operators used: * and **.

Oh, if only they were operators! It would be so much easier to be able to say: this kind of input gets mapped to that kind of output.

Aside: I’m very tempted to reflect on the ordering of concepts, but I also need to get this posted sometime, so I try to postpone this reflection.

Multiple assignment is the ability to assign multiple variables in one line.

In one statement.

My mentally modelled Beginner asks: but why would you even want that?

*<variable_name> and **<variable_name> […]

In unpacking contexts, not only variables are allowed, but all expressions as well. Syntactically, anyway.

In packing contexts, only assignment targets are allowed, which I guess are variables only.

There has to be x number of variables on the left side of the = sign and x number of values on the right side of the = sign.

“The number of variables on the left of the = sign and the number of values on the right need to be the same.”

I’m inclined to include an error message about this here.

swapping of variables in lists

Swapping of values.

linear sorting algorithms

What is the linear doing here? Should probably be removed.

It is also possible to assign multiple variables to the same value: […]

I question the value of teaching this. Very rare, not that useful, potentially confusing, yet another thing to learn.

it is therefore possible to unpack a list into variables

The loose language here has me uneasy, but I cannot think anything better right now.

(I’m allergic to «returns a Boolean value if Pac-Man is able to eat the ghost» as well. Maybe I’m extreme in this regard.)

you can use _ to ignore those values

Someone might expect something special to happen when one uses _, but in fact nothing special at all happens: _ is a valid identifier.

(That the REPL treats as special until you assign to it.)

You can also do deep unpacking

Deep unpacking isn’t special. It seems valuable to know why.

Deep unpacking and normal unpacking can be mixed together:

In fact, ‘deep’ unpacking always is ‘mixed’ with ‘normal’ unpacking.

If the unpacking has variables with incorrect placement and/or an incorrect number of values, you will get a ValueError

Ah, the error! I suggest moving it way up, and maybe coming back to it here as mistakes in nested assignments can be harder to figure out.

the remainder values

remainder is not ideal to me, but middle does not work great with first, *rest = ....

Aside: many examples! That’s good.

Some lists could use some shortening or line breaks: now they generate horizontal scrollbars.

Another aside: I noticed many beginners do not think of looking for answers in the official documentation. For this reason I mildly prefer referring to the offical docs rather than second+ hand explanations.

So when unpacking a dict, you can only unpack the keys and not the values

“keys” and “values” maybe shouldn’t be rendered as code.

the values() method

Nitpicky, I know: the values method.

Same for items.

dict.items() generates a tuple

It does not. It produces some dict_items iterable of tuples instead.

each key-value pair:.

As above, I feel key-value shouldn’t be rendered as code. Also: stray period.

As with unpacking, packing uses the same * and ** operators. Packing] is the ability […]

We actually haven’t seen ** in action yet.

Stray ].

This is useful for when

Somewhat stray “for”.

It also makes it possible to perform merges on 2 or more lists/tuples/dicts.

No. That would be unpacking.

As far as I know packing of dicts is only possible in parameter lists. {**d} = ... and such appears illegal.

The next examples are all about unpacking instead of packing.

Packing with function parameters

My gut feeling says that unpacking into argument lists should be introduced first. I estimate that the idea of using polyvariadic functions is more accessible to beginners than the idea of defining them.


The intended meaning here is not immediately obvious to me, so I propose to explain or scrap it.

...     print(args)

Have you considered print(f"{args = }")?

Ew: in assignments, packing results in a list, but in parameter lists it results in a tuple.

[Arguments have to be structured][Positional and keyword arguments] like this

Editing artefact?

iterable or iterator

Shouldn’t be rendered as code (I feel), and all iterators are iterable so this could be simplified to only “iterable”.

1 Like

Thanks for your feedback, I am still processing some.

We will change this

I also removed linear here.

The reason why we don’t use the official docs is that the documentation is very spread. This means we perhaps have to link to 12 different pages. Also as a beginner pythons documentation isn’t always that easy to grasp. I try to use the official docs when it feels appropriate to use the official docs.

We won’t do this since we haven’t introduced string formatting in the python concept.

It is marked as a known issue.

Also I mean if we want to be 100% factual correct so does it create Dictionary View Objects. Built-in Types — Python 3.11.0 documentation
Which is iterable. I am unsure though if we want to explain as such. In the concept we aren’t explaning dicts and dicts view objects. I am thinking of just saying generates an iterable with key-value pairs.

If you do not want to say “iterable”, you could also say

dict.items() generates key–value pairs as tuples


dict.items() contains the key–value pairs as tuples

:eyes: Ignoring the fact that we had an example that used f-string. Lol

Thank you for all your feedback Matthijs. We added you as a collaborator for the concept docs for your collaborations. We fixed/changed a lot of areas you were mentioning.

I will now also close the thread.

I left feedback for the exercise, as well. I didn’t see any response to any of that, do I don’t know if it got lost in the conversation around the concept discussion.


Sorry for missing this. We have reworked the document meaning most of your current feedback is out of date.

To give some general responses:

I am not a native English speaker and I know what a “locomotive” is. If the person reading doesn’t, google does exist.

We use wagons since we feel like it’s the least confusing word. I know that it may not be that “American” although there are other exercises that are using British English that means something else in American: Exercism
This exercise uses “football” in the British sense. Although if it was American English it would say soccer.

But is supposed to bind 2 main clauses. Although it isn’t a law as with most things. I have read literature that has started sentences with “and”.

I think it makes sense to say wagons and not wagons ids.

tdlr: The feedback is very out of date so it is hard to actually apply it.

All words can be Googled. That doesn’t mean we shouldn’t question word choices.

The fact that literature exists which do not follow best practices is not a reason to not follow best practices :slight_smile:

The prose is not consistent in following this. If the exercise is going to say the list contains wagons and mean wagon IDs, that should be explicitly specified, then followed consistently.

That’s good to know. The post requested feedback on the exercise and I gave feedback on the exercise as it existed when I then opened it.

Is there interest in any further feedback on the exercise or should I prefer to file PRs with any corrections I find?