Syntactic sugar for assignment statements: one value to multiple targets?
Hi everyone! Longtime lurker, hardly an expert, but I've been using Python for various projects since 2007 and love it. I'm looking for either (A) suggestions on how to do a very common operation elegantly and Pythonically, or (B) input on whether my proposal is PEP-able, assuming there's no answer to A. (The proposal is sort of like the inverse of PEP 3132; I don't think it has been proposed before, sorry if I missed it.) Anyway, I frequently need to initialize several variables to the same value, as I'm sure many do. Sometimes the value is a constant, often zero; sometimes it's more particular, such as defaultdict(list). I use dict() below. Target lists using comma separation are great, but they don't work very well for this task. What I want is something like a,b,c,d,e = *dict() where * in this context means something like "assign separately to all." I'm not sure that * would the best sugar for this, but the normal meaning of * doesn't seem as if it would ever be valid in this case, and it somehow feels right (to me, anyway). Statements fitting the form above would get expanded during parsing to a sequence of separate assignments (a = dict(); b = dict(); c = dict() and so forth.) That's all there is to it. Compared to the patterns below, it's svelte, less copy-paste-y (so it removes an opportunity for inconsistency, where I remember to change a-d to defaultdict(list) but forget with e), and it doesn't require me to keep count of the number of variables I'm initializing. This would update section 6.2 of the language reference and require a small grammar expansion. But: Is there already a good way to do this that I just don't know? Below, I compare four obvious patterns, three of which are correct but annoying and one of which is incorrect in a way which used to surprise me when I was starting out. # Option 1 (separate lines) # Verbose and annoying, particularly when the varnames are long and of irregular length a = dict() b = dict() c = dict() d = dict() e = dict() # Option 2 (one line) # More concise but still pretty annoying, and hard to read (alternates variables and assignments) a = dict(); b = dict(); c = dict(); d = dict(); e = dict() # Option 3 (multiple target list: this seems the most Pythonic, and is normally what I use) # Concise, separates variables from assignments, but somewhat annoying; have to change individually and track numbers on both sides. a,b,c,d,e = dict(),dict(),dict(),dict(),dict() # Option 4 (iterable multiplication) # Looks better, and if the dict() should be something else, you only have to change it once, but the extra brackets are ugly and you still have to keep count of the targets... a,b,c,d,e = [dict()] * 5 # and it will bite you... >>> a[1] = 1 >>> b {1: 1} >>> id(a) == id(b) True # Gotcha! # Other forms of 4 also have this behavior: a,b,c,d,e = ({},) * 5 >>> a[1] = 1 >>> b {1: 1} Alternatively, is there a version of iterable multiplication that creates new objects rather than just copying the reference? That would solve part of the problem, though it would still look clunky and you'd still have to keep count. Any thoughts? Thanks! -- http://mail.python.org/mailman/listinfo/python-list
Re: Syntactic sugar for assignment statements: one value to multiple targets?
Thanks for all the discussion on this. Very illuminating. Sorry for the long delay in responding--deadlines intervened. I will use the list comprehension syntax for the foreseeable future. Tim, I agree with you about the slurping in final position--it's actually quite surprising. As I'm sure you realized, that behavior makes your 'tidier' version: a,b,c,d,e, *more_dict_generator = (dict() for _ in itertools.count()) break with a MemoryError, which I don't think is the result that most people would expect. Stephen wrote: > While slightly ugly, it doesn't seem ugly enough to justify the > extra complexity of special syntax for such a special case. You're probably right (although for my coding this multiple assignment scenario is a pretty ordinary case.) Anyway, I'll shop the a,b,c = *dict() syntax over to python-ideas just to see what they say. Thanks again, everyone! Happy Python. On Aug 3, 7:25 am, Tim Chase wrote: > On 08/03/2011 03:36 AM, Katriel Cohn-Gordon wrote: > > > On Wed, Aug 3, 2011 at 9:25 AM, Steven D'Aprano wrote: > >> a, b, c, d, e = [dict() for i in range(5)] > > > I think this is good code -- if you want five different dicts, > > then you should call dict five times. Otherwise Python will > > magically call your expression more than once, which isn't > > very nice. And what if your datatype constructor has > > side-effects? > > If the side-effects are correct behavior (perhaps opening files, > network connections, or even updating a class variable) then > constructor side-effects are just doing what they're supposed to. > E.g. something I use somewhat regularly in my code[*]: > > a,b,c,d = (file('file%i.txt', 'w') for i in range(4)) > > If the side-effects aren't performing the correct behavior, fix > the constructor. :) > > -tkc > > [*] okay, it's more like > > (features, > adjustments, > internet, > ) = (file(fname) for fname in ( > 'features.txt', > 'adjustments.txt', > 'internet.txt' > ) > > or even > > (features, > adjustments, > internet, > ) = ( > set( > line.strip().upper() > for line > in file(fname) > if line.strip() > ) > for fname in ( > 'features.txt', > 'adjustments.txt', > 'internet.txt' > ) > > to load various set() data from text-files. -- http://mail.python.org/mailman/listinfo/python-list
Re: Syntactic sugar for assignment statements: one value to multiple targets?
On Aug 16, 4:39 pm, "Martin P. Hellwig" wrote: > On 03/08/2011 02:45, gc wrote: > > > > a,b,c,d,e = *dict() > > > where * in this context means something like "assign separately to > > all. > . . . it has a certain code smell to it. > I would love to see an example where you would need such a construct. Perfectly reasonable request! Maybe there aren't as many cases when multiple variables need to be initialized to the same value as I think there are. I'm a heavy user of collections, especially counters and defaultdicts. One frequent pattern involves boiling (typically) SQLite records down into Python data structures for further manipulation. (OK, arguably that has some code smell right there--but I often have to do very expensive analysis on large subsets with complex definitions which can be very expensive to pull, sometimes requiring table scans over tens of gigabytes. I *like* being able to use dicts or other structures as a way to cache and structure query results in ways amenable to analysis procedures, even if it doesn't impress Joe Celko.) defaultdict(list) is a very clean way to do this. I'll often have four or five of them collecting different subsets of a single SQL pull, as in: # PROPOSED SYNTAX: all_pets_by_pet_store, blue_dogs_by_pet_store, green_cats_by_pet_store, red_cats_and_birds_by_pet_store = *defautdict(list) # (Yes, indexes on color and kind would speed up this query, but the actual fields can be way quite complex and have much higher cardinality.) for pet_store, pet_kind, pet_color, pet_weight, pet_height in cur.execute("""SELECT s, k, c, w, h FROM SuperExpensivePetTable WHERE CostlyCriterionA(criterion_basis) IN("you", "get", "the", "idea")"""): all_pets_by_pet_store[pet_store].append(pet_weight, pet_height) if pet_color in ("Blue", "Cyan", "Dark Blue") and pet_kind in ("Dog", "Puppy"): blue_dogs_by_pet_store[pet_store].append(pet_weight, pet_height) #... and so forth all_pets_analysis = BootstrappedMarkovDecisionForestFromHell(all_pets_by_pet_store) blue_dogs_analysis = BootstrappedMarkovDecisionForestFromHell(blue_dogs_by_pet_store) red_cats_and_birds_analysis = BMDFFHPreyInteracton(red_cats_and_bird_by_pet_store) #... and so forth Point is, I'd like to be able to create such collections cleanly, and a,b,c = *defaultdict(list) seems as clean as it gets. Plus, when I realize I need six (or only three) it's annoying to need to change two redundant things (i.e. both the variable names and the count.) if tl_dr: break() Generally speaking, when you know you need several variables of the same (maybe complex) type, the proposed syntax lets the equals sign fully partition the variables question (how many variables do I need, and what are they called?) from the data structure question (what type should the variables be?) The other syntaxes (except Tim's generator- slurping one, which as Tim points out has its own issues) all spread the variables question across the equals sign, breaking the analogy with single assignment (where the form is Variable = DataStructure). If we're allowing multiple assignment, why can't we allow some form of Variable1, Variable2, Variable3 = DataStructure without reaching for list comprehensions, copy-pasting and/or keeping a side count of the variables? if much_tl_dr: break() Let me address one smell from my particular example, which may be the one you're noticing. If I needed fifty parallel collections I would not use separate variables; I've coded a ghastly defaultdefaultdict just for this purpose, which effectively permits what most people would express as defaultdict(defaultdict(list)) [not possible AFAIK with the existing defaultdict class]. But for reasons of explicitness and simplicity I try to avoid hash-tables of hash-tables (and higher iterations). I'm not trying to use dicts to inner-platform a fully hashed version of SQL, but to boil things down. Also bear in mind, reading the above, that I do a lot of script-type programming which is constantly being changed under severe time pressure (often as I'm sitting next to my clients), which needs to be as simple as possible, and which tech-savvy non-programmers need to understand. A lot of my value comes from quickly creating code which other people (usually research academics) can feel ownership over. Python is already a great language for this, and anything which makes the syntax cleaner and more expressive and which eliminates redundancy helps me do my job better. If I were coding big, stable applications where the defaultdicts were created in a header file and wouldn't be touched again for three years, I would care less about a more awkward initialization method. -- http://mail.python.org/mailman/listinfo/python-list
Re: Syntactic sugar for assignment statements: one value to multiple targets?
On Aug 17, 3:13 am, Chris Angelico wrote: > Minor clarification: You don't want to initialize them to the same > value, which you can do already: > > a=b=c=d=e=dict() Right. Call the proposed syntax the "instantiate separately for each target" operator. (It can be precisely defined as a * on the RHS of a one-into-many assignment statement--i.e. an assignment statement with 1 object on the RHS and more than 1 on the LHS). It has only one very modest function, which is to unpack a, b, c, d, e = *dict() to a, b, c, d, e = dict(), dict(), dict(), dict(), dict() so that you have n separate objects instead of one. If you want the same object duplicated five times, you'd best use a=b=c=d=e=dict(). (I'd guess that 90% of the people who try the a=b=c version actually *want* separate objects and are surprised at what they get--I made that mistake a few times!--but changing either behavior would be a very bad idea. This proposed syntax would be the Right Way to get separate objects.) Maybe this is more visibly convenient with a complex class, like x, y, z = *SuperComplexClass(param1, param2, kwparam = "3", ...) where you need three separate objects but don't want to duplicate the class call (for obvious copy-paste reasons) and where bundling it in a list comprehension: x, y, z = [SuperComplexClass(param1, etc, ...) for _ in range(3)] layers gunk on top of something that's already complex. > I think it's going to work out something very similar to a > list comprehension (as has been mentioned). Right; kind of a self-limiting generator[1], although that sounds MUCH more complex than it needs to. It's really just sugar. Not that it this is a suggestion :) but it could easily be done with a pre- processor. It would also be perfectly amenable to automated code conversion (i.e. 3to2). On Aug 16, 10:11 pm, MRAB wrote: > 1. Repeated evaluation of an expression: "dict()" would be evaluated as > many times as necessary. In other words, it's an unlimited generator. > 2. Lazy unpacking: unpacking normally continues until the source is > exhausted, but here you want it to stop when the destination (the RHS) > is satisfied. Yes, this is a good way to think of it. (Although I think you meant to type LHS, right?) * in this context would tell Python to do both of these things at once: evaluate successively and unpack lazily to the destination. Although it's still fully (and more simply) explainable as sugar. [1] Self-limiting is the key here. As above, the only proposed methods which don't require manual tallying on the RHS are Tim's very creative a,b,c,d,e,*scrap = (dict() for _ in range()), which in this case creates a list containing 9994 unnecessary dicts, or the purer but Python-crashing a,b,c,d,e,*scrap = (dict() for _ in itertools.count()). Tim's intuition, which I share, is that in both cases *scrap should, since it's in final position, actually become the generator-in-state-6, rather than slurping the rest into a list. One argument for this is that the present behavior is (surprisingly, counterintuitively) identical for list comprehensions and generator expressions. Changing the outer parens to brackets yields the same results. But that's a separate, more complex proposal which would need its own use cases. -- http://mail.python.org/mailman/listinfo/python-list
Re: Syntactic sugar for assignment statements: one value to multiple targets?
On Aug 17, 5:45 am, Chris Angelico wrote: (snip) > > Right. Call the proposed syntax the "instantiate separately for each > > target" operator. > (snip) > It might just > as easily be some other function call; for instance: > > head1,head2,head3=file.readline() Hm--that's interesting! OK, call it the "evaluate separately for each target" operator. Same benefits in this use case; if I realize that the file only has two header lines, I can just change head1, head2, head3 = *file.readline() to head1, head2 = *file.readline() without needing to keep a RHS like = [file.readline() for _ in range(3)] in lockstep with the number of variables I'm assigning. Presumably this syntax should be disallowed, as a grammatical matter, when there's a starred target (per PEP 3132/language reference 6.2). That is, head, *body, tail = *file.readline() is disallowed, since it is (by definition) simply sugar for head = file.readline() *body = file.readline() tail = file.readline() and *body = file.readline() is disallowed (see PEP 3132). (Here, of course, you'd just want head, *body, tail = file.readlines(), which is perfectly good code.) PS. Off-topic, but the *target syntax already gets into similar territory, since a, *b, c = itertools.count() crashes with a MemoryError--but what else could it do? Ruling out such infinite-assignment statements on a grammatical basis would require solving the halting problem through static analysis, which might be a bit inefficient :P -- http://mail.python.org/mailman/listinfo/python-list
if statement, with function inside it: if (t = Test()) == True:
Hello, I'm trying to do a if statement with a function inside it. I want to use that variable inside that if loop , without defining it. def Test(): return 'Vla' I searching something like this: if (t = Test()) == 'Vla': print t # Vla or if (t = Test()): print t # Vla -- The long way t = Test() if (t == 'Vla': print t # must contain Vla Greetings, GCMartijn -- http://mail.python.org/mailman/listinfo/python-list
Re: if statement, with function inside it: if (t = Test()) == True:
On 24 apr, 12:11, Steven D'Aprano wrote: > On Fri, 24 Apr 2009 03:00:26 -0700, GC-Martijn wrote: > > Hello, > > > I'm trying to do a if statement with a function inside it. I want to use > > that variable inside that if loop , without defining it. > > > def Test(): > > return 'Vla' > > > I searching something like this: > > > if (t = Test()) == 'Vla': > > print t # Vla > > > or > > > if (t = Test()): > > print t # Vla > > Fortunately, there is no way of doing that with Python. This is one > source of hard-to-debug bugs that Python doesn't have. > > > -- The long way > > t = Test() > > if (t == 'Vla': > > print t # must contain Vla > > What's wrong with that? > > -- > Steven- Tekst uit oorspronkelijk bericht niet weergeven - > > - Tekst uit oorspronkelijk bericht weergeven - Nothing is wrong with it , but it cost more lines (= more scrolling) When possible I want to keep my code small. -- http://mail.python.org/mailman/listinfo/python-list
Re: if statement, with function inside it: if (t = Test()) == True:
On 24 apr, 12:15, Chris Rebert wrote: > On Fri, Apr 24, 2009 at 3:00 AM, GC-Martijn wrote: > > Hello, > > > I'm trying to do a if statement with a function inside it. > > I want to use that variable inside that if loop , without defining it. > > > def Test(): > > return 'Vla' > > > I searching something like this: > > > if (t = Test()) == 'Vla': > > print t # Vla > > > or > > > if (t = Test()): > > print t # Vla > > > -- > > The long way > > t = Test() > > if (t == 'Vla': > > print t # must contain Vla > > Disregarding some ugly hacks, Python does not permit assignment in > expressions, so what you're asking for is not possible. > For the goods of readability, prevention of errors, and simplicity, > Python forces you to do it the "long way" (the Right Way(tm) if you > ask most Python partisans). > > If you could explain your situation and the context of your question > in greater detail, someone might be able to suggest an alternate > structure for your code which obviates your desire for such a > construct. > > Cheers, > Chris > -- > I have a blog:http://blog.rebertia.com- Tekst uit oorspronkelijk bericht niet > weergeven - > > - Tekst uit oorspronkelijk bericht weergeven - Oke, thanks. I will use the (correct) long way. -- http://mail.python.org/mailman/listinfo/python-list