8 ms·
Multiple assignment and tuple unpacking improve Python code readability (2018)
- Ambolia 5y agoA bit sad that this stopped working in lambdas in Python 3, it was nice to have in Python 2: (lambda (x, y): x+y)((2, 3),)
- anfractuosity 5y agoThat's pretty cool, shame it doesn't work in 3 too. Not as nice, but: (lambda x, y: x+y)(*(2, 3)) Seems to work
- klyrs 5y agoNow do lambda i,(x,y): x[i]+y[j] I never really understood the rationale here. Pep8 hates lambdas, which is also baffling. It seems as though tptb don't like lambdas, but there wasn't enough political will to remove them entirely, so they kneecapped them instead. Because... more lines = more readable?? But we've got a walrus operator now, because... less lines = more readable?
- sitkack 5y agoYou golf a lambda and then deride walrus in the same comment? Please explain yourself. And if you really want to make the perfect lambda, you can do that with code generation and frame walking/patching. http://farmdev.com/src/secrets/framehack/index.html http://farmdev.com/src/secrets/framehack/index.html
- wnoise 5y agoThe example lambda isn't "golfing". It's just plain more readable than naming the function. Introducing side-effects in the middle of expressions with the walrus operator is weird semantics, though admittedly sometimes very convenient.
- xigoi 5y agoI like the walrus operator for working with the regex module. if match := re.search(r"Score = (\d+)", data): print(match[1]) else: print("Not found")
- klyrs 5y agoThat wasn't golf. Are you going to tell me that this is more legible? lambda i, t: t[0][i] + t[1][i] edit: and... that frame hack thing is fun, but what does it have to do with anything? Also, note that I don't "deride" the walrus operator. But it does smack of inconsistency. Are we aiming for clean notation or not?
- sitkack 5y agoYou want tuple params back into an inlined function declaration? You can do that with a framehack. One of the examples from the link is Ruby style string interpolation, f-strings in Python, before f-string support. You can fight Python on lambda support, or you can build the language you want. It gives you the tools. I am not arguing about lambda legibility, but tilting at windmills rather than just walking around them. They can't move.
- abecedarius 5y agoDo you really use framehacks in production for the sake of syntactic sugar like this? It's fair that Python can do this sort of thing[0], but it seems like begging for trouble to make it load-bearing without a very strong reason. [0] That level of access was essential when I was working on https://codewords.recurse.com/issues/seven/dragon-taming-with-tailbiter-a-bytecode-compiler https://codewords.recurse.com/issues/seven/dragon-taming-wit...
- sitkack 5y agoIf the local sugar leads to a lower incidence of diabetes, then yes. I have never had a framehack spontaneously break. Self proclaimed Pythonistas of course make a sour face, like their God was offended, but that isn't a logical argument against. I really enjoy your compiler art, please keep it up. Are you doing anything with Wasm these days?
- abecedarius 5y agoThank you! I dislike style nazis too, e.g. carping when Peter Norvig's code won't pass PEP 8. I'm just leery of the expected cost in this kind of case. It can go on working for years until some new complication or some change in the ecosystem makes it suddenly create a really weird problem. Or when you want to try moving to a fancy new Python implementation, you find you have this friction. Matter of judgement where some chance of such messes is paid for by what it can do for you. (Of course when it's less "load bearing" the balance shifts.) With https://coverage.readthedocs.io/en/stable/ https://coverage.readthedocs.io/en/stable/ for example, it used bytecode hacks to do something you couldn't do otherwise, and that's unlikely to mess you up. I have had old C programs go crazy years later in a really hard to debug way because newer compilers may interpret your code like your ex-wife's divorce lawyer (as Kragen put it). Back in the day a lot of us thought we had a different kind of relationship with C compilers, and it'd be fine to code to that informal social contract. (Just a loose analogy.) I'm piddling away at https://github.com/darius/cant https://github.com/darius/cant these days. (Some of the motivation was feeling too confined by Python, actually.) No Wasm, but I'm happy it exists! I tried to make a system like it 20 years ago (Idel) and gave up too soon.
- tln 5y agoFYI here's the thread where tuple parameters were removed. https://mail.python.org/archives/list/python-dev@python.org/thread/LDSQZYLO6E5YG3HSN3MY4BX5CXF73K4Y/#WXPLAKXU7VO33KJSBH5UBR5TFJQPXY6C https://mail.python.org/archives/list/python-dev@python.org/...
- klyrs 5y ago> So the short answer is: yes, we are attached to it, we will miss it. No guarantees about throwing fits, though <wink>. Observe: me, throwing a fit.
- carapace 5y agoI cursed and swore for twenty full minutes when they removed tuple unpacking from parameters. God that pissed me off. Sure, I was one of only a handful of people who ever used it, and it had it's warts, but it was so elegant.
- klyrs 5y ago>>> Hey, we don't like this feature anymore, is anybody using it?" >> Yes, we're using it" > Okay, it's gone" [rage] The one thing I agree with in the thread is the confusion of foo = lambda (x,y): x+y foo(1,2) #boom I think a better approach would be to raise warnings when sole parameters are parenthesized, unless written as foo = lambda (x,y),: x+y which is kinda gross, but cogent with the weirdness of singletons: a, = [1] b = (1,) c = 1, Alas, I've never had Guido's ear, so I shout into the void.
- raymondh 5y agoPeople who respect the law and love sausage should never watch either of them being made ;-) The referenced thread isn't Python's finest hour. A: "Hey, this feature is slightly inconvenient to implement in AST, let's kill it." B: "Many users haven't heard of this, so let's kill it." C: "I find it useful and there's not real advantage in killing it". A&B: "Okay, it's gone". Really, you're better-off just enjoying a language that is mostly nice and mostly well-designed. Don't read the development threads. It will spoil the illusion.
- macintux 5y agoAs an Erlang devotee, when I switched jobs and started writing Python I was happy to find that destructuring worked for function arguments, only to be disappointed when I realized Python 2 was about dead and Python 3 had dropped it because “no one uses it”.
- deleted 5y ago[deleted]
- raymondh 5y agoI also miss this capability. It was super helpful for working with 2-D or 3-D point vectors: lambda (x1, y1), (x2, y2): return abs(x2 - x1) + abs(y2 - y1)
- moelf 5y agothat's all cool and fine, but can you unpack named tuple or struct (data class)? In Julia one of my favorite quality of life improvement in the 1.7: https://github.com/JuliaLang/julia/pull/39285 https://github.com/JuliaLang/julia/pull/39285 Example: julia> X = (this = 3, that = 5) (this = 3, that = 5) julia> this ERROR: UndefVarError: this not defined julia> (;that, this) = X; julia> this 3 julia> that 5 this works with structs as well (in fact anything with getproperty() defined, so like dataframe column too)
- jwilk 5y agoIf you don't mind questionable hacks, you can implement it in Python by abusing the import system: https://stackoverflow.com/a/69860087 https://stackoverflow.com/a/69860087
- kzrdude 5y agonamedtuple yes, it's a tuple so can be unpacked (by position) and dataclasses can not be unpacked. But with the new pattern matching, you can "unpack" dataclass objects that way! https://peps.python.org/pep-0636/ https://peps.python.org/pep-0636/
- jwilk 5y agoJavaScript has it too; it's called "object destructuring": https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#object_destructuring https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- 5y ago
- sitkack 5y agoThis is an excellent article! In functional languages this is called "destructuring assignment". This article could be a great jumping off point into namedtuple[1], the walrus operator [2] and the Pattern Matching[3] (Python 3.10), which assist into getting back into the symbolic domain from the structural domain, though you will have to come up with names! :) Remember that namedtuple has the following additional methods _make Class method that makes a new instance from an existing sequence or iterable _asdict Return a new dict which maps field names to their corresponding values _replace Return a new instance of the named tuple replacing specified fields with new values _fields Tuple of strings listing the field names _field_defaults Dictionary mapping field names to default values. [1] https://docs.python.org/3/library/collections.html#collections.namedtuple https://docs.python.org/3/library/collections.html#collectio... [2] https://peps.python.org/pep-0572/ https://peps.python.org/pep-0572/ [3] https://peps.python.org/pep-0636/ https://peps.python.org/pep-0636/
- agumonkey 5y agoone step at a time people will realize mainstream imperative programming and its derivative has been a long waste of time
- nerdponx 5y agoI don't think that's a valid conclusion here at all.
- agumonkey 5y agothe amount of code that was - point to a structure / object - declare a variable - access the object / structure field and assign it to var - repeat for every information you want to extract from the struct / object is large and now replaced by something transparent
- Espressosaurus 5y agoIn what way is multiple assignment and tuple unpacking not imperative code?
- BiteCode_dev 5y agoAs a python veteran, I'm quite envious of the advanced destructuring you get in some languages. Unpacking doesn't let you specify default values if cardinality doesn't match, nor can you extract dictionaries by keys. Since operators.itemgetter and itertools.slice are limited, I end up either adding a dependency or reinventing the wheel for every project.
- d0mine 5y agothere is a pattern matching in Python 3.10 (match/case) which can match dictionaries. https://peps.python.org/pep-0636/#going-to-the-cloud-mappings https://peps.python.org/pep-0636/#going-to-the-cloud-mapping...
- BiteCode_dev 5y agoYes, but since it's an expression it is very verbose for a simple assignment. Python really needs something like: {foo: "default", mapping} = mapping
- d0mine 5y agoYou might want: foo = mapping.pop("foo", "default") It removes "foo" key from mapping if any. `foo` is "default" if "foo" is missing and `mapping["foo"]` otherwise. Dictionary unpacking compatible with the match/case syntax could look like: {"foo": foo, **rest} = mapping It asserts that there is "foo" key in mapping and puts its value into `foo`.
- deleted 5y ago[deleted]
- z3c0 5y ago> Unpacking doesn't let you specify default values if cardinality doesn't match, nor can you extract dictionaries by keys. I'm a bit confused by this one, but maybe I'm misunderstanding. Does this not accomplish what you're defining? def parse_record(first=0, second=1, **all_the_others) -> dict: return dict(first=first, second=second, **all_the_others) dictionary = dict(first=99, third=2) parse_record(**dictionary) > {"first": 99, "second": 1, "third": 2}
- deleted 5y ago[deleted]
- z3c0 5y agoGreat article. I use unpacking regularly for a lot of the reasons given, as it helps ensure data is in its expected shape. However, I never knew of "star assignment" eg first, second, *others = some_tuple I've opted for less-readable positional indexing several times for not realizing this. Looks like I have some updates to do.
- JadeNB 5y ago> However, I never knew of "star assignment" eg first, second, *others = some_tuple For what it's worth, I think that's often called "splat".
- delaaxe 5y agoYou can also do: first, second, *_ = some_tuple If you don't care about the others
- jwarden 5y agoThis reminds me of an idea I wrote about years ago called Pass-Through Lists, where every assignment is implicitly a destructuring assignment: https://jonathanwarden.com/2014/06/19/pass-through-lists/ https://jonathanwarden.com/2014/06/19/pass-through-lists/
- dominotw 5y agothis list is awesome. made my morning. anyone know why this works in python lst=[] lst+=2, # note , in the end print(lst) #[2]
- HelloNurse 5y agoThe tuple is sequence-like enough to be concatenated with a list: []+(2,) differs from []+[2] only by the type of the temporary object that contains 2.
- klyrs 5y agoThe truly weird thing here is that [] + (2,) is a TypeError: can only concatenate list (not "tuple") to list but L = [] L += (2,) is totally cool. In the end, it makes sense because I can't tell you if []+() should be a tuple or a list, and even if I did, I might have a different answer for ()+[]; whereas L+=bla looks like the type of L should not change.
- maleldil 5y agoThey're different operations. The first is concatenation; the second is extension. Concatenation only works with other lists, but extension works with any iterable.
- carapace 5y agoThe comma operator makes tuples. lst += (2,)
- raymondh 5y agoRoughly, that is true. However, the actual pattern matching rules are a bit more complex: () # Makes a zero-tuple. The parens are required. 2, # Makes a one-tuple. (2,) # Makes a one-tuple. The parens are optional. [2] # Makes a list of size one. [(2)] # Also makes a list of size one. [2,] # Also makes a list of size one. f(x, y) # Call a function with two arguments. f((x, y)) # Make a 2-tuple and call a function with one argument.
- woodruffw 5y agoI've long wanted some kind of "null-filling" destructuring operator in Python, to avoid cases like this: # oops, ValueError on `item = "noequals"` key, value = item.split("=", 1) That alone makes destructuring somewhat limited, unless I'm either willing to validate my lengths ahead of time or do exception-driven control flow. Something like `?=` perhaps, to complement the walrus operator.
- klyrs 5y agoDoes this work for you? key, *value = item.split("=", 1) if value: value = value[0] else: value = None
- woodruffw 5y agoYep, that's a pattern I've applied before. It's not the worst, but I don't think it has the clarity that this would have: key, value ?= item.split("=", 1) Modulo bike shedding over the actual syntax, of course. (Edit: the semantics of this could be even more generic, like Ruby's `[...] rescue value` syntax for turning any raised exception into a default value instead.)
- xtreak29 5y agoThere was a similar PEP https://peps.python.org/pep-0505/ https://peps.python.org/pep-0505/
- srfwx 5y agovalue = value[0] if value else None
- tech2 5y agoIs empty string acceptable in place of None? If so, consider partition: key, sep, value = item.partition("=") If you need to know whether there was a split/match that occurred you could test sep's truthiness.
- deleted 5y ago[deleted]
- a_c 5y agoWhile I agree with the article, readability is rarely precisely defined. What appeared to a veteran programmer be readable can be confusing to novice programmer. It then begs the question aren't veterans meant to understand a program faster. I have seen teams spend countless hours arguing what makes code readable
- 0x0000000 5y agoI share your perspective. When I saw this line below in the article, I thought it was neat and might use it when called for, but I can already see the comments on code review from some team members I've worked with who would find it less readable than the hard coded indices alternative. head, *middle, tail = numbers
- deleted 5y ago[deleted]
- anyaya 5y agoI had no idea about the '*'!!
- carapace 5y agoI didn't see it mentioned (forgive me if I missed it) but you can also assign to slices: >>> n = list(range(10)) >>> n[::3] = 'fizz' >>> n ['f', 1, 2, 'i', 4, 5, 'z', 7, 8, 'z'] It's weird but occasionally useful.
- divs1210 5y agoDestructuring is one of the things i love about Clojure! I’m happy that js and python have also adopted it.
- yawaramin 5y agoI'm fairly sure Python had destructuring syntax before Clojure existed. I remember using it in early 2000s.
- ttyprintk 5y agoI suppose I’d like the smoke-filled rooms deciding readability to mandate square brackets on the left-hand side, or permit ,= without a space when a tool like black has an opinion.
- raymondh 5y agoWouldn't it have been better for the language itself to have made a decision rather than another group of people who are in the business of having strong, enforced decisions about what is allowed? The language specifies that simple statements can be separated by semicolons. Black eliminates this part of the language, essentially overriding Guido's decision. The language specifies that four different quoting characters are available. Black eliminates this part of the language, mandating double quotes in code and single quotes in a repr output, essentially overriding Guido's decision. The language specifies that in-line comments can start anywhere on a line. Black eliminates the writer's option to line-up their comments vertically, essentially overriding Guido's language decision. The language allows and PEP 8 encourages using whitespace to aid readers in reading mathematical expressions just like sympy and LaTex do: ``3x*2 - 2x +5``. Black eliminates this possibility and mandates equal spacing regardless of operator precedence: ``3 * x * 2 - 2 * x + 5``. Once again, this overrides Guido's judgment. Wouldn't be better for Guido to decide what the language allows rather than have a downstream tool narrow the range of what is possible?
- digisign 5y agoYes, and I'm looking forward to blue, and have used nero as well before I knew blue existed. Like the idea of black, and implementation, but its "taste" unfortunately leaves a bit to be desired. Pushing extra spaces into slices is another one I don't care for.
- ttyprintk 5y agogray seems more featureful, despite its slower-moving version odometer: https://pypi.org/project/gray/ https://pypi.org/project/gray/
- shadedtriangle 5y agoThis is probably my most used C++17 feature too: for (auto& [key, value] : persons) The nested bindings are neat. Don’t think you can do that in c++ yet.
- quibono 5y agoFor me it's these little things that make working with newer C++ standards so much easier. Always felt like iterating over anything was cumbersome. No longer the case.
- bobbyi 5y agoI definitely expected "multiple assignment" to refer to this syntax (which Python also has) x = y = None
- raymondh 5y agoYes, that would be correct. a = b = 10 # multiple assignment a, b = b, a # simultaneous (or parallel) assignment
- canjobear 5y agoI love tuple unpacking but it annoys me endlessly that the *arguments are unpacked into a list, not a tuple. Whereas if you use * in a function signature, you get a tuple, not a list. My code always ends up looking like first, *rest = xs rest = tuple(rest) because often I want to hash "rest". Another trick I often use: the_only, = xs This extracts the single element of a one-element iterable and throws an error if xs is empty or has more than one element.