Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #10776 > unrolled thread

Syntactic sugar for assignment statements: one value to multiple targets?

Started bygc <gc1223@gmail.com>
First post2011-08-02 18:45 -0700
Last post2011-08-18 09:13 -0400
Articles 20 on this page of 22 — 13 participants

Back to article view | Back to comp.lang.python


Contents

  Syntactic sugar for assignment statements: one value to multiple targets? gc <gc1223@gmail.com> - 2011-08-02 18:45 -0700
    Re: Syntactic sugar for assignment statements: one value to multiple targets? Chris Angelico <rosuav@gmail.com> - 2011-08-03 03:09 +0100
    Re: Syntactic sugar for assignment statements: one value to multiple targets? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-08-03 18:25 +1000
      Re: Syntactic sugar for assignment statements: one value to multiple targets? Tim Chase <python.list@tim.thechases.com> - 2011-08-03 06:15 -0500
      Re: Syntactic sugar for assignment statements: one value to multiple targets? Tim Chase <python.list@tim.thechases.com> - 2011-08-03 06:25 -0500
        Re: Syntactic sugar for assignment statements: one value to multiple targets? gc <gc1223@gmail.com> - 2011-08-16 12:50 -0700
    Re: Syntactic sugar for assignment statements: one value to multiple targets? Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2011-08-03 20:29 +1200
    Re: Syntactic sugar for assignment statements: one value to multiple targets? "Martin P. Hellwig" <martin.hellwig@gmail.com> - 2011-08-16 21:39 +0100
      Re: Syntactic sugar for assignment statements: one value to multiple targets? gc <gc1223@gmail.com> - 2011-08-16 17:14 -0700
        Re: Syntactic sugar for assignment statements: one value to multiple targets? MRAB <python@mrabarnett.plus.com> - 2011-08-17 03:11 +0100
        Re: Syntactic sugar for assignment statements: one value to multiple targets? Chris Angelico <rosuav@gmail.com> - 2011-08-17 08:13 +0100
          Re: Syntactic sugar for assignment statements: one value to multiple targets? gc <gc1223@gmail.com> - 2011-08-17 02:26 -0700
            Re: Syntactic sugar for assignment statements: one value to multiple targets? Chris Angelico <rosuav@gmail.com> - 2011-08-17 10:45 +0100
              Re: Syntactic sugar for assignment statements: one value to multiple targets? gc <gc1223@gmail.com> - 2011-08-17 03:33 -0700
            Re: Syntactic sugar for assignment statements: one value to multiple targets? Terry Reedy <tjreedy@udel.edu> - 2011-08-17 12:12 -0400
            Re: Syntactic sugar for assignment statements: one value to multiple targets? MRAB <python@mrabarnett.plus.com> - 2011-08-17 17:55 +0100
            Re: Syntactic sugar for assignment statements: one value to multiple targets? Chris Angelico <rosuav@gmail.com> - 2011-08-17 18:25 +0100
            Re: Syntactic sugar for assignment statements: one value to multiple targets? "OKB (not okblacke)" <brenNOSPAMbarn@NObrenSPAMbarn.net> - 2011-08-17 19:13 +0000
        Re: Syntactic sugar for assignment statements: one value to multiple targets? Zero Piraeus <schesis@gmail.com> - 2011-08-17 16:07 -0400
    Re: Syntactic sugar for assignment statements: one value to multiple targets? Ethan Furman <ethan@stoneleaf.us> - 2011-08-17 12:52 -0700
    Re: Syntactic sugar for assignment statements: one value to multiple targets? John Pinner <funthyme@gmail.com> - 2011-08-18 02:26 -0700
    Re: Syntactic sugar for assignment statements: one value to multiple targets? Roy Smith <roy@panix.com> - 2011-08-18 09:13 -0400

Page 1 of 2  [1] 2  Next page →


#10776 — Syntactic sugar for assignment statements: one value to multiple targets?

