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

I and @Bethanyg have authored a new exercise (Locomotive Engineer) and a new concept (Unpacking and Multiple Assignment) that we’d like to open up for feedback from the exercism community. We would like some more eyes on it and get feedback on:

  1. Wording/spelling and if there needs to be rephrasing etc.
  2. Check if the exercise itself has bugs and if everything is clear, solvable etc.

In the spirit of the new Exercism blog post, we’d like all feedback/comments to be in this thread and not on github.
We think this “feedback” period for the exercise will be the easiest time to merge in medium/small changes before we go public and let students tackle it.
Thanks for your help!

Exercise: Locomotive Engineer in Python on Exercism

Concept: Unpacking And Multiple Assignment in Python on Exercism

1 Like

Here will be a list of known issues(I will update this list as new issues come in):

  • Missing link for [Positional and keyword arguments]

  • Missing error messages for tests

Great work :slightly_smiling_face:

Problem though: the status is still WIP so no-one can actually see this to try it yet.

You can test in incognito whether it shows up. Maybe update that and add a link into the OP it when it’s there too? Then I’ll delete this post :slightly_smiling_face:

Is there a way to test the exercise in the online editor while it is wip?

If you’re a maintainer then yes I believe it works. Otherwise, no.

@ErikSchierboom can give you maintainer privileges on the website so you can try it :slightly_smiling_face:

As of yet I have only read introduction.md. Tomorrow I might try the exercise. I do not see about.md on the website yet, and haven’t yet read it either.

using iteration

I am slightly dismayed to realize that this really is the underlying mechanism. Naively I expected first, *mids, last = range(100_000_000) to be fast, through __getitem__ and slices. Of course it cannot be: mids will be a list in any case. Too bad when you do not need it.

[…] there are some special operators used: * and ** .

They are not exactly operators. Both *() and **{} by themselves are syntactically invalid; they need specific syntactical contexts: e.g. (*(),) and {**{}}.

When unpacking a list or tuple, […]

Any iterable, including strings! But I get that iterables are better left to be explained another day.

When unpacking a dictionary, the ** operator can be used to assign all the remaining key-value pairs to a variable.

Not sure I follow. Is this about passing items from a dictionary as arguments to a function? What happened before, so that now some key-value pairs are ‘remaining’?

When these operators are used without a collection, they pack a number of values into a list , tuple , or dict .

Is this now about using * and ** in function definition headers? I don’t think lists are involved there.

Both syntactical forms * and ** are used in four independent ways:

  • as mathematical operators
  • in expression contexts: as unpacking pseudo-operators
  • in function call contexts: unpacking into argument lists
  • in function definition headers: grouping of arguments into tuples and dicts.

The latter three surely are evokative of each other (by design), but they are distinct.

Hi, Thanks for your feedback. To read the about.md go into inkognito mode on your browser.

Me and Bethany will go through your feedback later today but I would say some of what you are mentining are being explaind in more detail in the about.md.


Not sure I follow. Is this about passing items from a dictionary as arguments to a function? What happened before, so that now some key-value pairs are ‘remaining’?

We mean like this:

def my_function(a, **kwargs):
    print(kwargs)

my_function(a=25, b = 24, c = 23)
# => {b: 24, c: 23}

In short it takes the “remaning” in the form that if that key-word parameter isn’t defineded it goes into the remaning(**kwargs) if that makes seense.

@Meatball I’ve given you maintainer permissions so you should be able to see the exercise

I can’t open the exercise in the online editor still, does it take some time until the changes updates?

@Meatball The exercise has the status wip, which means that it isn’t shown at all. If you want it to be available to maintainers but not students, you’ll have to set it to beta. The wip status is for exercises that are not to be tested yet, likely because documents are missing or have not yet been written.

Thanks for leting me know. I will look into changing the tag

Although there are exercises taged as beta which is accesable to non mantiners for the python track

@ErikSchierboom, all of the publicly visible concept exercises on the Python track are set to beta (as opposed to active), which is why we went with the WIP. We didn’t want students seeing and working through the exercise before we got mentor/maintainer/volunteer feedback on it.

My understanding was that WIP (in addition to being a placeholder for incomplete exercises/docs) would allow maintainers to work through the exercise prior to making it publicly visible … or is there something about the Python config that is making beta exercises publicly available to students when they shouldn’t be? Should we be setting the public exercises to active, and the “preview” (non-public) exercises to beta?

I already was in incognito mode. Turns out I need to sign out.

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

Going by the docs, about.md serves as a reference + elaboration. It would be fine (but maybe not necessary) for it to be longer than introduction.md. Edit: this sentence is true but irrelevant.

about.md introduction.md could really use this context.

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

There is the concept introduction.md vs the exercise introduction.md. The concept introdcution.md is very short, and the about.md 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 introduction.md 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
 3

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

>>> last_number
8

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/529537938.py", 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 about.md still coming up; I’m just letting myself be distracted by forum posts.

Yup.

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

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?