Mapping unpacking assignment
I am proposing to add smth like JS destructing assignment to python. Basically, it will allow unpacking any mapping (should have __getitem__ and keys() methods) into variables. Proposed syntax: ``` m = {"a": 1, "b": 2, "c": 3, "d": 4} {a, b} = m # a: 1, b: 2 {a, b, **rest} = m # a: 1, b: 2, rest: {c: 3, d: 4} ``` It will be rawly equal to: ``` m = {"a": 1, "b": 2, "c": 3, "d": 4} # {a, b} = m a = m["a"] b = m["b"] # {a, b, **rest} rest = {**m} a = rest.pop("a") b = rest.pop("b") ``` This is fully backward compatible feature because currently syntax like above is not supported: ``` {a, b} = m # a: 1, b: 2 ^^^^^^ SyntaxError: cannot assign to set display here. Maybe you meant '==' instead of '='? ```
On Thu, Feb 3, 2022 at 11:51 AM Yurii Karabas <1998uriyyo@gmail.com> wrote:
I am proposing to add smth like JS destructing assignment to python. Basically, it will allow unpacking any mapping (should have __getitem__ and keys() methods) into variables.
Proposed syntax: ``` m = {"a": 1, "b": 2, "c": 3, "d": 4}
{a, b} = m # a: 1, b: 2 {a, b, **rest} = m # a: 1, b: 2, rest: {c: 3, d: 4} ```
Can't you solve the same problem with structural pattern matching against mappings? https://www.python.org/dev/peps/pep-0622/#mapping-patterns Piper Thunstrom Senior Software Engineer/Open Source Maintainer
I can, but it seems like the wrong usage of pattern matching and it requires much more code. ``` match m: case {"a": a, "b": b, **rest}: pass case _: raise ValueError ``` We can use pattern matching to unpack sequences, but we have special syntax for this: ``` s = range(100) match s: case (a, b, *rest): pass case _: raise ValueError (a, b, *rest) = s # solve same problem with one line ```
Yurii Karabas writes:
I am proposing to add smth like JS destructing assignment to python. Basically, it will allow unpacking any mapping (should have __getitem__ and keys() methods) into variables.
Ideas like this have been suggested before, with a number of variations on syntax (specifically with unpacking into a tuple, usually notated without parentheses). I don't recall the more principled objections, but FWIW I'm about -0.5 because IAGNI, and it's one more thing to learn.
On Thu, Feb 03, 2022 at 07:50:50PM -0000, Yurii Karabas wrote:
I am proposing to add smth like JS destructing assignment to python. Basically, it will allow unpacking any mapping (should have __getitem__ and keys() methods) into variables.
This idea would be more compelling if you can show examples from real code (not made up pretend examples) where this would be useful, and contrast the existing solution with the proposed syntax. -- Steve
This feature is far more useful in JS because JS objects double up as mappings - Python dataclasses/attrs models/Pydantic models/named tuples/who knows what are all represented by (typed) objects in JS/TS. As soon as you want to describe your data e.g. by encoding it in a dataclass you'll no longer be able to dictionary-unpack it which will prove very frustrating.
On Mon, 7 Feb 2022 at 00:10, layday--- via Python-ideas <python-ideas@python.org> wrote:
This feature is far more useful in JS because JS objects double up as mappings - Python dataclasses/attrs models/Pydantic models/named tuples/who knows what are all represented by (typed) objects in JS/TS. As soon as you want to describe your data e.g. by encoding it in a dataclass you'll no longer be able to dictionary-unpack it which will prove very frustrating.
That's not entirely true, because there are a ton of places where you can read something generically and then process it afterwards. For instance, I can run "ffprobe -print_format json", then use json.loads to parse the output, and I'll get back a completely generic dictionary with all its information. Suppose I then want to iterate over the video streams: for {codec_type, width, height} in info["streams"]: if codec_type == "video": ... This would be extremely convenient, and it doesn't really work with dataclasses. A match/case block would kinda work here, but it's overkill, like using a for/break block to fetch the first element from a list. But I'm not convinced of the syntax, and there's only a small number of places where I'd actually use this. Consider me +0.25. (BTW Steven, you can consider this to be one real-world example; I lifted this straight from an actual script of mine. But the whole script is a dozen lines long, so this isn't exactly a strong demo case!) ChrisA
I don't think we're saying anything different, I agree. The point I was making was that _after_ loading the data onto a dataclass (or equivalent), you wouldn't be able to unpack it. In JS, you can use destructuring assignment when working with "classes" (and therefore well-defined APIs), which is why it has wider applicability in JS.
On Mon, 7 Feb 2022 at 06:03, layday--- via Python-ideas <python-ideas@python.org> wrote:
I don't think we're saying anything different, I agree. The point I was making was that _after_ loading the data onto a dataclass (or equivalent), you wouldn't be able to unpack it. In JS, you can use destructuring assignment when working with "classes" (and therefore well-defined APIs), which is why it has wider applicability in JS.
Please can you quote some text to show who and what you're responding to? I *think* you're responding to my post, but it's hard to be sure. ChrisA
TL;DR I think Python's distinction between code (e.g. classes) and data (e.g. dicts) is a good distinction to keep -- if you want code, define code, and datcalasses make that very easy these days. And now the detailed version: On Sun, Feb 6, 2022 at 6:05 AM Chris Angelico <rosuav@gmail.com> wrote:
This feature is far more useful in JS because JS objects double up as mappings -
Exactly. And because of this the behaviour is well defined. However, I think that there's alway as issue with "data", e.g. mappings, and "code", e.g. classes with attributes. Given how dynamic Python is, it can be a fuzzy boundary, but I don't think we should make it even fuzzier. (and it JS, there is no boundary at all). Using Chris's example: for {codec_type, width, height} in info["streams"]:
if codec_type == "video": ... This would be extremely convenient, and it doesn't really work with dataclasses.
First, how does this work now? This? for stream in info["streams"]: if stream['codec_type'] == "video": .... Is that so bad? as you have hard_coded codec_type, I suppose it's a bit more kludgy, but if the codec_type was in a variable, it would be more convenient. As for dataclasses, this is what i mean by "code" vs "data" -- if you know when you are writing the code exactly what key (fields, etc) you expect , and you want to be able to work with that data model as code (e.g. attribute access, maybe some methods, then you do: In [10]: @dataclass ...: class Stream: ...: codec_type : str ...: width: int ...: height: int And if you have that data in a dict (say, from JSON, then you can extract it like this: In [11]: stream_info = {'codec_type': 'video', ...: 'width': 1024, ...: 'height': 768, ...: } In [12]: stream = Stream(**stream_info) In [13]: stream Out[13]: Stream(codec_type='video', width=1024, height=768) That only works if you dict is in exactly the right form, but that would be the case anyway. Granted, that's a lot of code for a quickscript, but if it's a quick script, just work with the dict. IN practice, it's not so simple when you have a more nested data structure, but I don't think this proposal would help with that, and it's not hard to build that on top of dataclasses either, see Pydantic, or attrs, or my own half-baked flexi project: https://github.com/PythonCHB/flexi (note: half-baked because the public project is not well tested nor documented, but I am using this very code in a pretty substantial system with a very highly nested structure: https://adios.orr.noaa.gov (and the code: https://github.com/NOAA-ORR-ERD/adios_oil_database) -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
On Mon, 7 Feb 2022 at 15:17, Christopher Barker <pythonchb@gmail.com> wrote:
Using Chris's example:
for {codec_type, width, height} in info["streams"]: if codec_type == "video": ... This would be extremely convenient, and it doesn't really work with dataclasses.
First, how does this work now? This?
for stream in info["streams"]: if stream['codec_type'] == "video": ....
Is that so bad? as you have hard_coded codec_type, I suppose it's a bit more kludgy, but if the codec_type was in a variable, it would be more convenient.
Yes, and it's not hugely terrible, which is why I'm not pushing hard for this sort of unpacking. (To be honest, I don't use it much in JS either.)
As for dataclasses, this is what i mean by "code" vs "data" -- if you know when you are writing the code exactly what key (fields, etc) you expect , and you want to be able to work with that data model as code (e.g. attribute access, maybe some methods, then you do:
In [10]: @dataclass ...: class Stream: ...: codec_type : str ...: width: int ...: height: int
And if you have that data in a dict (say, from JSON, then you can extract it like this:
In [11]: stream_info = {'codec_type': 'video', ...: 'width': 1024, ...: 'height': 768, ...: }
In [12]: stream = Stream(**stream_info)
In [13]: stream Out[13]: Stream(codec_type='video', width=1024, height=768)
That only works if you dict is in exactly the right form, but that would be the case anyway.
One very *very* important aspect of a huge number of JSON-based protocols is that they absolutely will not break if new elements are added. In other words, I look at the things I'm interested in, but those streams also have a ton of other information (frame rate, metadata, pixel format), which could get augmented at any time, and I should just happily ignore the parts I'm not looking for. Making that work with dataclasses (a) is even more boilerplate, and (b) would obscure the relationship between the dataclass and the JSON schema. I'm not sure what you mean here about code vs data. What is the difference that you're drawing? Ultimately, I need to read a particular data structure and find the interesting parts of it. It's not about code. The only code is "iterate over info->streams, look at the codec_type, width, height, perform arithmetic on videos".
Granted, that's a lot of code for a quickscript, but if it's a quick script, just work with the dict.
Which is exactly the OP's view. Work with the dict. But preferably, make it easier to work with the dict.
IN practice, it's not so simple when you have a more nested data structure, but I don't think this proposal would help with that, and it's not hard to build that on top of dataclasses either, see Pydantic, or attrs, or my own half-baked flexi project:
https://github.com/PythonCHB/flexi
(note: half-baked because the public project is not well tested nor documented, but I am using this very code in a pretty substantial system with a very highly nested structure:
(and the code: https://github.com/NOAA-ORR-ERD/adios_oil_database)
If your data structure is incredibly rigid, you can take advantage of sequence unpacking, and it'll work really nicely: for x, y, z in data["points_3d"]: ... IMO it's not unreasonable to want the same kind of flexibility for named data. The trouble is, it would end up quite complicated, which inevitably means we're going to come back to match/case syntax. There have been thoughts thrown around in the past of having a "match assignment" concept. The OP is far from the first to notice the parallel. Maybe that's what we should be looking at - but the biggest question is syntax. What should it look like? # A different type of assignment operator? {codec_type, width, height} $= info["streams"] # A special assignment target? match {codec_type, width, height} = info["streams"] # A special assignment source? {codec_type, width, height = match info["streams"] # Something else? I'm not a fan of any of these, but maybe someone can figure out a better way to write it. It's one of those things where, if it existed in the language, I would certainly use it, but it's not something that causes all that much pain. ChrisA
On Sun, Feb 6, 2022 at 9:42 PM Chris Angelico <rosuav@gmail.com> wrote:
As for dataclasses, this is what i mean by "code" vs "data" -- if you know when you are writing the code exactly what key (fields, etc) you expect , and you want to be able to work with that data model as code (e.g. attribute access, maybe some methods, then you do:
In [10]: @dataclass ...: class Stream: ...: codec_type : str ...: width: int ...: height: int
And if you have that data in a dict (say, from JSON, then you can extract it like this:
In [11]: stream_info = {'codec_type': 'video', ...: 'width': 1024, ...: 'height': 768, ...: }
In [12]: stream = Stream(**stream_info)
In [13]: stream Out[13]: Stream(codec_type='video', width=1024, height=768)
That only works if you dict is in exactly the right form, but that would be the case anyway.
One very *very* important aspect of a huge number of JSON-based protocols is that they absolutely will not break if new elements are added. In other words, I look at the things I'm interested in, but those streams also have a ton of other information (frame rate, metadata, pixel format), which could get augmented at any time, and I should just happily ignore the parts I'm not looking for. Making that work with dataclasses (a) is even more boilerplate, and (b) would obscure the relationship between the dataclass and the JSON schema.
I believe some folks have asked for the ability for **kwargs to be tacked on to the dataclass generated __init__ -- I don't know if it will happen, but that would address this use case. Not sure what you mean by "obscure the relationship between the dataclass and the JSON schema." I guess you mean that the dataclass will then accept non-schema conforming JSON, but if you don't want it to do that, then do allow that. For my part, in an application that I'm doing all of of JSON -- data classes, I explicitly add and "extra_data" field, so I can capture anything in the JSON that doesn't have a "proper" place. After I posted, I realized that dataclasses are probably not the simplest solution -- but SimpleNamespace could be: In [9]: stream_info = {'codec_type': 'video', ...: 'width': 1024, ...: 'height': 768, ...: } In [10]: stream = types.SimpleNamespace(**stream_info) In [11]: stream.codec_type Out[11]: 'video' In [12]: stream.height Out[12]: 768 In [13]: stream.width Out[13]: 1024 In any case, if you don't like how dataclasses or SimpleNamespace does it, then write you own custom class / converter -- I don't see the need for it to be a language feature. I'm not sure what you mean here about code vs data. What is the
difference that you're drawing? Ultimately, I need to read a particular data structure and find the interesting parts of it. It's not about code. The only code is "iterate over info->streams, look at the codec_type, width, height, perform arithmetic on videos".
The distinction I'm trying to draw (and I did say it was a fuzzy one in Python) is that data are things you can store in variables -- e.g. the keys of a dict can be hard coded (known at code-writing time) or stored in a variable. Code is things like variable and attribute names that have to known at code-writing time (baring metaprogramming techniques, get/setattr, etc). In this case, we are looking to auto-extract variable from a dict -- you can't even start to write that code unless you know what the keys in the dict are -- if that's the case, then you know (at least part of) the schema, and you can use dataclasses, etc, and get your code. I"ve worked with systems (the netcdf4 library for example, if you want an obscure one :-) ) that auto translate essentially keys in a dict to object attributes. it seems pretty nifty at first: ds = Dataset("the_file.nc") ds.sea_surface_temp.units But it ends up just making things harder -- you need to poke into the file to see what names will be there, it's actually harder to introspect (can't just look at .keys() ) -- and things really go to heck if the keys in your data don't follow Python variable naming rules: In [14]: stream_info = {'codec-type': 'video', ...: 'width': 1024, ...: 'height': 768, ...: } In [15]: stream = types.SimpleNamespace(**stream_info) In [16]: stream.codec-type --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-16-26291ce709a5> in <module> ----> 1 stream.codec-type AttributeError: 'types.SimpleNamespace' object has no attribute 'codec' In [17]: stream.codec_type --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-17-6225e2eacec1> in <module> ----> 1 stream.codec_type AttributeError: 'types.SimpleNamespace' object has no attribute 'codec_type' In [18]: getattr(stream, 'codec-type') Out[18]: 'video' So: I say, keep your data in dicts, and if you want to load a code object with that data, do it in a clearly defined way. Again, in a quick script, maybe it'd be helpful occasionally, mostly saving some typing (all those darn square brackets and quotes)[*] -- but I don't think that's worth a language feature. -CHB [*] I'm not being facetious here -- I write a lot of quick scripts, and DO find typing: this.that a lot easier than this['that'] But I don't think it's worth a language feature And now that I've thought about it -- maybe I'll start using the SimpleNamespace trick in some of those quick scripts. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
On Mon, 7 Feb 2022 at 17:35, Christopher Barker <pythonchb@gmail.com> wrote:
After I posted, I realized that dataclasses are probably not the simplest solution -- but SimpleNamespace could be:
In [9]: stream_info = {'codec_type': 'video', ...: 'width': 1024, ...: 'height': 768, ...: }
In [10]: stream = types.SimpleNamespace(**stream_info)
In [11]: stream.codec_type Out[11]: 'video'
In [12]: stream.height Out[12]: 768
In [13]: stream.width Out[13]: 1024
In any case, if you don't like how dataclasses or SimpleNamespace does it, then write you own custom class / converter -- I don't see the need for it to be a language feature.
This doesn't really buy much. (You say in your footnote that stream.width is nicer than stream["width"], and you're absolutely right, but it's still not as convenient as unpacking.) This is particularly notable when you're unpacking something deep in the structure, and you might have something like: {width, height} = info["streams"][0]["panel"][1] # hypothetical example which means you either have to create a variable and then still repeat that everywhere, or repeat the long part. Honestly, neither dataclasses nor SimpleNamespace buy enough over direct dict access (with some strategic variable assignments) to be worth the hassle. Which basically means we're back at status quo: it's easy to unpack rigid, inflexible structures, and hard to unpack named ones that have room for augmentation. Which seems a little backwards.
I'm not sure what you mean here about code vs data. What is the difference that you're drawing? Ultimately, I need to read a particular data structure and find the interesting parts of it. It's not about code. The only code is "iterate over info->streams, look at the codec_type, width, height, perform arithmetic on videos".
The distinction I'm trying to draw (and I did say it was a fuzzy one in Python) is that data are things you can store in variables -- e.g. the keys of a dict can be hard coded (known at code-writing time) or stored in a variable.
Code is things like variable and attribute names that have to known at code-writing time (baring metaprogramming techniques, get/setattr, etc).
I think I see what you mean, but yeah, it's a very very fuzzy distinction. For instance, let's say it's not a JSON file, but something in some other format (a Europa Universalis IV savefile, perhaps). I could write a parser that steps byte-by-byte through the file and builds a dict out of it; or I could reach for yacc and define a full grammar, just like a programming language does. (Case in point: I actually did design a recursive grammar for an EU4 savefile, although not in Python, since Python doesn't have convenient grammar tools available. This might change in the future, which would be very convenient.) This actually reminds me of some of the discussions regarding what ended up becoming f-strings. You have the same "dict or variable?" consideration, but the other way around: def spam(): bird = "Norwegian Blue" volts = 4e6 return "{volts}V insufficient to voom {bird}".format(**locals()) Python gives us some tools for switching viewpoints between "this is data" and "this is code", and your parenthetical comment about metaprogramming actually fits into that too (and believe you me, it hurts when I'm stuck in a language without it!).
In this case, we are looking to auto-extract variable from a dict -- you can't even start to write that code unless you know what the keys in the dict are -- if that's the case, then you know (at least part of) the schema, and you can use dataclasses, etc, and get your code.
Yeah, but why write a dataclass and then write the code, instead of just writing the code? There are exceptions, of course; if you're parsing something that simply doesn't *have* all the information, and you have to go "take four bytes, that's an integer called Volts, take bytes until you get a \0, that's an ASCII string called Bird, take four bytes, we don't know what they are", then it makes perfect sense to make a dataclass (again, have done this); but when the file can be parsed without any external references, I'd rather do that, and stay generic.
So: I say, keep your data in dicts, and if you want to load a code object with that data, do it in a clearly defined way.
Exactly what the OP wanted, except instead of loading *a* code object, loading *several* code objects at once, to avoid the repetition. :) ChrisA
On Feb 7, 2022, at 1:55 AM, Chris Angelico <rosuav@gmail.com> wrote:
…
def spam(): bird = "Norwegian Blue" volts = 4e6 return "{volts}V insufficient to voom {bird}".format(**locals())
This is completely off topic, but: the better way to do this is with .format_map(locals()). This public service announcement is part of my goal to increase knowledge of format_map(). Eric
On Tue, 8 Feb 2022 at 01:27, Eric V. Smith <eric@trueblade.com> wrote:
On Feb 7, 2022, at 1:55 AM, Chris Angelico <rosuav@gmail.com> wrote:
…
def spam(): bird = "Norwegian Blue" volts = 4e6 return "{volts}V insufficient to voom {bird}".format(**locals())
This is completely off topic, but: the better way to do this is with .format_map(locals()).
This public service announcement is part of my goal to increase knowledge of format_map().
Fair point. Still, my original statement was that it is even better to do it as: return f"{volts}V insufficient to voom {bird}" which treats it fully as code. But if the information wasn't from locals and was from some other dict, then yes, I fully accept the correction, format_map would have been the correct choice. ChrisA
On Mon, 7 Feb 2022 at 16:43, Chris Angelico <rosuav@gmail.com> wrote:
There have been thoughts thrown around in the past of having a "match assignment" concept. The OP is far from the first to notice the parallel. Maybe that's what we should be looking at - but the biggest question is syntax. What should it look like?
# A different type of assignment operator? {codec_type, width, height} $= info["streams"] # A special assignment target? match {codec_type, width, height} = info["streams"] # A special assignment source? {codec_type, width, height = match info["streams"] # Something else?
A question and two thoughts... (1) Has there been any way to use the operator "=" in which the right hand side was not just an expression specifying an object? (2) For the form of the assignment target, I think an analogy with the reception of function arguments could also be considered. Note the intended assignment could be done by locals().update( (lambda codec_type, width, height, **rest: locals())( **info["streams"] ) ) if none of the variables were not local. (3) If some keys in the mapping object may not appear on the left hand side, then demanding **rest part would be consistent with the case of iterable unpacking, structural pattern matching, as well as the case for function arguments. For an assignment without **rest, the assignment source can just be sliced beforehand. Best regards, Takuo Matsuoka
On Fri, 11 Feb 2022 at 00:33, Matsuoka Takuo <motogeomtop@gmail.com> wrote:
On Mon, 7 Feb 2022 at 16:43, Chris Angelico <rosuav@gmail.com> wrote:
There have been thoughts thrown around in the past of having a "match assignment" concept. The OP is far from the first to notice the parallel. Maybe that's what we should be looking at - but the biggest question is syntax. What should it look like?
# A different type of assignment operator? {codec_type, width, height} $= info["streams"] # A special assignment target? match {codec_type, width, height} = info["streams"] # A special assignment source? {codec_type, width, height = match info["streams"] # Something else?
A question and two thoughts...
(1) Has there been any way to use the operator "=" in which the right hand side was not just an expression specifying an object?
I think not, with the caveat that "a = b = 1" has multiple assignment targets, so the "right hand side" has to be interpreted as the 1 and not "b = 1". The "special assignment source" wouldn't really work as an object. It would still, in effect, be a syntactic structure (you wouldn't be able to call "f(match x)", for instance - it's not an expression), a type of assignment that isn't simple object referencing.
(2) For the form of the assignment target, I think an analogy with the reception of function arguments could also be considered. Note the intended assignment could be done by
locals().update( (lambda codec_type, width, height, **rest: locals())( **info["streams"] ) )
if none of the variables were not local.
You can't actually update locals(), unless it happens to be an alias for globals(), so that doesn't technically work - but it's a good way to say "kinda like this".
(3) If some keys in the mapping object may not appear on the left hand side, then demanding **rest part would be consistent with the case of iterable unpacking, structural pattern matching, as well as the case for function arguments. For an assignment without **rest, the assignment source can just be sliced beforehand.
Yeah, that's something that could be considered, but it's worth noting that PEP 622 says that match syntax doesn't demand {**_} for mapping patterns. (You can use **rest to capture it, but you don't have to.) Personally, I think the argument in the PEP would apply here too: """ Extra keys in the subject are ignored even if **rest is not present. This is different from sequence pattern, where extra items will cause a match to fail. But mappings are actually different from sequences: they have natural structural sub-typing behavior, i.e., passing a dictionary with extra keys somewhere will likely just work. """ In any case, it's probably simplest to look at a "match assignment" syntax, and then define that it is absolutely equivalent to the equivalent match block: match TARGET: case MATCH_EXPR: pass case _: raise ValueError # <=> match MATCH_EXPR = TARGET But the crucial thing is to figure out the syntax. Without good syntax, this is not of interest. I'm personally inclined towards marking the assignment target in some way, since that would extrapolate conveniently to all other assignment types (eg "for match {MATCH_EXPR} in iterable:"), but I don't like the specific placeholder example I've used here. ChrisA
Just one note: On Thu, Feb 10, 2022 at 6:56 AM Chris Angelico <rosuav@gmail.com> wrote:
(2) For the form of the assignment target, I think an analogy with the reception of function arguments could also be considered.
I think so -- maybe only because it's new, but folks are certainly far more familiar with **kwargs than pattern matching.
For an assignment without **rest, the assignment source can just be sliced beforehand.
Yeah, that's something that could be considered, but it's worth noting that PEP 622 says that match syntax doesn't demand {**_} for mapping patterns.
Extra keys in the subject are ignored even if **rest is not present. This is different from sequence pattern, where extra items will cause a match to fail. But mappings are actually different from sequences: they have natural structural sub-typing behavior, i.e., passing a dictionary with extra keys somewhere will likely just work.
Except in unpacking in a function call -- which I this "feels" more like to me. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
On Fri, 11 Feb 2022 at 04:02, Christopher Barker <pythonchb@gmail.com> wrote:
Just one note:
On Thu, Feb 10, 2022 at 6:56 AM Chris Angelico <rosuav@gmail.com> wrote:
(2) For the form of the assignment target, I think an analogy with the reception of function arguments could also be considered.
I think so -- maybe only because it's new, but folks are certainly far more familiar with **kwargs than pattern matching.
Structural pattern matching does a more general assignment than happens at passing of function arguments. So the latter is simpler, but is sufficient (at least conceptually) for the form of assignment considered here. However, both of them may lead to distinct pieces of syntax, both of which might possibly be useful. Best regards, Takuo Matsuoka
I'd be in favor of a nice syntax for named mapping unpacking. But while we are at discussion - I'd point out it is "possible" (not easy) with current day syntax, by abusing the command "import" - One can set appropriate import hooks that would enable either for the current module, or maybe, in a context ("with") block the use of import targeting a variable - so, the syntax from mymap import key1, key2 can work. Downside: it will be much slower than an assignment. I have something similar working, but it is currently based on monkey patching builtins.__import__ - upgrading it to properly using the import machinery is on the roadmap. On Fri, Feb 11, 2022 at 8:25 AM Matsuoka Takuo <motogeomtop@gmail.com> wrote:
On Fri, 11 Feb 2022 at 04:02, Christopher Barker <pythonchb@gmail.com> wrote:
Just one note:
On Thu, Feb 10, 2022 at 6:56 AM Chris Angelico <rosuav@gmail.com> wrote:
(2) For the form of the assignment target, I think an analogy with the reception of function arguments could also be considered.
I think so -- maybe only because it's new, but folks are certainly far
more familiar with **kwargs than pattern matching.
Structural pattern matching does a more general assignment than happens at passing of function arguments. So the latter is simpler, but is sufficient (at least conceptually) for the form of assignment considered here. However, both of them may lead to distinct pieces of syntax, both of which might possibly be useful.
Best regards, Takuo Matsuoka _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/N6MXW3... Code of Conduct: http://python.org/psf/codeofconduct/
I'm sorry for the inaccuracy, and thank you for the corrections! On Fri, 11 Feb 2022 at 01:56, Chris Angelico <rosuav@gmail.com> wrote:
On Fri, 11 Feb 2022 at 00:33, Matsuoka Takuo <motogeomtop@gmail.com> wrote:
......
But the crucial thing is to figure out the syntax. Without good syntax, this is not of interest.
I'm personally inclined towards marking the assignment target in some way, since that would extrapolate conveniently to all other assignment types (eg "for match {MATCH_EXPR} in iterable:"), but I don't like the specific placeholder example I've used here.
Having special notation for the assignment target sounds like a good way to me. Best regards, Takuo Matsuoka
On Thu, Feb 3, 2022 at 11:53 AM Yurii Karabas <1998uriyyo@gmail.com> wrote:
Proposed syntax:
m = {"a": 1, "b": 2, "c": 3, "d": 4}
{a, b} = m # a: 1, b: 2 {a, b, **rest} = m # a: 1, b: 2, rest: {c: 3, d: 4}
as equivalent to this from PEP 634:
match m: case {"a": a, "b": b, **rest}: pass case _: raise ValueError
What I find particularly unsatisfying is that the left hand side syntax in your proposal {a, b, **rest} is not at all like, the case syntax {"a": a, "b": b, **rest} so that's twice as much syntax to learn. *IF* this is really used often enough that it is worth adding to the language it should be a variation of match, something like: match m as {"a": a, "b": b, **rest} The match clause defaults to doing nothing if the value doesn't match but that's less useful here (and harder to deal with since figuring out that a, b, and rest are still unbound is clumsy, so I would agree that it should raise ValueError if there's no match. --- Bruce
participants (11)
-
Bruce Leban
-
Chris Angelico
-
Christopher Barker
-
Eric V. Smith
-
Joao S. O. Bueno
-
layday@protonmail.com
-
Matsuoka Takuo
-
Piper Thunstrom
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Yurii Karabas