Fromgc <gc1223@gmail.com>
Date2011-08-02 18:45 -0700
SubjectSyntactic sugar for assignment statements: one value to multiple targets?
Message-ID<16ea4848-db0c-489a-968c-ca40700f5806@m5g2000prh.googlegroups.com>
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!

[toc] | [next] | [standalone]


#10777

FromChris Angelico <rosuav@gmail.com>
Date2011-08-03 03:09 +0100
Message-ID<mailman.1815.1312337377.1164.python-list@python.org>
In reply to#10776
On Wed, Aug 3, 2011 at 2:45 AM, gc <gc1223@gmail.com> wrote:
> 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.

If it's an immutable value (such as a constant integer), you can use
syntax similar to C's chained assignment:

a=b=c=0

If you do this with dict(), though, it'll assign the same dictionary
to each of them - not much use.

> # 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()

I think this is probably the best option, although I would be inclined
to use dictionary-literal syntax:
a,b,c,d,e = {},{},{},{},{}

It might be possible to do something weird with map(), but I think
it'll end up cleaner to do it this way.

Chris Angelico

[toc] | [prev] | [next] | [standalone]


#10789

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2011-08-03 18:25 +1000
Message-ID<4e39060f$0$29988$c3e8da3$5496439d@news.astraweb.com>
In reply to#10776
gc wrote:

> 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()


a, b, c, d, e = [dict() for i in range(5)]

Unfortunately there is no way of doing so without counting the assignment
targets. While slightly ugly, it doesn't seem ugly enough to justify the
extra complexity of special syntax for such a special case.


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#10800

FromTim Chase <python.list@tim.thechases.com>
Date2011-08-03 06:15 -0500
Message-ID<mailman.1832.1312370157.1164.python-list@python.org>
In reply to#10789
On 08/03/2011 03:25 AM, Steven D'Aprano wrote:
> gc wrote:
>
>> 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()
>
>
> a, b, c, d, e = [dict() for i in range(5)]
>
> Unfortunately there is no way of doing so without counting the assignment
> targets. While slightly ugly, it doesn't seem ugly enough to justify the
> extra complexity of special syntax for such a special case.

I understand that in Py3k (and perhaps back-ported into later 2.x 
series?) one can do something like

a, b, c, d, e, *junk = (dict() for _ in range(9999))

to prevent the need to count.  However, I was disappointed with 
all the generator-ification of things in Py3k, that this "and the 
rest" syntax slurps up the entire generator, rather than just 
assigning the iterator.  That would much more tidily be written as

a,b,c,d,e, *more_dict_generator = (dict() for _ in itertools.count())

(itertools.count() happening to be a infinite generator).  I can 
see the need to slurp if you have things afterward:

   a,b,c, *junk ,d,e = iterator(...)

but when the "and the rest" is the last one, it would make sense 
not to force a slurp.

-tkc




[toc] | [prev] | [next] | [standalone]


#10801

FromTim Chase <python.list@tim.thechases.com>
Date2011-08-03 06:25 -0500
Message-ID<mailman.1833.1312370748.1164.python-list@python.org>
In reply to#10789
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.

[toc] | [prev] | [next] | [standalone]


#11608

Fromgc <gc1223@gmail.com>
Date2011-08-16 12:50 -0700
Message-ID<5d0d89b4-d026-40ba-b9f1-d72f1731b690@a31g2000vbt.googlegroups.com>
In reply to#10801
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 <python.l...@tim.thechases.com> 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.

[toc] | [prev] | [next] | [standalone]


#10790

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2011-08-03 20:29 +1200
Message-ID<99sf72Fo87U1@mid.individual.net>
In reply to#10776
gc wrote:

> Alternatively, is there a version of iterable multiplication that
> creates new objects rather than just copying the reference?

You can use a list comprehension:

   a, b, c, d, e = [dict() for i in xrange(5)]

or a generator expression:

   a, b, c, d, e = (dict() for i in xrange(5))

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#11613

