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

2011-08-02 Thread gc
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?

2011-08-16 Thread gc
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?

2011-08-16 Thread gc
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?

2011-08-17 Thread gc
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?

2011-08-17 Thread gc
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:

2009-04-24 Thread GC-Martijn
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:

2009-04-24 Thread GC-Martijn
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:

2009-04-24 Thread GC-Martijn
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