From"Martin P. Hellwig" <martin.hellwig@gmail.com>
Date2011-08-16 21:39 +0100
Message-ID<j2eki3$43t$1@dont-email.me>
In reply to#10776
On 03/08/2011 02:45, gc wrote:
<cut>
> a,b,c,d,e = *dict()
>
> where * in this context means something like "assign separately to
> all.
<CUT>
> Any thoughts? Thanks!

Well got a thought but I am afraid it is the opposite of helpful in the 
direct sense. So if you don't want to hear it skip it :-)

Although I can not proficiently argument it, it has a certain code smell 
to it. In the sense that it could hint that there is a better more 
readable way of solving that particular problem (taking in account that 
the one letter labels are pure for demonstration purpose).

I would love to see an example where you would need such a construct.

-- 
mph

[toc] | [prev] | [next] | [standalone]


#11632

Fromgc <gc1223@gmail.com>
Date2011-08-16 17:14 -0700
Message-ID<7f30e39b-4e4f-4426-b819-b4670871d199@df3g2000vbb.googlegroups.com>
In reply to#11613
On Aug 16, 4:39 pm, "Martin P. Hellwig" <martin.hell...@gmail.com>
wrote:
> On 03/08/2011 02:45, gc wrote:
> <cut>
>
> > a,b,c,d,e = *dict()
>
> > where * in this context means something like "assign separately to
> > all.

> <snip> . . . it has a certain code smell to it. <snip>
> 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.

[toc] | [prev] | [next] | [standalone]


#11649

FromMRAB <python@mrabarnett.plus.com>
Date2011-08-17 03:11 +0100
Message-ID<mailman.112.1313547164.27778.python-list@python.org>
In reply to#11632
On 17/08/2011 01:14, gc wrote:
> On Aug 16, 4:39 pm, "Martin P. Hellwig"<martin.hell...@gmail.com>
> wrote:
>> On 03/08/2011 02:45, gc wrote:
>> <cut>
>>
>>> a,b,c,d,e = *dict()
>>
>>> where * in this context means something like "assign separately to
>>> all.
>
>> <snip>  . . . it has a certain code smell to it.<snip>
>> 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.
>
[snip]
As I see it, there are 2 issues:

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.

It just happens that in your use-case they are being used together.

[toc] | [prev] | [next] | [standalone]


#11659

FromChris Angelico <rosuav@gmail.com>
Date2011-08-17 08:13 +0100
Message-ID<mailman.117.1313565232.27778.python-list@python.org>
In reply to#11632
On Wed, Aug 17, 2011 at 1:14 AM, gc <gc1223@gmail.com> wrote:
> 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.
>

Minor clarification: You don't want to initialize them to the same
value, which you can do already:

a=b=c=d=e=dict()

You want to initialize them each to a fresh evaluation of the same
expression. What you're asking for is a syntax that writes an
expression once, but evaluates it many times; I think it's going to
work out something very similar to a list comprehension (as has been
mentioned).

ChrisA

[toc] | [prev] | [next] | [standalone]


#11671

Fromgc <gc1223@gmail.com>
Date2011-08-17 02:26 -0700
Message-ID<c6cd02ea-3020-4970-82c6-8d289738bf37@o11g2000yql.googlegroups.com>
In reply to#11659
On Aug 17, 3:13 am, Chris Angelico <ros...@gmail.com> 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 <pyt...@mrabarnett.plus.com> 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(9999)), 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.

[toc] | [prev] | [next] | [standalone]


#11673

FromChris Angelico <rosuav@gmail.com>
Date2011-08-17 10:45 +0100
Message-ID<mailman.123.1313574326.27778.python-list@python.org>
In reply to#11671
On Wed, Aug 17, 2011 at 10:26 AM, gc <gc1223@gmail.com> wrote:
> On Aug 17, 3:13 am, Chris Angelico <ros...@gmail.com> 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).
>

Agreed, but there's no requirement for it to be instantiating
something (although that will be common). "dict()" is an expression
that you want to evaluate five (or however many) times. It might just
as easily be some other function call; for instance:

head1,head2,head3=file.readline()

to read three lines from a file. Or it mightn't even be a function call per se.

ChrisA

[toc] | [prev] | [next] | [standalone]


#11675

Fromgc <gc1223@gmail.com>
Date2011-08-17 03:33 -0700
Message-ID<2e4e9b97-df0e-4c1e-9f41-db2b475add21@d7g2000vbv.googlegroups.com>
In reply to#11673
On Aug 17, 5:45 am, Chris Angelico <ros...@gmail.com> 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


[toc] | [prev] | [next] | [standalone]


#11690

FromTerry Reedy <tjreedy@udel.edu>
Date2011-08-17 12:12 -0400
Message-ID<mailman.133.1313597602.27778.python-list@python.org>
In reply to#11671
The issue behind this thread is that for immutable objects, binding to n 
copies has the same effect as n bindings to one object (so one does not 
really have to know which one is doing), whereas the two are different 
for mutable objects (so one does have to know). In short, identity 
matters for mutables but not for immutables. Python programmers must 
learn both this and the fact that Python does not make copies unless asked.

Adding a special case exception to the latter to mask the former does 
not seem like a good idea.

On 8/17/2011 5:26 AM, gc wrote:

> It has only one very modest function, which is to unpack
>
> a, b, c, d, e = *dict()

*expression has already been proposed to generally mean what it does in 
function calls -- unpack the iterator in place.

funnylist = [1,2,*dict,99,100]
# == [1,2]+list(dict)+[99,100]

would interpolate the keys of the dict into the list.

There is a tracker issue for this -- it would be a follow-on to the 
addition of *traget in assignments.

In a real sense, "a,b = iterable" *already* means "a,b = *iterable". If 
*iterable had been in general use from the beginning, presume the latter 
is how we would write sequence unpacking for assignments.

> a, b, c, d, e = dict(), dict(), dict(), dict(), dict()

*expression will not be changed in meaning to magically re-evaluate an 
expression some multiple number of times according to code elsewhere.

> 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().

Not 'duplicated', but 'bound'.

> (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!

Guessing that 90% of people are like you is likely to be wrong.
I think this use case (for more than 2 or 3 copies) is pretty rare for 
most people.

Where many people do trip up is "array = [[0]*i]*j", expecting to get j 
copies of [0]*i rather than j bindings of one object. But then, they 
must have the same wrong idea that [0]*i makes i copies of 0. For 
immutable 0, the misunderstanding does not matter. For mutable [0]*i, it 
does. People *must* learn that sequence multiplication multiplies 
bindings, not (copies of) objects. Both multiple copy problems have the 
same solution:

array = [[0]*i for _ in range(j)]
a,b,c,d,e = [dict() for _ in range(5)]

The fact that the number of assignment sources (possibly after implicit 
unpacking) and targets have to match, unless one uses *target, and that 
both sides need to be changed if one is, is true of all assignments, not 
just this rare case.

>--but changing either behavior would be a
> very bad idea. This proposed syntax would be the Right Way to get
> separate objects.)

It would be very Wrong as it already has a very different meaning.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#11696

FromMRAB <python@mrabarnett.plus.com>
Date2011-08-17 17:55 +0100
Message-ID<mailman.135.1313600157.27778.python-list@python.org>
In reply to#11671
On 17/08/2011 10:26, gc wrote:
> On Aug 17, 3:13 am, Chris Angelico<ros...@gmail.com>  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).
>
I think that lazy unpacking is the more important issue because we can
replace instantiation with copying:

def copies(obj, count=None):
     if count is None:
         while True:
             yield obj.copy()
     else:
         for i in range(count):
             yield obj.copy()

(Should it yield deep copies, or should there be a separate deep_copies
function?)

> 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()
>
This becomes:

a, b, c, d, e = copies(dict(), 5)

With lazy unpacking it would become:

a, b, c, d, e = lazy copies(dict())

(Or whatever the syntax is.)

> 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", ...)
>
x, y, z = lazy copies(SuperComplexClass(param1, etc, ...))

[snip]

[toc] | [prev] | [next] | [standalone]


#11702

FromChris Angelico <rosuav@gmail.com>
Date2011-08-17 18:25 +0100
Message-ID<mailman.139.1313601914.27778.python-list@python.org>
In reply to#11671
On Wed, Aug 17, 2011 at 5:55 PM, MRAB <python@mrabarnett.plus.com> wrote:
> x, y, z = lazy copies(SuperComplexClass(param1, etc, ...))
>

This assumes that you can construct it once and then copy it reliably,
which may mean that the class implement copying correctly. It also
wouldn't work with:

a, b, c, d = *random.randint(1,20)

which would roll 4d20 and get the results in separate variables. The
OP's idea of separately evaluating the expression would; but to do it
with copying would require a special "randint" object that functions
exactly as an integer but, when copied, would re-randomize.

Perhaps * is the wrong syntactic element to use. Maybe it needs a
special assignment operator:

a, b, c, d @= random.randint(1,20)

which would evaluate its left operand as a tuple of lvalues, then
evaluate its right operand once for each element in the left operand,
and assign to each element in turn. (I've no idea what form of
assignment operator would be suitable, but @= is currently illegal, so
it ought to be safe at least for discussion purposes.)

Chris Angelico

[toc] | [prev] | [next] | [standalone]


#11711

From"OKB (not okblacke)" <brenNOSPAMbarn@NObrenSPAMbarn.net>
Date2011-08-17 19:13 +0000
Message-ID<Xns9F447C2DCDDDBOKB@88.198.244.100>
In reply to#11671
gc wrote:

> 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.

    	That just seems like an odd use case to me.  I rarely find myself 
wanting to make exactly N copies of the same thing and assign them to 
explicit names.  If I'm not making just one, it's usually because 
I'm making some sort of list or dict of them that will be accessed by 
index (not with names like "x", "y", and "z"), in which case a list 
comprehension is the right way to go.

-- 
--OKB (not okblacke)
Brendan Barnwell
"Do not follow where the path may lead.  Go, instead, where there is
no path, and leave a trail."
	--author unknown

[toc] | [prev] | [next] | [standalone]


#11713

FromZero Piraeus <schesis@gmail.com>
Date2011-08-17 16:07 -0400
Message-ID<mailman.145.1313611680.27778.python-list@python.org>
In reply to#11632
:

Off on a tangent ...

On 16 August 2011 20:14, gc <gc1223@gmail.com> wrote:
>
> 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].

Dunno if it's better than your ghastly defaultdefaultdict, but this works:

>>> from collections import defaultdict
>>> ddd = defaultdict(lambda: defaultdict(list))
>>> ddd["foo"]["bar"].append("something")
>>> ddd["foo"]["bar"]
['something']
>>>

 -[]z.

[toc] | [prev] | [next] | [standalone]


#11712

FromEthan Furman <ethan@stoneleaf.us>
Date2011-08-17 12:52 -0700
Message-ID<mailman.144.1313609768.27778.python-list@python.org>
In reply to#10776
gc wrote:
> 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()

This isn't going to happen.  From all the discussion so far I think your 
best solution is a simple helper function (not tested):

def repeat(count_, object_, *args, **kwargs):
     result = []
     for _ in range(count_):
         result.append(object_(*args, **kwargs))
     return result

a, b, c, d, e = repeat(5, dict)

These are each new objects, so depending on the function (like the 
random.rand_int example) the values may not be the same.

Oh, and I put the trailing _ on count and object to minimize possible 
conflicts with keyword arguments.

~Ethan~

[toc] | [prev] | [next] | [standalone]


Page 1 of 2  [1] 2  Next page →

Back to top | Article view | comp.lang.python


csiph-web